Comments 15
О черезмерно толстых моделях писал nepster09 в своей недавней статье Разработка приложений на Yii2 без опыта — прямой путь в АД, но по моему мнению, проблема сводится к сугубо архитектурной и решается очень аккуратно, хороший пример Tairesh и показал.

Такая структура моделей может быть особенно полезной, когда разные типы одного объекта требуют разных правил валидации или специфического поведения. Описывание особенностей разных типов одной модели в одном классе быстро превращает его в сложноподдерживаемое месиво. А если еще и добавить сценарии — можно плакать. Проверено :)

Что касается практики, я бы добавил, что создание объектов базового класса Car не всегда хорошая идея. Я вижу две стратегии:

1) Car — суперкласс. Есть базовые правила валидации того, что есть у всех, типа
[['type', 'name'], 'required']
, но создание машин через этот класс невозможно, обязательно использовать уточненные классы. Это хорошо, когда каждый тип гарантированно имеет что-то свое, чего принципиально не может быть у базового и не пересекается с другими. Например, у спортивной машины, наверняка, не будет сцепного устройства для прицепа :)

2) Car — общий класс машины. Применим, когда удовлетворяет требованиям для создания большинства записей, но лишь некоторые типы содержат что-то специфической и требуют отдельных правил, которые будут вынесены в отдельный класс.

Открытым может оставаться вопрос о правильном наследовании правил валидации и возможностям комбинирования похожих свойств, но
это может сильно отличатся в разных предметных областях.
Из песочницы нельзя отметить публикацию как перевод, ссылку добавил. Огромное вам спасибо за замечательные рецепты! Не думал, что автор русскоязычный, да ещё и есть на хабре.
Если ещё переводить будете, имейте ввиду, что степень готовности там разная. Если у этот рецепт готов на 99%, то некоторые ещё совсем черновики.
просто как мнение — может быть интереснее и правильнее было бы сделать этот пример, показывая, что модели эти могут иметь разные поля и как этим всем управлять
Разные — это, скорее всего, не Single Table Inheritance, а что-то типа Class Table Inheritance. Просто потому как хранить поля для такого в STI не оптимально.
Из названия статьи показалось, что в yii2, точнее в его AR есть реализация STI. Но нет.
А за статью спасибо, я реализовывал наследование в моделях более хардко(р)но :)
Дублирования кода, можно избежать, вынеся эти методы в класс Car и используя вместо константы protected метод Car::getType, но сейчас я не буду на этом останавливаться для простоты.

Можно и не использовать. Достаточно будет описать эти методы в классе Car таким образом:

class Car extends ActiveRecord
{
    const TYPE = 'car';

    public function init()
    {
        $this->type = static::TYPE;
        parent::init();
    }

    public static function find()
    {
        return new CarQuery(get_called_class(), ['type' => static::TYPE]);
    }

    public function beforeSave($insert)
    {
        $this->type = static::TYPE;
        return parent::beforeSave($insert);
    }
}


Ключевое слово static как раз будет указывать, что речь идёт о том классе, который наследует логику, а не о том классе, в котором эта логика прописана (как происходит при использовании self). Соответственно, если класс HeavyCar расширяет класс Car, то при вызове метода HeavyCar::init() инструкция $this->type = static::TYPE; будет, по сути, исполняться как $this->type = HeavyCar::TYPE;, а при вызове SportCar::init() будет исполняться как $this->type = SportCar::TYPE;. При этом, если в дочернем классе нет константы TYPE, то она будет искаться в родительском классе. Вот пример, как работает static:

class ParentClass
{
    const TYPE = 'parent';

    public function getType()
    {
        return static::TYPE;
    }
}

class ChildOne extends ParentClass
{
    const TYPE = 'child one';
}

class ChildTwo extends ParentClass 
{}

$instance = new ParentClass();
echo $instance->getType() . PHP_EOL; // parent

$instance = new ChildOne();
echo $instance->getType() . PHP_EOL; // child one

$instance = new ChildTwo();
echo $instance->getType() . PHP_EOL; // parent


А с самим подходом, когда для работы с одной и той же таблицей используются несколько моделей, я не согласен, потому что появится соблазн ввести в таблице Car поле, хранящее JSON-кодированную (или ещё каким-нибудь способом сериализованную) структуру данных с информацией о прицепах для HeavyCar. В случае с СУБД, которые поддерживают JSON это может быть нормально, но если используется MySQL, как это часто бывает, лучше всё-таки подумать над формализацией данных на уровне БД, а не на уровне кода.
Я уточню на всякий случай про HeavyCar::init(), который выглядит так, будто я пытаюсь вызывать статический метод init, хотя он при этом не статический. В PHP на письме принято методы класса показывать именно таким образом — вне зависимости от того, статичный метод или нет. Просто так принято писать. В официальной документации, например, так и делается. В самом коде, конечно же, я бы использовал коррекный способ вызова в зависимости от статичности метода.
Буквально вчера читал про универсальные модули комментариев для YII2. Универсальные — в том смысле что один и тот же объект комментария может быть привязан к любому другому объекту (пост в блог, товар в каталоге и т.п.) одинаковым образом. Там одним из ключевых вопросов была уникальность идентификаторов объектов (id поста и id товара могут совпадать). Вот тут что-то похожее буду пробовать освоить в контексте этой задачи. Спасибо.
А если еще, разделить Модель на Объект поведения/состояния и репозиторий, то и вовсе можно будет паттерн называть Domain model pattern / Repository pattern :D
А зачем вручную фактически устанавливать связь типов и классов, если можно в поле БД в базовой модели записывать имя класса, а в instantiate создавать модель по полю в БД?
В своём проекте я так и делаю, но выглядит это не очень красиво
Я поехавший и вставляю код скриншотами
image
Only those users with full accounts are able to leave comments. Log in, please.