Наследование моделей в Laravel с помощью Parental

В Laravel обычно каждая модель Eloquent работает со своей таблицей в базе данных. Но что если у вас есть разные типы записей, которые в основном используют одни и те же поля, но отличаются поведением? Для таких случаев есть удобное решение - single table inheritance (STI, наследование моделей в одной таблице), которое реализует пакет Parental. Parental позволяет расширять базовую модель другими классами, при этом все они используют одну и ту же таблицу. Различие между типами моделей определяется значением в специальном столбце.

Зачем это нужно

STI особенно полезно, когда модели:

  • имеют почти одинаковые атрибуты, но разное поведение

  • требуют специфичных методов или связей

  • должны обрабатываться как разные типы сущностей в приложении

Примеры типичных случаев:

  • разные типы пользователей (Admin, Guest, Member), которые расширяют User

  • разные типы контента (TextPost, ImagePost), у которых один и тот же набор полей, но разная логика

  • состояния сущностей (PendingOrder, ShippedOrder, CancelledOrder), которые все хранятся в одной таблице заказов

Подобная организация позволяет избегать множества таблиц и дублирования кода, при этом сохраняя раздельную логику для каждого типа.

Установка и базовая настройка

Чтобы начать пользоваться Parental, установите его через Composer:

composer require tightenenco/parental

Затем определите родительскую модель и дочерние, используя специально предназначенные для этого трейты.

Родительская модель:

use Illuminate\Database\Eloquent\Model;
use Tighten\Parental\HasChildren;

class Order extends Model
{
    use HasChildren;
}

Дочерние модели:

use Tighten\Parental\HasParent;

class PendingOrder extends Order
{
    use HasParent;
}

class ShippedOrder extends Order
{
    use HasParent;
}

При создании экземпляра PendingOrder или ShippedOrder запись сохраняется в таблице orders, а в поле type автоматически записывается имя дочернего класса, чтобы отличать разные типы.

Как задавать собственный столбец для типа

По умолчанию пакет использует столбец type для хранения информации о типе модели. Но если в вашей таблице название другое, это легко изменить.

Просто определите в родительской модели свойство $childColumn:

class Order extends Model
{
    use HasChildren;

    protected $childColumn = 'status';
}

Теперь Parental будет использовать status вместо type.

Человеко-читаемые типы

Вместо длинных имен классов можно использовать короткие псевдонимы для значений в таблице. Для этого в родительской модели задаётся свойство $childTypes:

class Order extends Model
{
    use HasChildren;

    protected $childTypes = [
        'pending' => PendingOrder::class,
        'shipped' => ShippedOrder::class,
    ];
}

В этом случае в базе будут храниться строки pending или shipped, а не полные имена классов. Пакет также поддерживает числовые идентификаторы, если вы предпочитаете хранить числа.

Переключение типа модели

Иногда нужно не только сохранить модель, но и изменить её тип в ходе работы. Например, когда заказ переходит из состояния ожидает оплаты в отправлен. Пакет поддерживает метод become() для такой задачи:

$order = PendingOrder::find(1);

// Переклассифицировать заказ
$order = $order->become(ShippedOrder::class);
$order->save();

Этот вызов создаёт новый экземпляр нужного класса, перенесёт все атрибуты из старого объекта и вернёт его. При этом можно подключать события, например becoming, чтобы реагировать на смену типа.

Работа со связями дочерних моделей

Если у дочерних моделей есть собственные связи Eloquent (например, PendingOrder имеет items, а ShippedOrder - shipments), стандартный load() может вызвать ошибку при запросе всех заказов разного типа одновременно. Parental предлагает специальные методы, которые учитывают типы объектов:

$orders->loadChildren([
    PendingOrder::class => ['items'],
    ShippedOrder::class => ['shipments'],
]);

Аналогично можно загружать связи прямо в запросе:

Order::childrenWith([
    PendingOrder::class => ['items'],
    ShippedOrder::class => ['shipments'],
])->get();

Эти методы работают как с коллекциями, так и с билдерами запросов.

Parental превращает single table inheritance из абстрактной идеи в практичный инструмент для Laravel. Он позволяет:

  • определить разные типы моделей в одной таблице

  • писать чистый и понятный код для каждого типа

  • использовать привычные возможности Eloquent (включая связи) даже при смешанном результате запросов

Если ваши модели имеют много общего, но отличаются поведением то Parental может существенно упростить архитектуру приложения.

Комментарии (0)

Войдите, чтобы оставить комментарий

Похожие статьи

Как ускорить Laravel-приложение: параллельные API-запросы через Http::pool и Http::batch

Статья объясняет, как ускорить Laravel-приложения, выполняя API-запросы параллельно с помощью Http::pool и Http::batch. Приводятся практические примеры, обработка ответов и советы по таймаутам.