Skip to content

Модели

Третья версия работает на Eloquent. Модели живут в PageBlocks\App\Models\ и достаются по короткому имени хелпером model():

php
model('PbResource')->published()->limit(10)->get();

Почти всё здесь — обычный Eloquent. Кроме одной вещи, и на ней держится весь компонент.

Поля, которых нет в колонках

Конструктор позволяет менеджеру добавить блоку или таблице поле без миграции. Колонки у такого поля нет — оно живёт внутри JSON-колонки. AbstractDataModel делает это незаметным:

php
$block->subtitle = 'Привет';   // колонки `subtitle` не существует
$block->save();
echo $block->subtitle;         // Привет

Работают два хука:

ХукЧто происходит
retrievedКлючи JSON-колонки, не являющиеся настоящими колонками, разворачиваются в extra-атрибуты
savingExtra-атрибуты сворачиваются обратно в JSON-колонку

Чтение идёт через __get, поэтому extra-атрибут неотличим от колонки и в шаблонах, и в коде. Аксессоры для полей конструктора писать не нужно.

В какую именно JSON-колонку

Зависит от семейства модели:

СемействоБазовый классJSON-колонка
Данные — блоки, строки таблиц, ресурсыAbstractDataModeldata
Конструктор — поля, колонкиConstructorModelproperties
Конструктор — блоки, таблицыConstructorModelpermissions

Не присваивайте JSON-колонку целиком

$field->properties = ['foo' => 1] выбрасывает всё, что хук собирался положить обратно, и поле теряет настройки. Задавайте отдельные ключи: $field->foo = 1.

Extra-атрибуты фильтруются конструктором — если он есть

При сохранении extra-атрибуты сверяются с именами полей конструктора, и всё незнакомое отбрасывается. Именно это держит data в чистоте.

Но модель без конструктора сохраняет все свои extra:

php
$fields = $model->fields()->get()->toArray();
$data = $fields ? array_filter($extra, /* по именам полей */) : $extra;

Эта ветка появилась из-за настоящего бага: безусловная фильтрация стирала data при каждом сохранении руками написанной модели — pb_companies терял почту, телефон и адрес на каждом обновлении, потому что сверяться ему не с чем.

Fillable следует за реальной таблицей

Сгенерированные таблицы данных несут только часть базовых колонок. getFillable() пересекает базовый список с колонками, которые у таблицы действительно есть, — схема читается один раз и кэшируется на таблицу.

Без этого пришедший из формы parent_type или context_key дошёл бы до UPDATE и упал с Unknown column.

Мягкое удаление везде

AbstractDataModel и ConstructorModel наследуют SoftModel с трейтом SoftDeletes. Удаление проставляет deleted_at, выборки такие строки не видят. Из этого и восстанавливает корзина.

Окончательное удаление конструктора уносит и его поля, вкладки и колонки: дети привязаны парой model_type + model_id, а не внешним ключом, поэтому на стороне базы каскада нет.

Шаблоны видят extra-атрибуты — благодаря __isset

Fenom компилирует {$obj->prop} в проверку isset(). До __get дело бы не дошло, и каждое поле конструктора выводилось бы пустым. Поэтому AbstractDataModel реализует __isset и сообщает про extra-атрибуты, что они установлены.

Пишете свою модель с магическими свойствами — держите это в голове: баг выглядит как «в PHP значение есть, а в шаблоне пусто».

Морф-классы

Строки привязаны к владельцу парой model_type + model_id. Два семейства пишут model_type по-разному:

Семействоmodel_type
AbstractDataModelполное имя класса, например PageBlocks\App\Models\PbBlockData
ConstructorModelкороткий литерал, например pbBlock, pbTable

Оба написания встречаются в живых данных по историческим причинам, поэтому код, который сопоставляет model_type, обычно вынужден принимать несколько вариантов. Не «упрощайте» BaseModel::getMorphClass() — на нём держится весь конструктор.

© PageBlocks 2019-present