Создаем отношение «один ко многим» в Eloquent

Eloquent – это объектно-реляционный преобразователь (ORM), который включен в структуру Laravel по умолчанию. В этой серии статей вы узнаете, как делать запросы к базе данных и как работать с отношениями в Laravel Eloquent. Также вы сможете попрактиковаться на примерах: мы попробуем улучшить демо-приложение с помощью новых моделей и отношений.

Примечание: Все мануалы данной серии можно найти по тегу Laravel Eloquent.

Демо-приложение Landing Laravel, которое вы настроили в первом мануале этой серии, содержит одну таблицу для хранения ссылок. В этом руководстве мы изменим исходную структуру БД и добавим вторую таблицу, которую мы сможем использовать для организации ссылок в списки.

В примерах, которые мы будем рассматривать в этой серии, каждая ссылка может входить только в один список, но в каждом списке может быть несколько ссылок. Этот вид отношений называется «один ко многим».

Отношение «один ко многим» возникает, когда один элемент типа A может быть связан с несколькими элементами типа B, но при этом обратное неверно: элемент типа B может быть связан только с одним элементом типа A. В контексте текущих моделей нашего демо-приложения A – это список, а B – ссылка.

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

Сначала нам потребуется создать модель и таблицу БД для представления списка ссылок. Затем мы обновим существующую модель Link и таблицу, чтобы включить отношение между обеими моделями. Поскольку понятие List зарезервировано для внутреннего устройства PHP, мы не сможем назвать свою новую модель этим словом. Потому мы назовем нашу новую модель LinkList.

Сначала убедитесь, что вы находитесь в каталоге приложения:

cd ~/landing-laravel

С помощью artisan создайте новую модель:

docker-compose exec app php artisan make:model LinkList

Это создаст новый класс модели в каталоге app/Model:

app/Model/LinkList.php

Переименование существующей команды LinkList

Если вы посмотрите в каталог app/Console/Commands, вы заметите, что там уже есть файл класса с именем LinkList.php. Не следует путать его с только что созданной моделью Eloquent. Этот класс содержит команду CLI, которая через artisan создает список всех ссылок в БД.

Чтобы в будущем избежать путаницы, мы сейчас переименуем этот класс и его сигнатуры команды, присвоим ему другое имя. К примеру, здесь мы будем использовать имя класса LinkShow, поскольку оно также описывает, что делает этот класс. Чтобы переименовать файл app/Console/Commands/LinkList.php и присвоить ему другое допустимое имя, запустите следующую команду в своем терминале:

mv app/Console/Commands/LinkList.php app/Console/Commands/LinkShow.php

Затем откройте файл app/Console/Commands/LinkShow.php в редакторе кода, чтобы изменить имя класса с LinkList на LinkShow и сигнатуру команды с link:list на link:show (обратите внимание на выделенные строки в следующем фрагменте кода). Вот как должен выглядеть файл app/Console/Commands/LinkShow.php после того, как вы закончите:

<?php

namespace App\Console\Commands;

use App\Models\Link;
use Illuminate\Console\Command;

class LinkShow extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'link:show';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'List links saved in the database';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle()
    {
        $headers = [ 'id', 'url', 'description' ];
        $links = Link::all(['id', 'url', 'description'])->toArray();
        $this->table($headers, $links);

        return 0;
    }
}

Сохраните и закройте файл, когда закончите. Чтобы убедиться, что все работает должным образом, запустите новую переименованную команду link:show:

docker-compose exec app php artisan link:show

Вы получите следующий результат:

+----+-------------------------------------------------+----------------------------------+
| id | url                                             | description                      |
+----+-------------------------------------------------+----------------------------------+
| 1  | https://digitalocean.com/community              | DigitalOcean Community           |
| 2  | https://digitalocean.com/community/tags/laravel | Laravel Tutorias at DigitalOcean |
| 3  | https://digitalocean.com/community/tags/php     | PHP Tutorials at DigitalOcean    |
+----+-------------------------------------------------+----------------------------------+

Создание миграции для модели LinkList

Новый класс app/Model/LinkList.php, созданный с помощью команды artisan make:model, содержит общий код для нового класса Eloquent. В отличие от других ORM, таких как Doctrine, Eloquent не изменяет структуры БД, обрабатывая только сами данные. Модели Eloquent обычно экономичны, а свойства классов выводятся из структуры таблицы модели автоматически.

Такой подход Eloquent к обработке данных означает, что вам не нужно настраивать какие-либо свойства для класса LinkList, потому что они будут выведены из структуры таблицы БД для этой модели.

Структурные операции с БД обычно обрабатываются в Laravel посредством миграции БД. Миграции позволяют разработчикам программно определять структурные изменения в БД, в том числе создание, изменение и удаление таблиц.

Читайте также: Создание таблиц Laravel с помощью миграций

Теперь мы создадим новую миграцию, чтобы настроить в базе данных таблицу lists.

