Laravel モデルのリレーション:多対多

データベースのテーブルの多くは他のテーブルと関係性を持ちます。例えば posts テーブルで管理されているブログの投稿は comments テーブルで管理されている複数のコメントと関係しています。

テーブル同士の関係性をEloquent Modelを使えば簡単に定義することができます。今回はモデルを使った関係性の中でも多対多のリレーションについて定義と実際の利用方法をまとめます。

多対多を定義する

「学生」と生徒が受講している「授業」を想定してみましょう。

多対多の関係の場合、2つのテーブルとは別に中間テーブルというものを作ります。中間テーブルは両方のテーブルの id がわかるように student_id , class_id のようなカラムを持ちます。

※ 中間テーブルもLaravelのMigrationで作成する必要があります。

これによって、例えば students テーブルの1番の学生が受講している授業は、 classes テーブルの1番だということがわかります。

また classes テーブルの1番の授業は、 students テーブルの1番、2番、3番の学生が受講していることがわかります。

使用するモデルの作成

Student モデルと Class モデルを作成します。

$ php artisan make:model Student
$ php artisan make:model Class

コマンドを実行したら app ディレクトリ配下に Student.php と Class.php が作成されていればOKです👍

StudentモデルにClassモデルとのリレーションを定義する

Student モデルにはbelongsToMany メソッドを利用して、 Class モデルとのリレーションを表す classes メソッドを実装できます。(1つの Student に対して複数の Class が関係しているので、メソッド名は複数形です)

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Student extends Model
{
    public function classes()
    {
        return $this->belongsToMany('App\Class');
    }
}

多対多の関係を定義すると自動的に中間テーブルは2つのモデルがアルファベット順になっているものと判断されます。今回の場合は class_student テーブルです。

中間テーブルがアルファベット順ではない、または全然別の名前の場合、 belongsToMany メソッドの第二引数に中間テーブル名を記述してください。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Student extends Model
{
    public function classes()
    {
        // 中間テーブルが student_class の場合
        return $this->belongsToMany('App\Class', 'student_class');
    }
}

また中間テーブルに存在するカラム名がモデル名と異なる場合、belongsToMany メソッドの第三引数、第四引数に指定することができます。第三引数にはリレーションを定義しているモデルを判別するカラム、第四引数にはリレーション先のデータを判別するカラムを指定します。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Student extends Model
{
    public function classes()
    {
        // 中間テーブルのカラムがstudent_idとclass_idの場合
        // Studentモデルに実装されているため、第三引数はStudentモデルを判別するカラムを指定
        return $this->belongsToMany('App\Class', 'student_class', 'student_id', 'class_id');
    }
}

リレーションを利用する

Model同士のリレーションの定義ができたら、コントローラーとbladeでどのように利用するのかをみていきましょう。

Studentモデルに紐づいたClassをコントローラーで扱う

今回はControllerの中で利用することを想定して、以下のような StudentsController と indexアクション を作成しました。

※ Studentモデルを利用するためにファイル上部で use App\Student; と書かれていることを確認してください。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Student;

class StudentsController extends Controller
{
    public function index()
    {
        //
    }
}

多対多のリレーションですが、1つのデータに対して、複数のデータが紐づいていることを表すことに変わりありません。まずはStudent モデルを使ってデータを1つ見つけます。以下の例では id = 1 のデータを取得します。

public function index()
{
    $student = Student::find(1);
}

※ モデルをつかったデータ取得方法はこちらも記事を参考にしてください。
Laravel モデルの作成とデータ取得

Student モデルが1つ見つかると紐づく Class モデルが見つかります。先ほど Student モデルで belongsToMany メソッドを使って定義した関数を利用することができます。

public function index()
{
    // id = 1のデータを取得
    $student = Student::find(1);

    // $studentに紐づくClassをCollectionとして取得
    $classes = $student->classes;

    // $classesはCollectionなのでループができる
    foreach ($classes as $class) {
        //
    }
}

これでコントローラーでモデルのリレーションを使ったデータ取得ができます。

Studentモデルに紐づいたClassをbladeで扱う

例えば StudentsControllerindexアクション が以下のような実装だったとします。

public function index()
{
    $student = Student::find(1);
    return view('home', ['student' => $student]);
}

id = 1Student モデルを取得して、 home.blade.php というviewファイルを返しています。

bladeファイルで $student に紐づいている Class モデルを利用するにはどうすればいいでしょうか。コントローラーと同じく、 先ほど Student モデルで belongsToMany メソッドを使って定義した関数を利用することができます。

以下の例では仮に Student モデルは name属性Class モデルにも name属性 を持っているものとします。

{{-- $studentのname属性を表示します --}}
<p>{{ $student->name }}</p>

{{-- $studentに紐づくClassモデルをCollectionとして取得し、かつname属性を表示します --}}
@foreach($student->classes as $class)
  <p>{{ $class->name }}</p>
@endforeach

$student に紐づくクラスはCollectionとして取得するのでループを利用することに注意

※ 個人的にはbladeで $student->classes のように書くのではなく、コントローラーで変数に定義してから、bladeに渡してあげた方が読みやすいと思います。

これでbladeでモデルのリレーションを使ったデータ取得、表示ができます。

逆のリレーションを定義する

今まで Student モデルから紐づいている Class モデルを取得する方法をみてきましたが、逆に Class モデルから紐づいている1つの Student モデルを取得する方法をみていきます。

belongsToMany メソッドを利用したリレーションの逆を表現する時には同じく belongsToMany メソッドを利用します。Class モデルに以下のように students メソッドを実装してください。(1つの Class に対して複数の Student が関係しているので、メソッド名は複数形です)

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Class extends Model
{
    public function students()
    {
        return $this->belongsToMany('App\Student');
    }
}

こちらも同じく中間テーブル名、それぞれを表すカラム名を第二引数、第三引数、第四引数で指定できます。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Class extends Model
{
    public function students()
    {
        // 中間テーブルが student_class の場合
        // 中間テーブルのカラムがstudent_idとclass_idの場合
        // Classモデルに実装されているため、第三引数はClassモデルを判別するカラムを指定
        return $this->belongsToMany('App\Student', 'student_class', 'class_id', 'student_id');
    }
}

リレーションの利用の仕方も今までと同じです。コントローラーでもbladeでも Class モデルに紐づいた Student モデルを利用することができます。

※ Classモデルを利用するためにファイル上部で use App\Class; と書かれていることを確認してください。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Class;

class ClassesController extends Controller
{
    public function index()
    {
        // id = 1のデータを取得
        $class = Class::find(1);

        // $classに紐づくPostモデルを取得
        $students = $class->students;

        return view('home', ['students' => $students]);
    }
}
{{-- $classのname属性を表示します --}}
<p>{{ $class->name }}</p>

{{-- $classに紐づくStudentモデルを取得し、かつname属性を表示します --}}
@foreach($class->students as $student)
  <p>{{ $student->name }}</p>
@endforeach

まとめ

データベースのテーブルの多くは他のテーブルと関係性を持ちます。テーブル同士の関係性をEloquent Modelを使えば簡単に定義することができます。

今回はモデルを使った関係性の中でも多対多のリレーションについて定義と実際の利用方法をみてきました。

多対多の関係の場合、2つのテーブルとは別に中間テーブルというものを作ります。中間テーブルは両方のテーブルの id がわかるように student_id , class_id のようなカラムを持ちます。

リレーション(関係性)を定義する時には各モデルに belongsToMany メソッドを利用して実装します。

コントローラーとbladeで利用する時にはモデルに実装した関数を使ってデータ取得をします。