Урок 08

Связи между моделями

Зачем нужны связи

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

Один ко многим

Ситуация: один пользователь → много постов.

User:

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

Post:

public function user()
{
    return $this->belongsTo(User::class);
}

Миграция для posts:

$table->foreignId('user_id')->constrained()->cascadeOnDelete();

Использование:

$user = User::find(1);
foreach ($user->posts as $post) {
    echo $post->title;
}

$post = Post::find(1);
echo $post->user->name;

Один к одному

Ситуация: у пользователя один профиль.

public function profile()
{
    return $this->hasOne(Profile::class);
}

Обратная связь — belongsTo.

Многие ко многим

Ситуация: у поста много тегов, у тега много постов.

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

Нужна промежуточная таблица:

Schema::create('post_tag', function (Blueprint $table) {
    $table->foreignId('post_id')->constrained()->cascadeOnDelete();
    $table->foreignId('tag_id')->constrained()->cascadeOnDelete();
    $table->primary(['post_id', 'tag_id']);
});

Операции:

$post->tags()->attach($tagId);
$post->tags()->detach($tagId);
$post->tags()->sync([1, 2, 3]);

Полиморфные связи

Ситуация: комментарий может принадлежать и посту, и видео.

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

Миграция:

$table->morphs('commentable');

Жадная загрузка

Проблема N+1: при обращении к связи внутри цикла выполняется отдельный запрос на каждую запись.

foreach (Post::all() as $post) {
    echo $post->user->name; // запрос на каждый пост
}

Решение — предзагрузка через with:

foreach (Post::with('user')->get() as $post) {
    echo $post->user->name; // один дополнительный запрос
}

Вложенная загрузка:

Post::with('user', 'comments.user')->get();