Laravel 12では、Eloquent ORMを使ったリレーションの定義がより洗練され、初心者でも扱いやすくなっています。特に複雑になりがちな多対多リレーションは、アプリケーションの権限管理や商品分類など、実務で頻繁に登場する重要な概念です。本記事は、これからLaravelを学ぶ方や経験の浅い若手エンジニアを対象に、多対多リレーションの基本的な仕組みから、Eloquentモデルでの具体的な定義方法、さらに実用的なデータの操作方法までを、簡潔なコード例と共に徹底解説します。この解説を通じて、多対多リレーションをマスターし、Laravelプロジェクトでのデータベース操作に自信を持てるようになることを目指します。
多対多リレーションとは?
多対多(Many to Many)リレーションは、データベース設計において、二つのエンティティ(モデル)が相互に複数の関連を持つ関係を表します。これは、アプリケーションの機能を実現する上で非常に重要かつ頻繁に利用される関係性です。
たとえば、ブログシステムにおける「投稿(Post)」と「カテゴリ(Category)」の関係を考えてみましょう。
- 一つの投稿には、「ニュース」「技術」「雑記」など複数のカテゴリを付けられます。
- 一つのカテゴリ(例: 「技術」)には、複数の投稿が含まれます。
このように、双方向で「多」の関係が成立するのが多対多リレーションの基本的な概念です。この関係は、直接二つのテーブルを紐づけるのではなく、必ず中間テーブル(ピボットテーブル)を介して定義されます。Laravel 12のEloquentは、この中間テーブルの管理を裏側で担ってくれるため、開発者は直感的にデータを扱えます。

【初心者歓迎!一歩先のLaravel】リレーション入門「ユーザーと投稿」で「…
【初心者歓迎!一歩先のLaravel】リレーション入門「ユーザーと投稿」で「…
Laravel初心者必見! CRUDの次へ進むための「1対多リレーション」を徹底解説。ユーザーと投稿の紐付けをSQL不要で実現する方法、hasManyとbelongsToの使い方、アプリが遅くなるN+1問題の解決策まで、図解とコードで分かり…
Laravel初心者必見! CRUDの次へ進むための「1対多リレーション」を徹底解説。ユーザーと投稿の紐付けをSQL不要で実現する方法、hasManyとbelongsToの使い方、アプリが遅くなるN+1問題の解決策まで、図解とコードで分かり…
中間テーブル(ピボットテーブル)の構造と命名規則
多対多リレーションの鍵となるのが中間テーブル、別名ピボットテーブルです。このテーブルは、リレーションを構成する二つのモデルのレコード同士を結びつける役割を果たします。
LaravelのEloquentでは、この中間テーブルについて強力な命名規約を定めており、それに従うことでモデル側のリレーション定義をよりシンプルにできます。
標準的な命名規則
中間テーブルの名前は、関連する二つのモデルの単数形をアルファベット順に並べ、アンダースコア(_
)で結合するのが標準的な命名規則です。
- 例1: UserとRoleの場合 →
role_user
- 例2: ProductとTagの場合 →
product_tag
この命名規約に従うことで、リレーション定義時に中間テーブル名を省略でき、コードの可読性が向上します。
中間テーブルの必須カラム
中間テーブルが最低限持つべきカラムは、関連する二つのテーブルの外部キーです。
products_tags (例: 商品とタグの中間テーブル) − product_id (productsテーブルへの外部キー) − tag_id (tagsテーブルへの外部キー)
さらに、実務ではタイムスタンプや追加情報を持たせることが一般的です。
role_user (例: ユーザーと役割の中間テーブル) − id (中間テーブルの主キー − 必須ではないが推奨) − user_id − role_id − created_at (いつ関連付けられたか) − updated_at (いつ関連付けが更新されたか) − expired_at (役割の有効期限など、リレーション特有のデータ)
これらの追加のカラムのデータをEloquentで扱う方法については、後ほどwithPivot()
を使った解説で詳しく紹介します。
Eloquentモデルでの多対多リレーションの定義
多対多リレーションをLaravelのEloquentで機能させるには、関連する両方のモデルにリレーションメソッドを定義する必要があります。このとき利用するのが、多対多専用のメソッドであるbelongsToMany
です。
このメソッドは、指定されたモデルと中間テーブルを介して接続し、関連する複数のレコードを取得できるようにします。
UserとRoleモデルでの定義実例
「ユーザー」と「役割」の関係を例に、それぞれのモデルでどのように定義するかを見てみましょう。この定義により、あるユーザーから紐づく役割を、あるいはある役割から紐づくユーザーを簡単に取得できるようになります。
Userモデル (app/Models/User.php)
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; // 型ヒントに推奨 class User extends Model { /** * このユーザーが持つ役割(Role)を取得 */ public function roles(): BelongsToMany { // 引数には関連付けたいモデルクラスを指定するだけ return $this−>belongsToMany(Role::class); } }
Roleモデル (app/Models/Role.php)
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; // 型ヒントに推奨 class Role extends Model { /** * この役割(Role)に割り当てられたユーザー(User)を取得 */ public function users(): BelongsToMany { // 役割側からもUserモデルへ belongsToMany を定義 return $this−>belongsToMany(User::class); } }
このように、両モデルで相互にbelongsToMany
を定義するだけで、Laravelが自動的に中間テーブル(この例ではrole_user
)を探索し、多対多リレーションとして機能させます。

