Урок 07

Связи: многие ко многим

Ситуация

У поста много тегов, у тега много постов. Нужна промежуточная таблица.

Модели

Post:

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

Tag:

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

Миграция

Имя таблицы: post_tag (алфавитный порядок, единственное число).

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()->attach([1, 2, 3]);

Отвязать

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

Синхронизировать

Оставить только указанные ID, остальные отвязать:

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

Без отвязки

Добавить новые, не трогая существующие:

$post->tags()->syncWithoutDetaching([4, 5]);

Переключить

Если есть — отвязать, если нет — привязать:

$post->tags()->toggle([1, 2, 3]);

Данные в промежуточной таблице

Если в post_tag есть дополнительные колонки (например, created_at):

return $this->belongsToMany(Tag::class)
    ->withPivot('created_at')
    ->withTimestamps();

Доступ к данным:

foreach ($post->tags as $tag) {
    echo $tag->pivot->created_at;
}

Фильтрация по промежуточной таблице

return $this->belongsToMany(Tag::class)
    ->wherePivot('approved', 1);

Переименование pivot

return $this->belongsToMany(Tag::class)
    ->as('subscription');

Теперь $tag->subscription вместо $tag->pivot.