У меня есть одна таблица в базе данных (MySQL). Но эта таблица хранит несколько немного разных типов строк. Тип зависит от этой таблицы type
колонка. У меня есть абстрактный класс ActiveRecord для таблицы и несколько дочерних подклассов, реализующих слегка отличную логику для строк одной и той же таблицы разных типов. Сейчас я реализую действие контроллера обновления для всех типов строк. Мне предоставляется идентификатор строки, и мне нужно создать экземпляр ActiveRecord, представляющий строку с этим идентификатором. Но мне как-то нужно создавать экземпляры разных подклассов в зависимости от типа соответствующей строки.
Если бы мне предоставили как тип, так и идентификатор, я мог бы использовать фабрику, чтобы выбрать соответствующий подкласс. Но я уже могу иметь тип в базе данных, и идентификатор дает мне достаточно информации, чтобы выбрать его оттуда. Но если бы я сначала выбрал тип из базы данных, а затем создал экземпляр соответствующего подкласса, это означало бы выполнение одного и того же запроса дважды.
Я хочу найти хороший способ получить данные из базы данных, а затем выбрать правильный подкласс ActiveRecord, чтобы создать для него экземпляр, не делая чрезмерных запросов и не требуя избыточных данных. Есть ли способ сделать это Yii2?
Или я должен подойти к этой проблеме как-то иначе? Проблема в том, что в одной таблице хранятся несколько почти одинаковых, но немного разных сущностей с немного другой бизнес-логикой.
Один из подходов к этой проблеме называется «наследование одной таблицы» и описан Мартином Фаулером. Вот. Также есть хорошая статья о его реализации в Yii 2 в samdarkкулинарная книга (одна из основных авторов Yii 2), которая в настоящее время находится в процессе написания, но доступна на Github.
Я не собираюсь копировать всю статью, но одной ссылки недостаточно. Вот несколько важных вещей:
1) Создайте общий запрос для всех типов объектов (например, автомобилей):
namespace app\models;
use yii\db\ActiveQuery;
class CarQuery extends ActiveQuery {
public $type;
public function prepare($builder)
{
if ($this->type !== null) {
$this->andWhere(['type' => $this->type]);
}
return parent::prepare($builder);
}
}
2) Создайте отдельную модель для каждого типа (исходя из общей модели автомобиля):
Спортивные машины:
namespace app\models;
class SportCar extends Car
{
const TYPE = 'sport';
public static function find()
{
return new CarQuery(get_called_class(), ['type' => self::TYPE]);
}
public function beforeSave($insert)
{
$this->type = self::TYPE;
return parent::beforeSave($insert);
}
}
Тяжелые машины:
namespace app\models;
class HeavyCar extends Car
{
const TYPE = 'heavy';
public static function find()
{
return new CarQuery(get_called_class(), ['type' => self::TYPE]);
}
public function beforeSave($insert)
{
$this->type = self::TYPE;
return parent::beforeSave($insert);
}
}
3) Override instantiate()
метод в Car
Модель для возврата правильного типа автомобилей:
public static function instantiate($row)
{
switch ($row['type']) {
case SportCar::TYPE:
return new SportCar();
case HeavyCar::TYPE:
return new HeavyCar();
default:
return new self;
}
}
Тогда вы можете использовать любой тип автомобилей индивидуально, как обычные модели.
Других решений пока нет …