【初心者必見】LaravelのEloquent入門!たった10分でデータベー…
【初心者必見】LaravelのEloquent入門!たった10分でデータベー…
Laravel初心者向けEloquent入門ガイド!SQLが書けなくても、Laravel Eloquentが魔法のようにデータベースを操作する基本を徹底解説。
Laravel初心者向けEloquent入門ガイド!SQLが書けなくても、Laravel Eloquentが魔法のようにデータベースを操作する基本を徹底解説。
多対多リレーションのためのマイグレーションの作成
多対多リレーションを実際にデータベースで機能させるには、二つの主要テーブル(例: users
, roles
)に加え、それらを結びつける中間テーブルが必要です。Laravelのマイグレーション機能を使って、この中間テーブルを定義しましょう。
中間テーブルのマイグレーションファイルの作成
コマンドラインから以下の Artisan コマンドを実行し、中間テーブルのマイグレーションファイルを作成します。テーブル名は、規約通りrole_user
とします。
$ php artisan make:migration create_role_user_table −−create=role_user
これにより、データベースのスキーマ定義を記述するPHPファイルが database/migrations
ディレクトリ内に生成されます。
マイグレーションのスキーマ定義
生成されたファイル(例: 2025_01_01_000000_create_role_user_table.php
)を編集し、二つの外部キーを設定します。Laravel 12では、foreignId()
メソッドと constrained()
メソッドを組み合わせることで、外部キーの定義を簡潔に行えるため、初心者でも間違いにくいです。
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { /** * マイグレーションを実行 */ public function up(): void { Schema::create('role_user', function (Blueprint $table) { $table−>id(); // 中間テーブル自体の主キー (必須ではないが推奨) // user_idカラムを作成し、usersテーブルのidに外部キー制約を設定 // onDelete('cascade') は、ユーザーが削除されたら関連付けも自動削除する設定 $table−>foreignId('user_id')−>constrained()−>onDelete('cascade'); // role_idカラムを作成し、rolesテーブルのidに外部キー制約を設定 $table−>foreignId('role_id')−>constrained()−>onDelete('cascade'); // ユーザーと役割の組み合わせが重複しないようにユニーク制約を設定 (任意だが推奨) $table−>unique(['user_id', 'role_id']); $table−>timestamps(); // created_at と updated_at カラム }); } /** * マイグレーションをロールバック */ public function down(): void { Schema::dropIfExists('role_user'); } };
上記のコードでは、foreignId('user_id')−>constrained()
が user_id
カラムを作成し、自動で users
テーブルへの外部キーを設定しています。最後に php artisan migrate
コマンドを実行すれば、データベースに中間テーブルが作成され、多対多リレーションの準備が完了します。
![]() | PHPフレームワークLaravel入門第2版 [ 掌田津耶乃 ] 価格:3300円 |

![]() | Laravelの教科書 バージョン10対応 [ 加藤 じゅんこ ] 価格:3300円 |

多対多リレーションを使ったデータの操作(登録・取得・解除)
Eloquentで多対多リレーションを定義したら、次は実際に中間テーブルのデータを操作する方法を学びましょう。多対多リレーションでは、関連付けを行うための専用のメソッド群(attach
, detach
, sync
)を使います。
データの関連付け(登録):attach()
メソッド
attach()
メソッドは、新しい関連付けを中間テーブルに挿入します。これは、既存の関連付けを上書きすることなく、単純に追加したい場合に利用します。
<?php // IDが1のユーザーを取得 $user = App\Models\User::find(1); // ユーザーにIDが3と5の役割(Role)を関連付ける // 中間テーブル(role_user)に2つの新しいレコードが追加される $user−>roles()−>attach([3, 5]); // または単一のIDを渡すことも可能 // $user−>roles()−>attach(6);
データの取得
関連データを取得する際は、他のリレーションと同様に、定義したメソッド名(この例では roles
)をプロパティとして呼び出すだけで、コレクションとして関連モデルのリストを取得できます。
<?php $user = App\Models\User::find(1); echo "ユーザー名: " . $user−>name . "\n"; echo "--- 割り当てられた役割 ---\n"; // $user−>roles は Roleモデルのコレクションを返す foreach ($user−>roles as $role) { echo "− " . $role−>name . "\n"; }
関連付けの解除:detach()
メソッド
detach()
メソッドは、指定した関連付けを中間テーブルから削除(解除)します。
<?php $user = App\Models\User::find(1); // ユーザーからIDが3の役割との関連付けを解除 $user−>roles()−>detach(3); // IDを指定せず呼び出すと、そのユーザーの全ての役割との関連付けを解除する // $user−>roles()−>detach();
関連付けの同期:sync()
メソッド
実務で最もよく使われるのが sync()
です。これは、渡されたIDのリストに中間テーブルの状態を完全に一致させます。リストにない既存の関連は自動的に削除されるため、権限の一括更新などに最適です。
<?php $user = App\Models\User::find(1); // ユーザーの役割をID: [1, 7, 10] の3つだけにする // 既存でこれ以外の役割があれば解除、足りなければ新規に attach される $user−>roles()−>sync([1, 7, 10]);
![]() | 気づけばプロ並みPHP 改訂版ーーゼロから作れる人になる! [ 谷藤賢一 ] 価格:2970円 |

中間テーブルに追加情報を持たせる:withPivot()
とwithTimestamps()
多対多リレーションの中間テーブルには、外部キーだけでなく、リレーション特有の追加データ(ピボットデータ)を保存したいケースが多くあります。例えば、「ユーザーに役割を割り当てた管理者ID」や「その役割の有効期限」などです。
カスタムカラムの取得:withPivot()
中間テーブルに追加したカスタムカラムをEloquentで取得対象に含めるには、リレーション定義時にwithPivot()
メソッドを使います。
モデル定義の拡張
中間テーブル(role_user
)に assigned_by
カラムと expires_at
カラムがあると想定し、User
モデルでこれらを取得できるようにします。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; class User extends Model { public function roles(): BelongsToMany { // 取得したい中間テーブルのカラム名を引数で指定 return $this−>belongsToMany(Role::class) −>withPivot('assigned_by', 'expires_at'); } }
ピボットデータへのアクセス
リレーションで取得したモデル(Role
)のデータから、中間テーブルのデータにアクセスする場合は、pivot
プロパティを経由します。
<?php $user = App\Models\User::find(1); foreach ($user−>roles as $role) { echo "役割名: " . $role−>name . "\n"; // pivot プロパティを通じて中間テーブルのカスタムカラムにアクセス echo "割り当てた管理者ID: " . $role−>pivot−>assigned−by . "\n"; echo "役割の有効期限: " . $role−>pivot−>expires−at . "\n"; }
タイムスタンプの自動管理:withTimestamps()
中間テーブルに created_at
と updated_at
カラムを定義している場合、withTimestamps()
メソッドを使えば、わざわざ withPivot()
で指定しなくても、これらのタイムスタンプを自動的に取得し、データ操作時に自動で更新してくれます。
<?php // withTimestamps() を追加することで、created_at/updated_at が自動で扱われる class User extends Model { public function roles(): BelongsToMany { return $this−>belongsToMany(Role::class) −>withPivot('assigned_by') // カスタムカラムのみ指定 −>withTimestamps(); // タイムスタンプを有効化 } }

Laravel初心者必見!失敗しないテーブル設計はマイグレーションで決まる …
Laravel初心者必見!失敗しないテーブル設計はマイグレーションで決まる …
【設計の壁を越える!】Laravelマイグレーション完全ガイド。初心者でも失敗しない「記事テーブル」の作り方、カラム型の選び方、リレーション設定まで、コードで学ぶデータベース設計の基礎。
【設計の壁を越える!】Laravelマイグレーション完全ガイド。初心者でも失敗しない「記事テーブル」の作り方、カラム型の選び方、リレーション設定まで、コードで学ぶデータベース設計の基礎。
多対多リレーションに関するFAQ (よくある質問)
多対多リレーションの実装や運用において、初心者の方がつまずきやすいポイントや、実務で役立つテクニックについてまとめました。
Q: 💡 Laravelの命名規約通りではない中間テーブルを使いたいのですが、可能ですか?
A. 可能です。
Laravelは中間テーブルの命名(例: role_user
)を自動で推測しますが、既存のデータベースや特殊な命名規則に対応するため、belongsToMany
メソッドの第2引数で中間テーブル名を明示的に指定できます。
<?php // 例: 中間テーブル名が 'user_roles_pivot' の場合 public function roles(): BelongsToMany { // 第2引数にカスタム中間テーブル名を指定 return $this−>belongsToMany(Role::class, 'user_roles_pivot'); }
Q: ⏱️ 中間テーブルに created_at
や updated_at
は必要ですか?
A. 必須ではありませんが、強く推奨されます。
これらを追加することで、「いつ役割が割り当てられたか」といった履歴情報が自動で記録されます。中間テーブルにこれらのタイムスタンプカラムがある場合、リレーション定義に**withTimestamps()
**メソッドを追加するだけで、Laravelが自動で管理してくれます。
Q: 🙅♂️ 同じ関連付けが二重に登録されるのを防ぐには?
A. 複合ユニーク制約を適用します。
マイグレーションファイルで、関連する二つの外部キー(例: user_id
と role_id
)の組み合わせに対してユニーク制約を設定するのが標準的な方法です。これにより、データベースレベルで重複登録を防ぐことができます。
<?php // マイグレーションファイル内のスキーマ定義 Schema::create('role_user', function (Blueprint $table) { // ... 外部キー定義 ... // user_id と role_id の組み合わせが重複しないようにする $table−>unique(['user_id', 'role_id']); // ... });
Q: 🔍 リレーション先のデータではなく、中間テーブルのデータだけで絞り込みたい場合は?
A. wherePivot()
メソッドを使います。
中間テーブルに expires_at
のようなカスタムカラムがある場合、そのカラムの値でフィルタリングを行うには、リレーションクエリビルダに対して wherePivot()
を使います。
<?php // 期限がまだ来ていない役割を持つユーザーを取得 $activeUsers = User::whereHas('roles', function ($query) { $query−>wherePivot('expires_at', '>', now()); })−>get();
多対多リレーションの完全マスターに向けて
本記事を通じて、Laravel 12における多対多リレーション(belongsToMany
)の核心を理解できたはずです。このリレーションは、一見複雑に思えるかもしれませんが、その構造は「二つの主要テーブル」と「一つの中間テーブル(ピボットテーブル)」というシンプルな構成で成り立っています。
特に重要なポイントは以下の通りです。
- ✅ 規約に従った中間テーブル名(例:
role_user
)を用いることで、コードが簡潔になる。 - ✅ モデルでは、相互に
belongsToMany(TargetModel::class)
を定義する。 - ✅ データ操作には、
attach()
,detach()
,sync()
という専用メソッドを利用する。特にsync()
は一括更新に非常に強力です。 - ✅ 中間テーブルのカスタムデータは、
withPivot()
を使って取得・操作できる。
多対多リレーションは、アプリケーションの柔軟性と拡張性を高める上で不可欠な要素です。権限管理、商品タグ付け、イベント参加者管理など、あらゆる実務シーンで活用できます。まずはご自身の開発環境で、ユーザーと役割などの簡単なリレーションを構築し、実際にsync()
やwithPivot()
を試してみることから始めてみましょう。この基礎知識があれば、Laravelでの大規模なデータ設計にも自信を持って取り組めるようになります。
![]() | Laravelの教科書 バージョン10対応 [ 加藤 じゅんこ ] 価格:3300円 |

![]() | PHPフレームワークLaravel入門第2版 [ 掌田津耶乃 ] 価格:3300円 |
