#09 Laravel基礎

多対多リレーションとポリモーフィック

多対多リレーションとは

「投稿はタグを複数持てる」「タグは複数の投稿に付けられる」という関係が多対多です。これには中間テーブル(ピボットテーブル)が必要です。


中間テーブルのマイグレーション

<?php
// 📁 database/migrations/xxxx_create_post_tag_table.php
// ← テーブル名はアルファベット順で単数形を_で結ぶ慣習

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']); // 複合主キー
});

belongsToMany の定義

<?php
// 📁 app/Models/Post.php

class Post extends Model
{
    public function tags()
    {
        return $this->belongsToMany(Tag::class);
        // デフォルトで post_tag テーブルを使用する
    }
}
<?php
// 📁 app/Models/Tag.php

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

データの操作

関連づける(attach)

$post = Post::find(1);

// タグID 1, 2, 3 を関連づける
$post->tags()->attach([1, 2, 3]);

関連を削除する(detach)

// タグID 2 の関連を削除
$post->tags()->detach(2);

// すべての関連を削除
$post->tags()->detach();

差分を同期する(sync)

フォームのチェックボックスなど「送られてきたIDの集合が最終状態」の場合に使います。

// 送られてきたタグIDに同期(不要なものを消し、新しいものを追加)
$post->tags()->sync($request->input('tag_ids', []));

中間テーブルに追加カラムを持つ場合

「いつ関連づけたか」などを中間テーブルに持たせる場合は withPivot() を使います。

// 📁 app/Models/Post.php

public function tags()
{
    return $this->belongsToMany(Tag::class)
                ->withPivot('note')      // 中間テーブルの追加カラム
                ->withTimestamps();      // created_at, updated_at を自動管理
}

アクセス方法:

foreach ($post->tags as $tag) {
    echo $tag->pivot->note;  // 中間テーブルの値
}

ポリモーフィックリレーション

「コメントを投稿にもビデオにも付けたい」という場合にポリモーフィックリレーションが使えます。同じ comments テーブルを複数のモデルで共有します。

マイグレーション

<?php
// 📁 database/migrations/xxxx_create_comments_table.php

Schema::create('comments', function (Blueprint $table) {
    $table->id();
    $table->text('body');
    $table->morphs('commentable');
    // → commentable_id(INT)と commentable_type(VARCHAR)が作られる
    $table->foreignId('user_id')->constrained();
    $table->timestamps();
});

モデルの定義

<?php
// 📁 app/Models/Comment.php

class Comment extends Model
{
    public function commentable()
    {
        return $this->morphTo(); // 動的に親モデルを返す
    }
}
<?php
// 📁 app/Models/Post.php

class Post extends Model
{
    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}
<?php
// 📁 app/Models/Video.php

class Video extends Model
{
    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

使い方

// 投稿にコメントを追加
$post->comments()->create(['body' => 'よい記事です', 'user_id' => 1]);

// ビデオにコメントを追加
$video->comments()->create(['body' => '参考になりました', 'user_id' => 2]);

// コメントから親を取得
$comment = Comment::find(1);
$parent = $comment->commentable; // Post または Video が返る

まとめ

  • belongsToMany() で多対多リレーションを定義する
  • 中間テーブルのマイグレーションはアルファベット順で post_tag のように命名する
  • attach(), detach(), sync() で中間テーブルのデータを操作する
  • ポリモーフィックは morphs('commentable') + morphTo() / morphMany() の組み合わせ

次回はソフトデリートとオブザーバーを学びます。