Статьи

Что такое полиморфные отношения и как мы используем их с Eloquent?

Эта статья была рецензирована Кристофером Вунди и Кристофером Питтом . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!


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

  • Пользователи просят расценки бюджета для определенной задачи.
  • Каждое задание имеет свое местоположение.
  • Профессионалы могут подписаться на разные зоны.
  • Зона может быть областью или городом.

Теперь давайте пренебрегаем основным приложением и попробуем реализовать этот единственный модуль, чтобы увидеть, чего мы можем достичь здесь.

Laravel-л-наклонная

Применение лесов

Я предполагаю, что ваша среда разработки уже настроена. Если нет, то вы можете проверить этот Советы по улучшению Homestead или просто использовать официальную коробку Homestead .

Создайте новый проект Laravel, используя установщик Laravel или Composer.

laravel new demo

Или

 composer create-project --prefer-dist laravel/laravel demo

Отредактируйте файл .env

 DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead    
DB_PASSWORD=secret

Создание миграций

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

Давайте начнем с создания таблицы zones

 php artisan make:model Zone --migration

Это создает файл миграции, но нам нужно добавить немного кода до того, как он будет завершен, о чем свидетельствуют:

 // database/migrations/2016_12_02_130436_create_zones_table.php

class CreateZonesTable extends Migration
{
    public function up()
    {
        Schema::create('zones', function (Blueprint $table) {
            $table->integer('user_id')->unsigned();

            $table->integer('zone_id')->unsigned();
            $table->string('zone_type');
        });
    }

    public function down()
    {
        Schema::dropIfExists('zones');
    }
}

Далее мы создаем таблицы городов и регионов.

 php artisan make:model Region --migration
 // database/migrations/2016_12_02_130701_create_regions_table.php

class CreateRegionsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('regions', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
        });

    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('regions');
    }
}
 php artisan make:model City --migration
 // database/migrations/2016_12_02_130709_create_cities_table.php

class CreateCitiesTable extends Migration
{
    public function up()
    {
        Schema::create('cities', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name', 255);
            $table->integer('postal_code')->unsigned();

            $table->integer('region_id')->unsigned()->nullable();
        });
    }

    public function down()
    {
        Schema::drop('cities');
    }
}

Мы могли бы достичь того же результата, установив связь «многие ко многим» с таблицей городов и регионов. Однако таблица зон будет действовать как абстрактный класс для двух других таблиц. Идентификатор zone_idzone_type

Создание моделей

Eloquent имеет возможность угадывать связанные поля таблицы, но я решил не использовать его просто для объяснения того, как сопоставить поля базы данных с моделями. Я объясню их по ходу дела!

 // app/User.php

class User extends Authenticatable
{
    // ...

    public function cities()
    {
        return $this->morphedByMany(City::class, 'zone', 'zones', 'user_id', 'zone_id');
    }

    public function regions()
    {
        return $this->morphedByMany(Region::class, 'zone', 'zones', 'user_id', 'zone_id');
    }
}

Метод morphedByManymanyToMany Мы указываем связанную модель, имя поля отображения (используется для zone_typezone_id
Мы могли бы автоматизировать это, позволив Eloquent угадать имена полей, если вы посмотрите на документацию, то увидите, что мы можем назвать поля как zoneable_idzoneable_typereturn $this->morphedByMany(City::class, 'zoneable'

 // app/Region.php

class Region extends Model
{
    // ...

    public function cities()
    {
        return $this->hasMany(City::class);
    }

    public function users()
    {
        return $this->morphMany(User::class, 'zones', 'zone_type', 'zone_id');
    }
}

Мы можем быстро угадать определение параметров из приведенного выше кода. Мы указываем имя связанной таблицы, вид измененного отношения и идентификатор.

 // app/City.php

class City extends Model
{
    // ...

    public function users()
    {
        return $this->morphMany(User::class, 'zones', 'zone_type', 'zone_id');
    }
}

Теперь, когда все настроено, мы можем начать тестировать наши отношения и посмотреть, все ли работает так, как ожидалось. Мы должны сначала посеять таблицы, чтобы сэкономить время. Вы можете получить код получателя базы данных из репозитория GitHub .

Использование отношений

Мы можем присоединять города и регионы к пользователям, используя синхронизацию, присоединение и отключение методов Eloquent. Вот пример:

 $user = App\User::find(1);

$user->cities()->sync(App\City::limit(3)->get());

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

 $user = App\User::find(1);

$user->regions()->sync(App\Region::limit(3)->get());

Красноречивый полиморфизм отношений

Если мы проверим нашу базу данных сейчас, чтобы увидеть, что было сохранено, мы увидим следующее:

 mysql> select * from zones;
+---------+---------+------------+
| user_id | zone_id | zone_type  |
+---------+---------+------------+
|       1 |       1 | App\City   |
|       1 |       2 | App\City   |
|       1 |       3 | App\City   |
|       1 |       1 | App\Region |
|       1 |       2 | App\Region |
|       1 |       3 | App\Region |
+---------+---------+------------+
6 rows in set (0.00 sec)

Мы также можем отделить регионы, если они существуют.

 $user = App\User::find(1);

$user->regions()->detach(App\Region::limit(3)->get());

Теперь мы можем выбирать пользовательские города и регионы, как если бы у нас были нормальные отношения «многие ко многим»:

 $user = App\User::find(1);

dd($user->regions, $user->cities);

Мы могли бы добавить новый метод под названием zones

 class User extends Authenticatable
{
    // ...

    public function zones()
    {
        return $this->regions->pluck("cities")->flatten()->merge($this->cities);
    }
}

Метод pluck позволяет получить коллекцию городов из каждого региона, которые затем объединяются (объединяются все коллекции в один) и объединяются с выбранными пользователем городами. Вы можете прочитать больше о коллекциях в документации, и если вы хотите узнать больше, я рекомендую эту книгу Refactoring to Collections от Adam Wathan.

Вывод

Несмотря на то, что полиморфные отношения редко используются в приложениях, Eloquent позволяет легко работать со связанными таблицами и возвращать правильные данные. Если у вас есть какие-либо вопросы или комментарии о Eloquent или Laravel в целом, вы можете опубликовать их ниже, и я сделаю все возможное, чтобы ответить на них!