Отношение «Один-ко-Многим» в Laravel Eloquent

Отношение «Один-ко-Многим» в Laravel Eloquent

В этой статье мы рассмотрим на конкретном примере, как можно создать отношение «Один-ко-Многим» в Laravel.

Что такое отношение «Один-ко-Многим»?

Оно также известное как hasMany и связывает одну запись в таблице со множеством других записей в других таблицах базы данных. Это наиболее используемый тип отношений.

Например, на коммерческом сайте модель Brand принадлежит несколько записей модели Product, а модель Product принадлежит модели Brand. Эта взаимосвязь изображена на диаграмме ниже
Отношение Один ко Многим

 

Отношение «Один-ко-Многим» очень простое, как и отношение «Один-к-Одному», о которых мы рассказывали в статье «Отношение «Один-к-Одному» в Laravel Eloquent«. Чтобы разобраться с «Один-ко-Многим» и научиться его использовать, давайте рассмотрим очень простой пример.

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

Для реализации отношения нам понадобятся две таблицы в нашей базе данных brands и products. Создадим модель и миграцию, а затем определим «Один-ко-Многим». Для генерации моделей и миграций запустите две команды в терминале

php artisan make:model Brand -m
php artisan make:model Product -m

Выше приведенная команда сгенерирует в папке app две модели под названиями Brand и Product. Вы также найдете две новые миграции в папке database/migrations. Свежесозданные модели и миграция будут выглядеть следующим образом:

// Brand Model
namespace App;

use Illuminate\Database\Eloquent\Model;

class Brand extends Model
{
    //
}

Миграция Brand:

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

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

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

Модель Product:

// Product Model
namespace App;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    //
}

Миграция Product:

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

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

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

После того, как вы сгенерировали модели и миграции, обновите метод up() в обоих миграциях.

Добавляем несколько полей для таблицы бренда.

/**
 * Run the migrations.
 *
 * @return void
 */
public function up()
{
    Schema::create('brands', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->text('description');
        $table->timestamps();
    });
}

И добавляем несколько полей для таблицы продуктов. Обратите внимание на столбец brand_id, являющимся внешним ключом к идентификатору brands.

/**
 * Run the migrations.
 *
 * @return void
 */
public function up()
{
    Schema::create('products', function (Blueprint $table) {
        $table->id();
        $table->unsignedBigInteger('brand_id');
        $table->foreign('brand_id')
          ->references('id')->on('brands')
          ->onDelete('cascade');
        $table->string('name');
        $table->string('slug');
        $table->double('price');
        $table->integer('qty');
        $table->text('description');
        $table->timestamps();
    });
}

Теперь выполните команду ниже, для того чтобы создать таблицы brands и products в вашей базе данных:

php artisan migrate

Определение отношения «Один-ко-Многим»

Теперь у нас есть таблицы брендов и продуктов, давайте определим отношение «Один-ко-Многим» в моделях.

Откройте файл модели app/Brand.php и добавьте новую функцию с именем products(), которая будет возвращать отношение hasMany, как показано ниже:

// Brand Model
namespace App;

use Illuminate\Database\Eloquent\Model;

class Brand extends Model
{
    public function products()
    {
    	return $this->hasMany(Product::class);
    }
}

Примечание. Мы возвращаем отношение «Один-ко-Многим» для модели бренда, которая вернёт более одной записи модели продукта, поэтому мы назвали ее во множественном числе: products. Позже, когда мы определим обратное отношение «Один-ко-Многим» в модели продукта, то будем использовать название в единственном числе: brand. У каждого продукта будет только один бренд, поэтому  имя в единственном числе.

Как видите, в методе products() мы определили отношение hasMany для модели Product .

Определение обратного отношения «Один-ко-Многим»

Поскольку мы определили отношение hasMany для модели Brand, которое вернет связанные записи модели Product, то мы можем определить обратную зависимость для модели Product.

Откройте файл модели app/Product.php и добавьте в него новый метод brand(), возвращающий бренд, который связан с этим продуктом.

После добавления нового метода brand() обновите его с помощью приведенного ниже фрагмента кода.

// Product Model
namespace App;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    public function brand()
    {
    	return $this->belongsTo(Brand::class);
    }
}

Как видите, в это методе мы возвращаем отношение belongsTo, которое вернут соответствующий продукту бренд.

Получение данных «Один-ко-Многим»

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

$brand = Brand::find(1);
$products = $brand->products;

Когда мы запрашиваем продукты для бренда, то Laravel забирает из таблицы продуктов все записи, которые в поле brand_id содержать id запрошенного бренда.

Точно так же мы можем получить обратную связь.

$product = Product::find(1);
$brand = $product->brand;
dd($brand->name);

Для обратного отношения «Один-ко-Многим», Laravel забирает из таблицу брендов запись, у которой поле идентификатора равно столбцу brand_id продукта.

Создание отношения «Один-ко-Многим»

Для того чтобы создать отношения между двумя моделями или связать их, мы можем сначала создать дочерний объект, а затем сохранить его, используя родительский объект.

$brand = Brand::find(1);

$product = new Product();
$product->name = 'iPhone X';
$product->slug = 'iphone-x';
$product->price = '899.99';
$product->qty = 10;
$product->description = 'Some description';

// Сохраняем связанную модель
$brand->products()->save($product);

Вы также можете использовать метод saveMany() вместо save() для сохранения нескольких связанных моделей.

Фильтрация отношения «Один-ко-Многим»

Возможно, вы захотите отфильтровать связанные записи и отсортировать их при получении. Вы можете объединить методы в своем поисковом запросе.

$brand = Brand::find(1);
$products = $brand->products()
      ->where('price', '>', '500')
      ->orderBy('name', 'asc')
      ->get();

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

Удаление отношения «Один-ко-Многим»

Процесс удаления отношения такое же, как и их создание. Сначала мы получаем родительский объект — Бренд, а затем используем метод products() для удаления всех продуктов. Чтобы удалить все продукты для конкретного бренда, используйте приведенный ниже пример:

$brand = Brand::find(1);
$brand->products()->delete();

Заключение

Я старался как мог, делая эту статью насколько возможно простой, так как отношения в Laravel — самая неверно понимаемая тема. Рекомендую вам потратить немного времени на чтение официальной документации Laravel.

Автор: Larashout
Перевод: Алексей Широков

Наш Телеграм-канал — следите за новостями о Laravel.