N
N
Nikolay2015-11-13 17:46:13
Yii
Nikolay, 2015-11-13 17:46:13

How to properly use multi-model related method in Yii2?

The question arose - how to correctly use the method associated with several models. There are several options, but I don't know which one is better. I would like to learn how to use OOP correctly in the context of Yii2. If someone is not too lazy to delve into all this, then thank you very much =). So we have:
AR models One, Two, Three

class One extends ActiveRecord
{
    // Есть атрибуты - поля в БД: id, category_idpublic function getTwo()
    {
       return $this->hasMany(Two::className(), ['one_id' => 'id']);
    }
    ...
}

class Two extends ActiveRecord
{
    // Есть атрибуты - поля в БД: id, one_idpublic function getOne()
    {
        return $this->hasOne(One::className(), ['id' => 'one_id']);
    }

    public function getThree()
    {
        return $this->hasMany(Three::className(), ['id' => 'three_id'])
                ->viaTable('two_three', ['two_id' => 'id']);
    }
    ...
}

class Three extends ActiveRecord
{
    // Есть атрибуты - поля в БД: id, category_id, name, grouppublic function getThree()
    {
        return $this->hasMany(Three::className(), ['id' => 'two_id'])
                ->viaTable('two_three', ['three_id' => 'id']);
    }
    ...
}

For the One class, the following functionality needs to be done: selection of all Three models associated with the current object whose category_id attribute is equal to the category_id attribute of the current One object. Group the resulting array by group - an attribute of the Three model. Save the result temporarily. Those. All this can be achieved by adding the following property and method to the One class:
private $threeByGroup = [];
…
public function threeArrayForGroup($group)
{
    if (empty($this->threeByGroup)) {
        $this->threeByGroup = ArrayHelper::map(
               Three::find()->where(['category_id' => $this->category_id])->all(),
               'id', 'name', 'group'
         );
     }
     return isset($this->threeByGroup[$group]) ? $this->threeByGroup[$group] : [];
}

Next, the task appears for class Two to implement similar functionality, which can be achieved by adding a property and method to class Two:
private $threeByGroup = [];
…
public function threeArrayForGroup($group)
{
    if (empty($this->threeByGroup)) {
        $this->threeByGroup = ArrayHelper::map(
            $this->getThree()->all(),
            'id', 'name', 'group'
        );
    }
    return isset($this->threeByGroup[$group]) ? $this->threeByGroup[$group] : [];
}

These methods can be accessed directly through the One and Two objects, respectively. Those. wherever there is access to ready-made objects. This is a plus (probably).
Actually, after that, the problems begin. As you can see, the two methods added to the classes One and Two differ only in the data source for the ArrayHelper::map method.
What is the best way to do this? You can leave it as it is, but you can remove these methods and add a property and method to the Three class:
public static $threeByGroup = [];
...
public static function threeArrayForGroup($group, $category_id = false, $two_id = false)
{
    if (! $category_id && ! $two_id) {
        throw new InvalidParamException('Хотя бы один из двух аргументов должен быть не пустым: $category_id $two_id ');
    }
    if ($category_id) {
        $data =  Three::find()->where(['category_id' => $category_id])->all();
    } elseif ($two_id) {
        $data = Three::find()->joinWith('two')->where(['two.id' => $two_id])->all();
    }
    $key = $category_id .’_’.$two_id;
    if (empty($this->threeByGroup[$key])) {
        $this->threeByGroup[$key] = ArrayHelper::map(
            $data,
            'id', 'name', 'group'
        );
    }
    return isset($this->threeByGroup[$key][$group]) ? $this->threeByGroup[$key][$group] : [];
}

This method will have to be called separately in the controller and its result assigned to the variable that the view is already passing, but this is logical. You can go the other way in the classes One and Two to leave the methods:
public function threeArrayForGroup($group)
{
    $data = Three::find()->where(['category_id' => $this->category_id])->all();
    $key = ‘category’;
    return Three:: threeArrayForGroup($group, $data, $key);
}

and
public function threeArrayForGroup($group)
{
    $data =   $this->getThree()->all();
    $key = ‘two_’ . $this->id;
    return Three:: threeArrayForGroup($group, $data, $key);
}

Accordingly, in the Three class, make the appropriate changes to the method.
public static $threeByGroup = [];
...
public static function threeArrayForGroup($group, $data, $key)
{
    if (empty($this->threeByGroup[$key])) {
        $this->threeByGroup[$key] = ArrayHelper::map(
            $data,
            'id', 'name', 'group'
        );
    }
    return isset($this->threeByGroup[$key][$group]) ? $this->threeByGroup[$key][$group] : [];
}

Then the methods can be called through the One and Two objects. There are other options, but I do not want to burden the poor reader any more. If someone can share their thoughts on how best to do this, I would be very grateful.

Answer the question

In order to leave comments, you need to log in

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question