Как верно использовать метод связанный с несколькими моделями в Yii2? Возник вопрос - как верно использовать метод связанный с несколькими моделями. Есть несколько вариантов, но не знаю, какой вернее. Хотелось бы научиться правильно использовать ООП в разрезе Yii2. Если кому-то не лень во все это вникать, то большое спасибо =). Итак, у нас есть: AR модели One, Two, Threeclass 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. Есть и еще варианты, но не хочу больше нагружать бедного читателя. Если кто-то выскажет свои мысли, как это лучше делать, буду очень признателен.
Не совсем понятно, почему вы добавляете свойство $threeByGroup как приватное для каждого объекта класса One и Two. Вместо этого, вы можете использовать статическое свойство в классе Three для хранения кэша данных. Таким образом, данные будут доступны для всех объектов классов One и Two.
Также, вместо вызова метода threeArrayForGroup через класс Three, вы можете добавить статический метод в классы One и Two, который будет обращаться к методу в классе Three.
Например, вы можете изменить методы в классах One и Two следующим образом:
class One extends ActiveRecord { public function threeArrayForGroup($group) { return Three::threeArrayForGroupByCategory($group, $this->category_id); } } class Two extends ActiveRecord { public function threeArrayForGroup($group) { return Three::threeArrayForGroupByTwo($group, $this->id); } }
И соответственно, измените статический метод в классе Three:
class Three extends ActiveRecord { public static $threeByGroup = []; public static function threeArrayForGroupByCategory($group, $category_id) { $key = $category_id; if (empty(self::$threeByGroup[$key])) { $data = self::find()->where(['category_id' => $category_id])->all(); self::$threeByGroup[$key] = ArrayHelper::map( $data, 'id', 'name', 'group' ); } return isset(self::$threeByGroup[$key][$group]) ? self::$threeByGroup[$key][$group] : []; } public static function threeArrayForGroupByTwo($group, $two_id) { $key = $two_id; if (empty(self::$threeByGroup[$key])) { $data = self::find()->joinWith('two')->where(['two.id' => $two_id])->all(); self::$threeByGroup[$key] = ArrayHelper::map( $data, 'id', 'name', 'group' ); } return isset(self::$threeByGroup[$key][$group]) ? self::$threeByGroup[$key][$group] : []; } }
Таким образом, вы можете вызывать метод threeArrayForGroup через объекты классов One и Two, который будет делегировать запрос к статическому методу класса Three, хранящему кэш данных для всех объектов.
Не совсем понятно, почему вы добавляете свойство $threeByGroup как приватное для каждого объекта класса One и Two. Вместо этого, вы можете использовать статическое свойство в классе Three для хранения кэша данных. Таким образом, данные будут доступны для всех объектов классов One и Two.
Также, вместо вызова метода threeArrayForGroup через класс Three, вы можете добавить статический метод в классы One и Two, который будет обращаться к методу в классе Three.
Например, вы можете изменить методы в классах One и Two следующим образом:
class One extends ActiveRecord {public function threeArrayForGroup($group) {
return Three::threeArrayForGroupByCategory($group, $this->category_id);
}
}
class Two extends ActiveRecord {
public function threeArrayForGroup($group) {
return Three::threeArrayForGroupByTwo($group, $this->id);
}
}
И соответственно, измените статический метод в классе Three:
class Three extends ActiveRecord {public static $threeByGroup = [];
public static function threeArrayForGroupByCategory($group, $category_id) {
$key = $category_id;
if (empty(self::$threeByGroup[$key])) {
$data = self::find()->where(['category_id' => $category_id])->all();
self::$threeByGroup[$key] = ArrayHelper::map(
$data,
'id', 'name', 'group'
);
}
return isset(self::$threeByGroup[$key][$group]) ? self::$threeByGroup[$key][$group] : [];
}
public static function threeArrayForGroupByTwo($group, $two_id) {
$key = $two_id;
if (empty(self::$threeByGroup[$key])) {
$data = self::find()->joinWith('two')->where(['two.id' => $two_id])->all();
self::$threeByGroup[$key] = ArrayHelper::map(
$data,
'id', 'name', 'group'
);
}
return isset(self::$threeByGroup[$key][$group]) ? self::$threeByGroup[$key][$group] : [];
}
}
Таким образом, вы можете вызывать метод threeArrayForGroup через объекты классов One и Two, который будет делегировать запрос к статическому методу класса Three, хранящему кэш данных для всех объектов.