Laravel

Laravel Eloquentリレーション入門|1対多・多対多の設定と使い方

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)

app/Models/Department.php
<?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)

app/Models/Employee.php
<?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();を定義しておいてください。

リレーションの使い方

モデルにリレーションを定義すると、プロパティアクセスで関連データを取得できます。

PHP
// 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クエリが発行されてしまい、パフォーマンスが大幅に低下します。

PHP
// 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つしかない場合に使います。

PHP
// 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つの役割は複数のユーザーに割り当てられる」という関係です。

PHP
// 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()で中間テーブルを操作する