В соответствии с этим паттерном, нужно использовать общую таблицу для наследуемых моделей и в этой таблице добавить поле type
, которое будет определять класс-наследника этой записи.
В этой статье будет использоваться следующая структура наследования моделей:
Car
|- SportCar
|- HeavyCar
Таблица
`car`
имеет следующую структуру:
CREATE TABLE `car` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`type` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `car` (`id`, `name`, `type`) VALUES (1, 'Kamaz', 'heavy'), (2, 'Ferrari', 'sport'), (3, 'BMW', 'city');
Модель
Car
можно сгенерировать с помощью Gii.Как это работает
Нам понадобится простой класс запроса
CarQuery
, который автоматически будет подставлять тип автомобиля.
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);
}
}
И теперь мы можем создать классы-наследники от
Car
. В них мы определим константу TYPE
которая будет хранить тип автомобиля для записи в поле type
модели, и переопределим ActiveRecord-методы init
, find
и beforeSave
, в которых этот тип будет автоматически подставляться в модель и в запрос CarQuery
. TYPE
не обязательно должен быть строкой (разумнее использовать unsigned int) и даже не обязательно константой, но для простоты сделаем так. Таким будет SportCar
:
namespace app\models;
class SportCar extends Car
{
const TYPE = 'sport';
public function init()
{
$this->type = self::TYPE;
parent::init();
}
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);
}
}
И таким
HeavyCar
:
namespace app\models;
class HeavyCar extends Car
{
const TYPE = 'heavy';
public function init()
{
$this->type = self::TYPE;
parent::init();
}
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);
}
}
Дублирования кода, можно избежать, вынеся эти методы в класс
Car
и используя вместо константы protected
метод Car::getType
, но сейчас я не буду на этом останавливаться для простоты.
Теперь нам нужно переопределить метод Car:instantiate:
для автоматического создания модели нужного класса, в зависимости от типа:
public static function instantiate($row)
{
switch ($row['type']) {
case SportCar::TYPE:
return new SportCar();
case HeavyCar::TYPE:
return new HeavyCar();
default:
return new self;
}
}
Знающий о всех наследниках
switch case
в коде модели-родителя — на самом деле не слишком удачное решение, но, опять же, это сделано только для простоты понимания подхода и от этого несложно избавиться чуть усложнив код.
Теперь для single table inheritance
всё готово. Вот простой пример его прозрачного использования в контроллере:
// finding all cars we have
$cars = Car::find()->all();
foreach ($cars as $car) {
echo "$car->id $car->name " . get_class($car) . "<br />";
}
// finding any sport car
$sportCar = SportCar::find()->limit(1)->one();
echo "$sportCar->id $sportCar->name " . get_class($sportCar) . "<br />";
Этот код выведет следующее:
1 Kamaz app\models\HeavyCar
2 Ferrari app\models\SportCar
3 BMW app\models\Car
2 Ferrari app\models\SportCar
Как можно заметить, модели получают класс в соответствии с указанным у них типом.
Обработка уникальных значений
Если в таблице есть поля, отмеченные в модели как уникальные, для того чтобы
UniqueValidator
пропускал их у разных классов, можно использовать такую приятную фишку Yii как targetClass
:
public function rules()
{
return [
[['MyUniqueColumnName'], 'unique', 'targetClass' => Car::classname()],
];
}
This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.
Комментариев нет:
Отправить комментарий