Eloquentリレーションは、Laravelのモデル同士の関連付けを定義する仕組みです。例えば「部署には複数の社員が所属する」「社員は1つの部署に属する」といった関係をモデルに定義しておくと、SQLのJOINを書くことなく、直感的にデータを取得できるようになります。この記事では、最も基本的な1対多のリレーションを中心に、設定方法と使い方を解説します。
リレーションの基本概念
リレーショナルデータベースでは、テーブル同士を外部キーで関連付けます。Eloquentリレーションは、この外部キーの関連をPHPのメソッドとして定義し、$department->employeesのようなプロパティアクセスで関連データを取得できるようにする仕組みです。
リレーションを定義するには、app/Models/配下の各モデルファイルにメソッドを追加します。モデルファイルがない場合は、以下のコマンドで作成してください。
php artisan make:model Department
php artisan make:model Employee
Laravelの命名規則では、モデル名は単数形(Department, Employee)、テーブル名は複数形(departments, employees)です。この規則に従うと、Laravelが自動的にテーブルを推測してくれます。
1対多(hasMany / belongsTo)
最も頻繁に使うリレーションです。「1つの部署に複数の社員が所属する」という関係を例に設定します。
1側のモデル(Department)
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Department extends Model
{
public function employees()
{
return $this->hasMany(Employee::class);
}
}
hasMany()は「このモデルは複数のEmployeeを持つ」という1対多の関係を定義します。メソッド名は複数形(employees)にするのが慣例です。
多側のモデル(Employee)
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Employee extends Model
{
public function department()
{
return $this->belongsTo(Department::class);
}
}
belongsTo()は「このモデルは1つのDepartmentに属する」という逆方向の関係を定義します。メソッド名は単数形(department)にします。
リレーションが正しく動作するには、employeesテーブルにdepartment_idカラムが外部キーとして存在している必要があります。マイグレーションで$table->foreignId('department_id')->constrained();を定義しておいてください。
リレーションの使い方
モデルにリレーションを定義すると、プロパティアクセスで関連データを取得できます。
// 1側から多側を取得(部署の全社員を取得)
$department = Department::find(1);
$employees = $department->employees; // Eloquentコレクションが返る
foreach ($employees as $employee) {
echo $employee->name;
}
// 多側から1側を取得(社員の所属部署を取得)
$employee = Employee::find(5);
$departmentName = $employee->department->name;
// コントローラーでの実用例
public function show($id)
{
$department = Department::with('employees')->findOrFail($id);
return view('departments.show', compact('department'));
}
Eagerロード(N+1問題の解決)
リレーションを使う際に注意すべき最も重要なポイントが「N+1問題」です。ループ内でリレーションにアクセスすると、ループの回数分だけSQLクエリが発行されてしまい、パフォーマンスが大幅に低下します。
// N+1問題が発生するコード(部署数+1回のクエリが発行される)
$departments = Department::all(); // 1回目のクエリ
foreach ($departments as $department) {
echo $department->employees->count(); // 部署ごとに追加クエリ
}
// Eagerロードで解決(たった2回のクエリで済む)
$departments = Department::with('employees')->get();
foreach ($departments as $department) {
echo $department->employees->count(); // 追加クエリなし
}
with()メソッドを使うと、関連データを事前に一括取得(Eagerロード)します。部署が100件あっても、発行されるクエリは「部署の取得」と「全社員の取得」の2回だけです。リレーションをループ内で使う場合は、必ずwith()を使いましょう。
1対1(hasOne / belongsTo)
1対1のリレーションは、1つのレコードに対して関連レコードが最大1つしかない場合に使います。
// User.php
public function profile()
{
return $this->hasOne(Profile::class);
}
// Profile.php
public function user()
{
return $this->belongsTo(User::class);
}
// 使い方
$user = User::find(1);
$bio = $user->profile->bio; // プロフィールの自己紹介を取得
多対多(belongsToMany)
多対多のリレーションは中間テーブルを使って定義します。例えば「ユーザーは複数の役割を持ち、1つの役割は複数のユーザーに割り当てられる」という関係です。
// User.php
public function roles()
{
return $this->belongsToMany(Role::class);
}
// Role.php
public function users()
{
return $this->belongsToMany(User::class);
}
// 使い方
$user = User::find(1);
$roles = $user->roles; // ユーザーの全ロールを取得
// 中間テーブルへのデータ追加・削除
$user->roles()->attach($roleId); // ロールを追加
$user->roles()->detach($roleId); // ロールを削除
$user->roles()->sync([1, 2, 3]); // 指定したロールだけに同期
多対多の中間テーブル名は、2つのモデル名を英語のアルファベット順にスネークケースで結合したものがデフォルトです(例:role_user)。この規則に従えば、テーブル名を明示する必要はありません。
まとめ
hasMany()とbelongsTo()で1対多のリレーションを定義する- 1側のメソッド名は複数形、多側のメソッド名は単数形にするのが慣例
- プロパティアクセス(
$model->relation)で関連データを直感的に取得できる with()メソッドでEagerロードし、N+1問題を防ぐhasOne()で1対1、belongsToMany()で多対多のリレーションも定義できる- 多対多は
attach()、detach()、sync()で中間テーブルを操作する