Skip to content

Instantly share code, notes, and snippets.

@uniconstructor
Last active October 2, 2018 19:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save uniconstructor/f3a94163ddd3952f2dcd3c2881df9770 to your computer and use it in GitHub Desktop.
Save uniconstructor/f3a94163ddd3952f2dcd3c2881df9770 to your computer and use it in GitHub Desktop.
Laravel 5 snippets

Полиморфные связи

many-to-many

Пример: партнер может оказывать определенный набор услуг. Рабочее место в точке продаж может оказывать не все услуги партнера. Оказание некоторых услуг партнером или рабочим местом может быть приостановлено (для этого используется поле status в pivot-таблице). Нужно иметь возможность прикрепить услуги и к партнеру и к рабочему месту.

Модель партнера:

  • Если на эту модель ссылается полиморфная связь (instance_type + instance_id) экземпляра услуги (таблица service_instances) значит внутри модели должна быть связь morphToMany()
<?php
class Partner extends Model
{
    /**
     * Все услуги предоставляемые партнером
     *
     * @return HasMany|MorphToMany|BelongsToMany|Service[]|Builder
     */
    public function services()
    {
        return $this->morphToMany(
            // экземпляры чего привязываем к партнеру (услуги)
            Service::class, 
            // какой префикс полиморфной связи? (будем искать пару полей related_type + related_id)
            'related', 
            // как называется таблица с полиморфными связями? (здесь "service_instances" - экземпляры услуг)
            // мы привязываем ссылки на услуги к партнеру а не ссылки на пактнера к услугам
            'service_instances', 
            // как называется поле содержащее id этой модели (в данном случае где искать id партнера)
            'related_id', 
            // как называется поле ссылающееся на привязываемый объект? (в данном случае где искать id услуги)
            'service_id'
            )
            // дополнительные колонки, относящиеся только к связи между партнером и услугой: 
            // в этом примере статус - это статус такой связи, он может использоваться для того чтобы
            // показать что партнер в принципе может оказывать эту услугу, но сейчас она у него временно недоступна
            // при этом статус самого партнера не меняется, статус услуги тоже
            ->withPivot('status')
            // условие накладываемое на значение в дополнительных колонках 
            // в этом примере - только записи в у которых статус связи "active" попадут в реляционную связь
            // (также можно использовать ->wherePivot("column", "=", "value") который работает как обычный where)
            ->wherePivotIn('status', ['active'])
            // timestamp-поля нужны для того чтобы можно было определить когда какой объект был прикреплен или когда сменен статус связи между объектами
            ->withTimestamps();
    }
}

Модель рабочего места:

  • Если на эту модель ссылается полиморфная связь (instance_type + instance_id) экземпляра услуги (таблица service_instances) значит внутри модели должна быть связь morphToMany()
<?php
class Workspace extends Model
{
    /**
     * Все услуги предоставляемые партнером
     *
     * @return HasMany|MorphToMany|BelongsToMany|Service[]|Builder
     */
    public function services()
    {
        return $this->morphToMany(Service::class, 'related', 'service_instances', 'related_id', 'service_id')
                    ->withPivot('status')
                    ->withTimestamps();
    }
}

Модель услуги:

  • Если для этой модели есть таблица "экземпляров" (в этом примере service_instances) значит внутри модели должна быть связь morphedByMany()
<?php
class Service extends Model
{
    /**
     * Все партнеры которые могут оказывать эту услугу
     *
     * @return MorphToMany|BelongsToMany|Partner[]
     */
    public function partners()
    {
        return $this->morphedByMany(Partner::class, 'related', 'service_instances', 'service_id')
                    ->withPivot('status')
                    ->withTimestamps();
    }
    
    /**
     * Все рабочие места которые могут оказывать эту услугу
     *
     * @return MorphToMany|BelongsToMany|Workspace[]
     */
    public function workspaces()
    {
        return $this->morphedByMany(Workspace::class, 'related', 'service_instances', 'service_id')
                    ->withPivot('status')
                    ->withTimestamps();
    }
}

Структура таблицы service_instances:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateServiceInstancesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('service_instances', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('service_id')->unsigned()->index();
            // возможность прикреплять услуги к разным типам объектов
            // создаст колонки related_type и related_id, проиндексирует их, запретит null-значения
            $table->morphs('related');
            // pivot-колонки
            $table->string('status')->nullable()->index();
            // timestamp-поля нужны для того чтобы можно было определить когда какой объект был прикреплен или когда сменен статус связи между объектами
            $table->timestamps();
            // индексты для timestamp-полей автоматически сами не создаются а вот искать по ним приходится, поэтому добавляем их вручную
            $table->index('created_at');
            $table->index('updated_at');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('service_instances');
    }
}

Выводы

Реляционные связи в Laravel это, по сути запросы на выборку моделей. Интересный факт: в ядре Laravel объект Relation наследуется от объекта Query, поэтому любые запросы которые вы можете создать при помощи QueryBuilder можно также сделать реляционной связью вашей модели. Используйте это чтобы упростить себе задачу построения приложения.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment