BLOG

ブログ

2024/07/22 技術系

Laravelのリレーション整理:多対多のリレーション【belongsToMany】

この記事を書いた人 M.Y
画像1

前回のブログでは『1対多のリレーション(hasMany)』、また前々回のブログでは『1対1のリレーション(hasOne)』について整理してきました!
今回は、Eloquentのリレーションのうち、多対多のリレーションについてまとめていきます💪

多対多のリレーション(belongsToMany)

多対多の場合はそれぞれのモデルを繋ぐための中間テーブルを設ける必要があります。
その中間テーブルに紐づけるモデルそれぞれにbelongsToManyを定義します。

モデル1モデル2
多対多belongsToManybelongsToMany

前回1対多のリレーションの場合の子モデル側の定義が『belongsTo』であったように、多対多では『belongsToMany』と複数のモデルを持っているということになります。

belongsToManyのリレーション定義方法について例を用いて整理していきます🖋️

前提

今回は学生🧑‍🎓と受講するコース📚を例にしていきます。
学生は複数のコースを受講でき、コースには複数の学生が参加できるという関係です。
そしてそれぞれの関係テーブルをリンクするための中間テーブル⛓️としてcourse_studentを定義します!

DB定義

まず、studentscoursesの2つのテーブルと、それらを結びつけるための中間テーブルcourse_studentを作成します。

まずはstudentsテーブルのマイグレーション。

// studentsテーブル🧑‍🎓作成
Schema::create('students', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('student_number');
});

次にcoursesテーブルのマイグレーション。

// coursesテーブル📚作成
Schema::create('courses', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->string('course_label');
});

そして、course_studentテーブルのマイグレーションです。
ここでポイントになるのは中間テーブルは、『2つの外部キーを持つ必要がある』という点です。

// course_studentテーブル⛓️作成
Schema::create('course_student', function (Blueprint $table) {
    $table->id();
    // ここで『2つの外部キー』を設定
    $table->foreignId('student_id')->constrained()->onDelete('cascade');
    $table->foreignId('course_id')->constrained()->onDelete('cascade');
});

上記のマイグレーションから、DB構造は以下のようになります。

学生テーブル🧑‍🎓

カラム名コメント
id学生ID
name学生名
student_number学籍番号
学生テーブル(students)

コーステーブル📚

カラム名コメント
idコースID
titleコースタイトル
course_labelコースラベル
コーステーブル(courses)

中間テーブル⛓️

カラム名コメント
id中間テーブルID
student_id学生のリレーション
course_idコースのリレーション
中間テーブル(course_student)

モデル定義

リレーションはどちらのモデルに定義するの?

リレーションは通常、両方のモデルに定義します!
StudentとCourseどちらにも設定することを忘れないようにしましょう🙏

学生モデル🧑‍🎓

『学生は複数のコースを受講できる』という関係をStudentモデル内に定義するため、belongsToManyメソッドを使用します。

<?php

use Illuminate\Database\Eloquent\Model;

class Student extends Model
{
    /**
     * 生徒が受講するコースのリレーションを定義
     */
    public function courses()
    {
        return $this->belongsToMany(Course::class);
    }
}

コースモデル📚

『コースには複数の学生が参加できる』という関係をCourseモデル内に定義するため、こちらにもbelongsToManyメソッドを使用します。

<?php

use Illuminate\Database\Eloquent\Model;

class Course extends Model
{
    /**
     * コースを受講する生徒のリレーションを定義
     */
    public function students()
    {
        return $this->belongsToMany(Student::class);
    }
}

ここでbelongsToManyメソッドを使用することで、引数で渡したクラスのモデルインスタンスを返します。
belongsToManyメソッドはオプション設定ができます。
上記は、第2引数以下を省略することで、親モデル名_idが紐づくよう規約で自動的にリレーションしてくれます。
第2引数以下を設定することで、リレーションするカラムの紐付けを指定するよう設定が可能です。

public function courses()
{
    return $this->belongsToMany(
        Course::class,           // 関連するモデルのクラス名
        'course_student',        // 中間テーブルの名前(省略可能)
        'student_id',            // 中間テーブルにおける呼び出し元モデルの外部キー名(省略可能)
        'course_id',             // 中間テーブルにおける関連モデルの外部キー名(省略可能)
    );
}
  • 第1引数
    • 関連するモデルのクラス名を指定します。(必須)
public function courses()
{
    return $this->belongsToMany(Course::class);
}
  • 第2引数
    • 中間テーブルの名前を指定します(省略可能)。
      そもそも、リレーションの中間テーブルのテーブル名は、
      ・2つのテーブルをアルファベット順に並べる
      ・2つのテーブル名 (※単数形) を_ (アンダーバー) で繋げる
      という命名規則があり、第2引数を指定しない場合は、上記テーブル名であるという前提で処理が行われます。
public function courses()
{
    // course_student 以外のテーブル名(ここではcustom_course_student)を指定したい時
    return $this->belongsToMany(Course::class, 'custom_course_student');
}
  • 第3引数
    • 中間テーブルにおける呼び出し元モデルの外部キー名(接続元の中間テーブルの外部キー名)を指定します(省略可能)。
  • 第4引数
    • 中間テーブルにおける関連モデルの外部キー名(接続先の中間テーブルの外部キー名)を指定します(省略可能)。
public function courses()
{
    // 各モデルのID以外を外部キーに設定したい時
    return $this->belongsToMany(Course::class, 'custom_course_student', 'student_number', 'course_label');
}

外部キーを指定した場合は中間テーブルのマイグレーションの設定も正しく行いましょう☝️

public function courses()
{
    return $this->belongsToMany(Course::class, 'custom_course_student', 'custom_student_id', 'custom_course_id');
}

// studentsテーブルのマイグレーション
Schema::create('students', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('student_number')->unique(); // student_numberをユニークにする
});

// coursesテーブルのマイグレーション
Schema::create('courses', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->string('course_label')->unique(); // course_labelをユニークにする
});

// custom_course_studentテーブルのマイグレーション
Schema::create('custom_course_student', function (Blueprint $table) {
    $table->id();
    $table->string('student_number'); // 外部キーとしてstudent_numberを使用
    $table->string('course_label');   // 外部キーとしてcourse_labelを使用
    $table->foreign('student_number')->references('student_number')->on('students')->onDelete('cascade');
    $table->foreign('course_label')->references('course_label')->on('courses')->onDelete('cascade');
});

まとめ

今回は少し複雑な多対多のリレーションの定義方法についてまとめてみました!🙇‍♀️
Eloquentの機能をしっかり使えるようにすることで得られるメリットは以前のブログでも書かせていただいていますが、Eloquentモデルオブジェクトで返してくれてデータ型も担保されている上に、その後のデータの取り扱いを便利にしてくれるクエリビルダも使うことができます👍
自由自在に使いこなせるととっても重宝すること間違いなしです🙌

3回にわたって、Laravelのリレーションについてをまとめさせていただきました🙇‍♀️
整理することで基本的な使用ルールや、そのルールでは賄えない時の対処法を改めて認識できました。
どう使うんだったかな?と迷った時のお役に立てたら幸いです☺️



株式会社ウイングドアは福岡のシステム開発会社です。
現在、私達と一緒に"楽しく仕事が出来る仲間"として、新卒・中途採用を絶賛募集しています!
ウイングドアの仲間達となら楽しく仕事できるかも?と興味をもった方、
お気軽にお問い合わせ下さい!

アーカイブ