В 10-м уроке мы разберём одну из самых важных тем в Laravel, это отношения между моделями в Eloquent. Если вы работали с базами данных, то знаете, без связей между таблицами не обойтись. В Eloquent это реализовано элегантно и интуитивно. Мы рассмотрим четыре типа отношений: One-to-Many, Many-to-Many, получение данных через with()
и полиморфные связи. В конце напишу практические задания и примеры кода.
One-to-Many: посты и комментарии
Самый распространённый пример пост в блоге и его комментарии. Один пост может иметь много комментариев, а каждый комментарий принадлежит только одному посту. Давайте смоделируем это.
Шаг 1: Создание моделей и миграций
Создадим модели Post
и Comment
с миграциями:
php artisan make:model Post -m php artisan make:model Comment -m
Миграция для постов (posts
):
// 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
):
// 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:
// app/Models/Post.php public function comments() { return $this->hasMany(Comment::class); }
В модели Comment:
// app/Models/Comment.php public function post() { return $this->belongsTo(Post::class); }
Пример использования
// Создаём пост $post = Post::create([ 'title' => 'Мой первый пост', 'content' => 'Это содержимое поста...' ]); // Добавляем комментарий $comment = $post->comments()->create([ 'body' => 'Отличный пост!' ]); // Получаем все комментарии поста $comments = $post->comments; // Находим пост комментария $post = $comment->post;
Many-to-Many: посты и теги
Теги могут принадлежать многим постам, а посты многим тегам. Для этого нужна промежуточная таблица.
Шаг 1: Создание моделей и миграций
Создадим модель Tag
и промежуточную таблицу post_tag
:
php artisan make:model Tag -m
Миграция для тегов (tags
):
public function up() { Schema::create('tags', function (Blueprint $table) { $table->id(); $table->string('name'); $table->timestamps(); }); }
Миграция для связи (post_tag
):
php artisan make:migration create_post_tag_table
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:
public function tags() { return $this->belongsToMany(Tag::class); }
В модели Tag:
public function posts() { return $this->belongsToMany(Post::class); }
Пример использования
// Привязываем теги к посту $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 запроса возникает, когда мы получаем данные в цикле. Например:
$posts = Post::all(); foreach ($posts as $post) { echo $post->comments->count(); // Каждый вызов — отдельный запрос! }
Решение: жадная загрузка через with()
:
$posts = Post::with('comments')->get(); foreach ($posts as $post) { echo $post->comments->count(); // Все комментарии загружены одним запросом! }
Загрузка вложенных отношений
Если нужно загрузить отношения внутри отношений:
$posts = Post::with('comments.user')->get();
Полиморфные отношения (лайки)
Полиморфные связи позволяют одной модели принадлежать разным моделям. Пример: лайки для постов, комментариев и видео.
Шаг 1: Создание моделей и миграций
Модель Like
:
php artisan make:model Like -m
Миграция для лайков:
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:
public function likeable() { return $this->morphTo(); }
В моделях Post и Comment:
// 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'); }
Пример использования
// Пользователь ставит лайк посту $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 и т.д.
Практические задания
- One-to-Many:
- Создайте 3 поста и по 2 комментария к каждому.
- Напишите запрос, который получает все комментарии поста с ID = 3.
- Many-to-Many:
- Создайте теги «PHP», «Laravel», «Web».
- Привяжите к первому посту теги «PHP» и «Laravel».
- Выведите все посты с тегом «Web».
- with():
- Получите все посты с комментариями и авторами комментариев за один запрос.
- Полиморфные отношения:
- Реализуйте возможность ставить лайки комментариям.
- Посчитайте общее количество лайков для поста с ID = 5.
Не забывайте про полный курс по Laravel для начинающих, там ещё много интересного.
Увидимся в следующем уроке.