Возник вопрос - как верно использовать метод связанный с несколькими моделями. Есть несколько вариантов, но не знаю, какой вернее. Хотелось бы научиться правильно использовать ООП в разрезе 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. Есть и еще варианты, но не хочу больше нагружать бедного читателя. Если кто-то выскажет свои мысли, как это лучше делать, буду очень признателен.