Parental — пакет для Laravel від Tighten, який додає Single Table Inheritance (STI) в Eloquent. Замість створення окремих таблиць для кожної варіації моделі, Parental дозволяє розширювати батьківську модель дочірніми класами, що використовують одну таблицю, де їх розрізняє колонка type.
# Сценарії використання
Single Table Inheritance корисний, коли моделі мають більшість спільних атрибутів, але відрізняються поведінкою. Типові приклади:
- User types —
AdminіGuest, що розширюють базовийUser, кожен із власними методами та відношеннями - Content types —
TextPostіImagePost, які розширюютьPost, з типовими для них відношеннями, наприклад mentions або attachments - Order states —
PendingOrderіShippedOrder, що розширюютьOrder; при зміні стану замовлення змінюється й тип моделі
# Початок роботи
Встановіть пакет через Composer:
composer require tightenco/parental
Далі опишіть батьківську й дочірні моделі: додайте трейд HasChildren до батьківської моделі та HasParent до кожної дочірньої. Кожен дочірній клас наслідує батьківський і читає/записує дані в ту саму таблицю:
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;
}
use Tighten\Parental\HasParent;
class ShippedOrder extends Order
{
use HasParent;
}
Коли ви створюєте PendingOrder або ShippedOrder, запис зберігається в таблиці orders з колонкою type, яка ідентифікує дочірній клас.
# Власна колонка типу
Якщо в таблиці використовується інша назва колонки замість type, перевизначте її через властивість $childColumn у батьківській моделі:
class Order extends Model
{
use HasChildren;
protected $childColumn = 'status';
}
# Псевдоніми типів
За замовчуванням Parental зберігає повне ім'я класу в колонці type. Ви можете задати короткі псевдоніми через властивість $childTypes у батьківській моделі:
use Illuminate\Database\Eloquent\Model;
use Tighten\Parental\HasChildren;
class Order extends Model
{
use HasChildren;
protected $childTypes = [
'pending' => PendingOrder::class,
'shipped' => ShippedOrder::class,
];
}
У базі зберігатиметься pending або shipped замість повного імені класу. Колонка типу також підтримує цілі числа, якщо ви віддаєте перевагу числовим ідентифікаторам:
protected $childTypes = [
1 => PendingOrder::class,
2 => ShippedOrder::class,
];
# Перекласифікація через become()
Метод become() дозволяє динамічно змінити тип моделі на інший під час виконання. Наприклад, коли замовлення проходить життєвий цикл, його можна перекласифікувати в інший клас:
$order = PendingOrder::findOrFail(1);
$order = $order->become(ShippedOrder::class);
$order->save();
Виклик повертає новий екземпляр цільового класу з усіма початковими атрибутами. Parental також відправляє подію becoming перед переключенням — на неї можна підписатися, щоб реагувати на зміну типу й виконувати додаткову логіку.
# Попереднє завантаження відношень дочірніх класів
Коли запит повертає змішаний набір дочірніх типів, іноді потрібно підвантажити відношення, що існують лише в окремих класах. Виклик звичайного load() для відношення, визначеного лише в одному дочірньому класі, спричинить помилку. Parental має спеціальні хелпери, які звужують eager load до потрібного дочірнього класу:
$orders->loadChildren([
PendingOrder::class => ['items'],
ShippedOrder::class => ['shipments'],
]);
Ці хелпери працюють на Eloquent query builders, collections та paginators:
// On queries
Order::childrenWith([
PendingOrder::class => ['items'],
ShippedOrder::class => ['shipments'],
])->get();
// Count child-specific relationships
Order::childrenWithCount([
PendingOrder::class => ['items'],
ShippedOrder::class => ['shipments'],
])->get();
Щоб дізнатися більше про Parental і переглянути вихідний код, відвідайте GitHub repository.