Инструмент командной строки artisan, включенный в Laravel по умолчанию, содержит несколько вспомогательных методов для загрузки новых компонентов, таких как контроллеры, модели, миграции и др. Чтобы создать новую миграцию с помощью artisan, запустите:

docker-compose exec app php artisan make:migration create_link_lists_table

Вы получите ответ:

Created Migration: 2021_07_07_152554_create_link_lists_table

Эта команда сгенерирует в каталоге database/migrations приложения Laravel новый файл и присвоит ему имя, автоматически созданное на основе текущей даты, времени и имени миграции. Этот файл содержит общий код, который мы изменим, чтобы настроить таблицу lists.

С помощью редактора кода откройте созданный файл миграции, database/migrations/2021_07_07_152554_create_link_lists_table.php. В настоящее время он выглядит так:

<?php

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

class CreateLinkListsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('link_lists', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

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

Миграция при выполнении с командой artisan migrate запускает метод up(). Здесь находится определение таблицы, и по умолчанию он создает поле первичного ключа id и два поля меток времени (created_at и updated_at), определенные методом схемы timestamps(). Эти поля автоматически заполняются Eloquent при создании и обновлении модели соответственно. Метод down() вызывается, когда миграция откатывается с помощью команды artisan rollback, и обычно выполняет код для удаления таблицы или отмены структурных изменений.

Мы изменим метод up, включив в него следующие поля:

  • title: строка, представляющая заголовок этого списка.
  • description: строка, представляющая описание списка.
  • slug: уникальная короткая строка, основанная на заголовке (обычно используется для создания удобных URL-адресов).

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

Таблица lists, с другой стороны, не нуждается в каком-либо специальном поле для ссылки на links.

Замените текущее содержимое файла миграции database/migrations/2021_07_07_152554_create_link_lists_table.php следующим кодом:

<?php

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

class CreateLinkListsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('link_lists', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
            $table->string('title', 60);
            $table->string('slug', 60)->unique();
            $table->text('description')->nullable();
        });
    }

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

И сохраните свои изменения.

Обновление миграции links

Затем откройте существующий файл миграции links в редакторе кода. В демо-проекте вы найдете миграцию по следующему пути:

2020_11_18_165241_create_links_table.php

Во-первых, включите директиву use, указывающую полное имя класса LinkList (поместите ее в начало файла сразу за последней строкой use):

…
use Illuminate\Support\Facades\Schema;
use App\Models\LinkList;
...

Затем включите в определение таблицы следующую строку (в методе up сразу после строки, которая устанавливает поле description):

$table->text('description');
$table->foreignIdFor(LinkList::class);

Метод foreignIdFor() создает для указанной модели Eloquent столбец внешнего ключа. Он использует стандартную номенклатуру для настройки поля, которое связано с полем первичного ключа указанной таблицы.

Вот как должен выглядеть класс миграции database/migrations/2020_11_18_165241_create_links_table.php, когда вы внесете в него все изменения:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use App\Models\LinkList;

class CreateLinksTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('links', function (Blueprint $table) {
            $table->id();
            $table->string('url', 200);
            $table->text('description');
            $table->foreignIdFor(LinkList::class);
            $table->timestamps();
        });
    }

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

Сохраните файл, когда закончите его редактировать. Затем очистите базу данных и снова запустите команду миграции, чтобы воссоздать структуру БД с учетом обновлений в файлах миграции:

docker-compose exec app php artisan db:wipe
docker-compose exec app php artisan migrate

Настройка отношения Eloquent

Таблицы БД настроены, но нам все еще нужно настроить модели Eloquent, чтобы определить отношения между ними.

В модели List, которая является стороной «один», мы создадим новый метод по имени links. Он будет работать как прокси для доступа к ссылкам, связанным с тем или иным списком. Это происходит через метод hasMany из родительского класса Illuminate\Database\Eloquent\Model.

В редакторе кода откройте файл app/Model/LinkList.php. Замените текущий общий код следующим кодом:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class LinkList extends Model
{
    use HasFactory;

    public function links()
    {
        return $this->hasMany(Link::class);
    }
}

И сохраните файл.
Затем отредактируйте сторону «многие» и включите обратную ссылку на модель List, чтобы ссылки могли получить доступ к соответствующему списку. Это делается с помощью метода belongsTo из родительского класса Model. Этот метод используется для определения обратной стороны отношения «один ко многим».

Откройте модель Link в редакторе кода:

app/Model/Link.php

Замените текущий код в файле Link.php следующим кодом:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Link extends Model
{
    public function link_list()
    {
        return $this->belongsTo(LinkList::class);
    }
}

Сохраните изменения.

Обновив обе модели, мы закончили настройку БД. Она готова к работе, но в настоящее время пуста. В следующем разделе этой серии вы узнаете, как вставить новые записи в базу данных с помощью моделей Eloquent.

Tags: , , ,

Добавить комментарий