@izheme
Познаю мир

Как верно использовать метод связанный с несколькими моделями в Yii2?

Возник вопрос - как верно использовать метод связанный с несколькими моделями. Есть несколько вариантов, но не знаю, какой вернее. Хотелось бы научиться правильно использовать ООП в разрезе Yii2. Если кому-то не лень во все это вникать, то большое спасибо =). Итак, у нас есть:
AR модели One, Two, Three

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

class Two extends ActiveRecord
{
    // Есть атрибуты - поля в БД: id, one_id
    …
    public 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, group
    …
    public function getThree()
    {
        return $this->hasMany(Three::className(), ['id' => 'two_id'])
                ->viaTable('two_three', ['three_id' => 'id']);
    }
    ...
}

Для класса One необходимо сделать следующий функционал: выбор всех связанных с с текущим объектом моделей Three у которых атрибут category_id равен атрибуту category_id текущего объекта One. Итоговый массив сгруппировать по group - атрибуту модели Three. Результат временно сохранить. Т.е. все это можно добиться добавив в класс One следующие свойство и метод:
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] : [];
}


Далее появляется задача для класса Two реализовать подобный функционал, которого можно добиться добавив в класс 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] : [];
}


Эти методы можно получать непосредственно через объекты One и Two соответственно. Т.е. везде, где есть доступ к готовым объектам. Это плюс (наверное).

Собственно, после этого и начинаются проблемы. Как видно, два метода, добавленные в классы One и Two отличаются только источником данных для метода ArrayHelper::map.

Как это лучше сделать? Можно оставить как есть, но ведь можно удалить данные методы и добавить уже в класс Three свойство и метод:
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] : [];
}


Данный метод придется вызывать отдельно в контролере и присваивать его результат переменной, которую уже передавать вид, но это и логично. Можно пойти по другому пути в классах One и Two оставить методы:
public function threeArrayForGroup($group)
{
    $data = Three::find()->where(['category_id' => $this->category_id])->all();
    $key = ‘category’;
    return Three:: threeArrayForGroup($group, $data, $key);
}

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

Соответственно, а в классе Three внести соответствующие изменения в метод.
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] : [];
}


Тогда методы можно будет вызывать через объекты One и Two. Есть и еще варианты, но не хочу больше нагружать бедного читателя. Если кто-то выскажет свои мысли, как это лучше делать, буду очень признателен.
  • Вопрос задан
  • 453 просмотра
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы