Полиморфные отношения

В этом уроке я помогу вам понять концепцию полиморфных отношений в Laravel. Обещаю, это будет весело, так как будем рассматривать их на примере пиццы.

Основые типы Отношений

Прежде чем рассмотрим Полиморфизм, давайте быстренько пробежимся по трем основным типам отношений:

  • Один к одному (One-to-One)
  • Один ко многим (One-to-Many)
  • Многие-ко-многим (Many-to-Many)

Один к одному

Эти отношения связывают одну вещь с другой. Одна пицца имеет один тип топпинга (начинки) и наоборот — один топпинг может быть у одной пиццы.

Отношения Один к одному (One-to-One)

Базовая структура таблиц, для отношения Один-к-Одному будет такой:

pizzas
    id - integer

toppings
    id - integer
    name - string
    pizza_id - integer

Таблица toppings имеет уникальный внешний ключ pizza_id, который ссылается на id таблицы pizzas, создавая отношения Один-к-Одному.

Конечно, пицца с одним топпингом это смешно. В большинстве случаев в пицце их будет много. Это подводит нас к следующим отношениям.

Один ко многим

Эти отношения связывают одну вещь со многими другими. Согласно нашему сценарию, одна пицца может иметь много топпингов, и, наоборот, множество топпингов могут быть в одной пицце.

Отношения Один ко многим (One-to-Many)

Таблицы БД для этих отношений:

pizzas
    id - integer

toppings
    id - integer
    name - string
    pizza_id - integer

Похоже на таблицы предыдущих отношений? Так и есть!

Эти отношения имеют такую же структуру, но есть небольшое отличие. В отношениях Один-к-Одному внешний ключ pizza_id уникален (unique). В отношениях Один-ко-Многим» внешний ключ не уникален, что позволяет пицце иметь много топпингов.

А что, если бы мы хотим много пицц со множеством топпингов? Лучший способ — это отношения Многие-ко-Многим» .

Многие ко многим

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

Отношения Многие-ко-многим (Many-to-Many)

Структуру таблицы отношений:

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.


Задать вопросы по урокам можно на нашем форуме.