В этом уроке я помогу вам понять концепцию полиморфных отношений в Laravel. Обещаю, это будет весело, так как будем рассматривать их на примере пиццы.
Основые типы Отношений
Прежде чем рассмотрим Полиморфизм, давайте быстренько пробежимся по трем основным типам отношений:
- Один к одному (One-to-One)
- Один ко многим (One-to-Many)
- Многие-ко-многим (Many-to-Many)
Один к одному
Эти отношения связывают одну вещь с другой. Одна пицца имеет один тип топпинга (начинки) и наоборот — один топпинг может быть у одной пиццы.

Базовая структура таблиц, для отношения Один-к-Одному будет такой:
pizzas
id - integer
toppings
id - integer
name - string
pizza_id - integer
Таблица toppings имеет уникальный внешний ключ pizza_id, который ссылается на id таблицы pizzas, создавая отношения Один-к-Одному.
Конечно, пицца с одним топпингом это смешно. В большинстве случаев в пицце их будет много. Это подводит нас к следующим отношениям.
Один ко многим
Эти отношения связывают одну вещь со многими другими. Согласно нашему сценарию, одна пицца может иметь много топпингов, и, наоборот, множество топпингов могут быть в одной пицце.

Таблицы БД для этих отношений:
pizzas
id - integer
toppings
id - integer
name - string
pizza_id - integer
Похоже на таблицы предыдущих отношений? Так и есть!
Эти отношения имеют такую же структуру, но есть небольшое отличие. В отношениях Один-к-Одному внешний ключ pizza_id уникален (unique). В отношениях Один-ко-Многим» внешний ключ не уникален, что позволяет пицце иметь много топпингов.
А что, если бы мы хотим много пицц со множеством топпингов? Лучший способ — это отношения Многие-ко-Многим» .
Многие ко многим
В этих отношениях мы используем сводную (промежуточную) таблицу для того, чтобы определить отношения между пиццей и ее топпингами.

Структуру таблицы отношений:
pizzas
id - integer
pizza_toppings
pizza_id - integer
topping_id - integer
toppings
id - integer
name - string
Сводная таблица pizza_toppings имеет два внешних ключа, которые ссылаются на таблицы pizzas и toppings. Эта таблица и создает отношения между многими пиццами и многими топпингами.
Это три наиболее распространенных типа отношений.
Теперь, когда мы с этим разобрались, то перейдем к полиморфным отношениям.
Полиморфизм
Что если нужно добавить в наш сценарий новый продукт? Например, горячие сандвичи! И чтобы они использовали те же топпинги, что и пицца, то как это оформить в нашей базе данных?
Вот тогда мы и призываем на помощь Полиморфизм!
У Полиморфных Отношений есть сводная таблица, похожая на таблицу отношений «Многие-ко-Многим», но, она имеет дополнительное поле, определяющее тип продукта питания.

Структура таблиц для полиморфных отношений:
pizzas
id - integer
sandwiches
id - integer
toppings
id - integer
name - string
toppables
topping_id - integer
toppable_id - integer
toppable_type - string
В этих отношениях продукт питания (пицца или сандвич) не указывается напрямую, в то время как в отношениях «Многие-ко-Многим» в таблице pizza_toppings прямо указана пицца.
В полиморфных отношениях наша еда может быть «морфирована» (преобразована) в разные типы (пицца, сандвичи и т.д.). Теперь любой новый продукт, который мы добавляем в меню, может быть toppable, то есть иметь много топпингов.
Важно помнить, что таблицы в полиморфных отношениях могут быть «морфированными» или динамическими.
Как работает полиморфизм
Полиморфные отношения работают с использованием сводной таблицы с дополнительным полем, определяющим, на какую таблицу должен ссылаться внешний ключ.
В неполиморфных отношениях внешние ключи ссылаются на primary ID в конкретной таблице. С другой стороны, внешний ключ в полиморфной сводной таблице может ссылаться на множество таблиц.
Пример полиморфизма
Полиморфные отношения доступны в любом языке, использующем реляционную базу данных. Я покажу вам несколько примеров кода для Laravel с использованием этих таблиц:
pizzas
id - integer
sandwiches
id - integer
toppings
id - integer
name - string
toppables
topping_id - integer
toppable_id - integer
toppable_type - string
Во-первых, мы создадим новую Eloquent-модель Pizza с отношением toppings():
namespace App;
use Illuminate\Database\Eloquent\Model;
class Pizza extends Model
{
/**
* Получаем все топпинги для этой пиццы
*/
public function toppings()
{
return $this->morphToMany('App\Topping', 'toppable');
}
}
Чтобы получить топпинги для пиццы, напишем следующий код:
$pizza = App\Pizza::find(1);
foreach ($pizza->toppings as $topping) {
//
}
Круто!
Далее, мы также можем получить обратные отношения для модели Topping:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Topping extends Model
{
/**
* Получаем все пиццы, имеющие конкретный топпинг
*/
public function pizzas()
{
return $this->morphedByMany('App\Pizza', 'toppable');
}
/**
* Получаем все сандвичи, имеющие конкретный топпинг
*/
public function sandwiches()
{
return $this->morphedByMany('App\Sandwich', 'toppable');
}
}
И тогда мы могли бы попросить все пиццы и сандвичи с определенным топпингом:
$topping = App\Topping::where('name', 'onions')->first();
// Получить все пиццы c луком, в качестве топпинга
foreach ($topping->pizzas as $pizza) {
//
}
// Получить все сендвичи c луком, в качестве топпинга
foreach ($topping->sandwiches as $sandwich) {
//
}
Добавление полиморфных связей может сделать ваше приложение более эффективным, гибким и простым в программировании.
Заглянем глубже
Полиморфные отношения также можно разделить на три категории, которые мы рассмотрели ранее. Они могут быть «Один-к-Одному», «Один-ко-Многим» и «Многие-ко-Многим».
Пример отношения, который мы рассмотрели в этом уроке, это Полиморфные Отношения «Многие-ко-Многим».
Если вы хотите узнать больше о полиморфных отношениях, то обязательно ознакомьтесь с документацией Laravel:
- Полиморфные отношения «Один-к-Одному»
- Полиморфные отношения «Один-ко-Многим»
- Полиморфные отношения «Многие-ко-Многим»
Вывод
Надеюсь, этот урок помог вам разобраться с полиморфными отношениями.
Лучший способ изучить какую-либо концепцию — просто погрузиться в неё и начать применять. После внедрения полиморфизма в несколько проектов вы начнете понимать все больше и больше. В конце концов, концепция использования «Полиморфных отношений» будет похожа на многослойную пиццу ;)
Автор: Tony Lea
Перевод: Алексей Широков
Наш Телеграм-канал — следите за новостями о Laravel.
