Урок 10: Отношения в Eloquent. One-to-Many, Many-to-Many, полиморфные связи

В 10-м уроке мы разберём одну из самых важных тем в Laravel, это отношения между моделями в Eloquent. Если вы работали с базами данных, то знаете, без связей между таблицами не обойтись. В Eloquent это реализовано элегантно и интуитивно. Мы рассмотрим четыре типа отношений: One-to-Many, Many-to-Many, получение данных через with()и полиморфные связи. В конце напишу практические задания и примеры кода.

One-to-Many: посты и комментарии

Самый распространённый пример пост в блоге и его комментарии. Один пост может иметь много комментариев, а каждый комментарий принадлежит только одному посту. Давайте смоделируем это.

Шаг 1: Создание моделей и миграций

Создадим модели Post и Comment с миграциями:

bash
php artisan make:model Post -m  
php artisan make:model Comment -m

Миграция для постов (posts):

php
// database/migrations/xxxx_create_posts_table.php  
public function up()  
{  
    Schema::create('posts', function (Blueprint $table) {  
        $table->id();  
        $table->string('title');  
        $table->text('content');  
        $table->timestamps();  
    });  
}

Миграция для комментариев (comments):

php
// database/migrations/xxxx_create_comments_table.php  
public function up()  
{  
    Schema::create('comments', function (Blueprint $table) {  
        $table->id();  
        $table->text('body');  
        $table->foreignId('post_id')->constrained();  
        $table->timestamps();  
    });  
}

Обратите внимание на post_id в комментариях — это внешний ключ, связывающий комментарий с постом.

Шаг 2: Определение отношений в моделях

В модели Post:

php
// app/Models/Post.php  
public function comments()  
{  
    return $this->hasMany(Comment::class);  
}

В модели Comment:

php
// app/Models/Comment.php  
public function post()  
{  
    return $this->belongsTo(Post::class);  
}

Пример использования

php
// Создаём пост  
$post = Post::create([  
    'title' => 'Мой первый пост',  
    'content' => 'Это содержимое поста...'  
]);  

// Добавляем комментарий  
$comment = $post->comments()->create([  
    'body' => 'Отличный пост!'  
]);  

// Получаем все комментарии поста  
$comments = $post->comments;  

// Находим пост комментария  
$post = $comment->post;

Many-to-Many: посты и теги

Теги могут принадлежать многим постам, а посты многим тегам. Для этого нужна промежуточная таблица.

Шаг 1: Создание моделей и миграций

Создадим модель Tag и промежуточную таблицу post_tag:

bash
php artisan make:model Tag -m

Миграция для тегов (tags):

php
public function up()  
{  
    Schema::create('tags', function (Blueprint $table) {  
        $table->id();  
        $table->string('name');  
        $table->timestamps();  
    });  
}

Миграция для связи (post_tag):

bash
php artisan make:migration create_post_tag_table
php
public function up()  
{  
    Schema::create('post_tag', function (Blueprint $table) {  
        $table->foreignId('post_id')->constrained();  
        $table->foreignId('tag_id')->constrained();  
        $table->primary(['post_id', 'tag_id']);  
    });  
}

Шаг 2: Определение отношений

В модели Post:

php
public function tags()  
{  
    return $this->belongsToMany(Tag::class);  
}

В модели Tag:

php
public function posts()  
{  
    return $this->belongsToMany(Post::class);  
}

Пример использования

php
// Привязываем теги к посту  
$post = Post::find(1);  
$post->tags()->attach([1, 2, 3]); // ID тегов  

// Или через синхронизацию  
$post->tags()->sync([1, 3]);  

// Получаем все теги поста  
$tags = $post->tags;  

// Все посты с определённым тегом  
$tag = Tag::find(1);  
$posts = $tag->posts;

Получение связанных данных через with()

Проблема N+1 запроса возникает, когда мы получаем данные в цикле. Например:

php
$posts = Post::all();  
foreach ($posts as $post) {  
    echo $post->comments->count(); // Каждый вызов — отдельный запрос!  
}

Решение: жадная загрузка через with():

php
$posts = Post::with('comments')->get();  
foreach ($posts as $post) {  
    echo $post->comments->count(); // Все комментарии загружены одним запросом!  
}

Загрузка вложенных отношений

Если нужно загрузить отношения внутри отношений:

php
$posts = Post::with('comments.user')->get();

Полиморфные отношения (лайки)

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

Шаг 1: Создание моделей и миграций

Модель Like:

bash
php artisan make:model Like -m

Миграция для лайков:

php
public function up()  
{  
    Schema::create('likes', function (Blueprint $table) {  
        $table->id();  
        $table->foreignId('user_id')->constrained();  
        $table->morphs('likeable'); // Создаёт likeable_id и likeable_type  
        $table->timestamps();  
    });  
}

Поле likeable_type хранит имя модели (например, App\Models\Post), а likeable_id — её ID.

Шаг 2: Определение отношений

В модели Like:

php
public function likeable()  
{  
    return $this->morphTo();  
}

В моделях Post и Comment:

php
// app/Models/Post.php  
public function likes()  
{  
    return $this->morphMany(Like::class, 'likeable');  
}  

// app/Models/Comment.php  
public function likes()  
{  
    return $this->morphMany(Like::class, 'likeable');  
}

Пример использования

php
// Пользователь ставит лайк посту  
$post = Post::find(1);  
$like = $post->likes()->create([  
    'user_id' => auth()->id()  
]);  

// Получаем все лайки комментария  
$comment = Comment::find(1);  
$likes = $comment->likes;  

// Получаем объект, к которому привязан лайк  
$like = Like::find(1);  
$likeable = $like->likeable; // Может быть Post, Comment и т.д.  

Практические задания

  1. One-to-Many:
    • Создайте 3 поста и по 2 комментария к каждому.
    • Напишите запрос, который получает все комментарии поста с ID = 3.
  2. Many-to-Many:
    • Создайте теги «PHP», «Laravel», «Web».
    • Привяжите к первому посту теги «PHP» и «Laravel».
    • Выведите все посты с тегом «Web».
  3. with():
    • Получите все посты с комментариями и авторами комментариев за один запрос.
  4. Полиморфные отношения:
    • Реализуйте возможность ставить лайки комментариям.
    • Посчитайте общее количество лайков для поста с ID = 5.

Не забывайте про полный курс по Laravel для начинающих,  там ещё много интересного.

Увидимся в следующем уроке.