LaravelのMany-to-Many(多対多)テーブル実例

さてさて、Laravelのデータベース管理には始めから強力なリレーションシップを構築する機能が備わっていますが、Many-to-Many(多対多)の関係を構築する場合は、さすがに少しだけ複雑になっています。

私自身もリレーションシップのDB構造を考える場合に「あれ、どうだったっけ??」ということもたまにあるので、せっかくなのでこれを機に実例としてまとめておくことにしました。

ぜひお役にたてたら嬉しいです!

モデルをつくる

まずは、リレーションシップを作りたい2つのモデルを作りましょう。

今回の例は、ある生徒グループの趣味を管理する、

  1. Student ・・・ 生徒
  2. Hobby ・・・ 趣味

のテーブルでやってみることにします。

では、まずはLaravelのArtisanコマンドを使ってモデルを作成しましょう。

php artisan make:model Student -m
php artisan make:model Hobby -m

※「-m」がつけているのは、自動的にマイグレーションも作成してくれるからです。マイグレーションは次の項目で説明します。

次に作成した「Student」と「Hobby」モデル内にリレーションシップを設定します。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Student extends Model
{
    /**
     * The roles that belong to the user.
     */
    public function hobbies()
    {
        return $this->belongsToMany('App\Hobby');
    }
}
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Hobby extends Model
{
    /**
     * The roles that belong to the user.
     */
    public function students()
    {
        return $this->belongsToMany('App\Student');
    }
}

マイグレーションをつくる

さっきモデルを作ったときに対応するマイグレーションも一緒に作成したので、新しく必要なのは「Student」と「Hobby」をつなぐPivotテーブルだけです。以下のコマンドで作成しましょう。

php artisan make:migration create_student_hobbies_table

では、全部で3つあるマイグレーションの中身を設定していきます。

※冗長になるので、up()メソッドの中身だけを表示しています。また、サンプルなので追加しているフィールドは「name」のみです。適宜必要なフィールドを追加してください。

1.students

Schema::create('students', function (Blueprint $table) {
    $table->increments('id');
    $table->string('name');
    $table->timestamps();
});

2.hobbies

Schema::create('hobbies', function (Blueprint $table) {
    $table->increments('id');
    $table->string('name');
    $table->timestamps();
});

3.hobby_student(Pivotテーブル)

Schema::create('hobby_student', function (Blueprint $table) {
    $table->unsignedInteger('student_id')->index();
    $table->foreign('student_id')->references('id')->on('students')->onDelete('cascade');
    $table->unsignedInteger('hobby_id')->index();
    $table->foreign('hobby_id')->references('id')->on('hobbies')->onDelete('cascade');
});

ここで注意が必要なのは、onDelete()です。
これは、

  • もしStudentデータが削除されたら自動で該当データを削除
  • もしHobbyデータが削除されたら自動で該当データを削除

という意味です。

簡単に言うと、「あ、そっちのデータを削除するんだったら、もうこっちもいらないよね。はい、削除〜!」ってことです。

テストデータをつくる

テストを実行するために、「Student」と「Hobby」テーブルにあらかじめ以下のデータを追加しておきましょう。

studentsテーブル

hobbiesテーブル

※ひとつずつテーブルに追加してもいいですけど、めんどうな場合はSeeding機能で一気に追加してもいいでしょう。

テストしてみる

では、「桃太郎」の趣味が「読書」&「料理」になるようにデータを作成してみましょう。

$student = \App\Student::find(1);   // 桃太郎
$student->hobbies()->sync([1, 2]);  // 読書、料理

取得したStudentデータに「モデルをつくる」で作成したhobbies()を呼び出し、そこにsync()を使ってデータを保存しています。

これで、テーブルの中身はこうなりました。

もし、この後変更をしたい場合は同じようにすればOKです。

$student->hobbies()->sync([2, 3]);  // 料理、旅行

その場合は、テーブルはこう変更されます。

※つまり、IDが1の「読書」は削除されて、「料理」と「旅行」の2つが桃太郎の趣味になります。

では、リレーションシップを含めたStudentテーブルの全体が今どうなっているか見てみましょう。

$students = \App\Student::with('hobbies')->get();
print_r($students->toArray());

結果はこうです。

Array
(
    [0] => Array
        (
            [id] => 1
            [name] => 桃太郎
            [created_at] => 2018-05-20 19:24:25
            [updated_at] => 2018-05-20 19:24:25
            [hobbies] => Array
                (
                    [0] => Array
                        (
                            [id] => 2
                            [name] => 料理
                            [created_at] => 2018-05-20 19:24:25
                            [updated_at] => 2018-05-20 19:24:25
                            [pivot] => Array
                                (
                                    [student_id] => 1
                                    [hobby_id] => 2
                                )

                        )

                    [1] => Array
                        (
                            [id] => 3
                            [name] => 旅行
                            [created_at] => 2018-05-20 19:24:25
                            [updated_at] => 2018-05-20 19:24:25
                            [pivot] => Array
                                (
                                    [student_id] => 1
                                    [hobby_id] => 3
                                )

                        )

                )

        )

    [1] => Array
        (
            [id] => 2
            [name] => 金太郎
            [created_at] => 2018-05-20 19:24:25
            [updated_at] => 2018-05-20 19:24:25
            [hobbies] => Array
                (
                )

        )

    [2] => Array
        (
            [id] => 3
            [name] => かぐや姫
            [created_at] => 2018-05-20 19:24:25
            [updated_at] => 2018-05-20 19:24:25
            [hobbies] => Array
                (
                )

        )

)

最後に設定した「料理」「旅行」が桃太郎のデータ内に入っていますね。

ちなみに、これだけだとOne-to-Manyとほぼ同じですが、Many-to-Manyは逆側、つまりHobby側から呼び出してもリレーションシップが取得できます。

次のようにして、Hobbyテーブル側からも同じように見てみましょう。

$hobbies = \App\Hobby::with('students')->get();
print_r($hobbies->toArray());

中身はこれです。

Array
(
    [0] => Array
        (
            [id] => 1
            [name] => 読書
            [created_at] => 2018-05-20 19:24:25
            [updated_at] => 2018-05-20 19:24:25
            [students] => Array
                (
                )

        )

    [1] => Array
        (
            [id] => 2
            [name] => 料理
            [created_at] => 2018-05-20 19:24:25
            [updated_at] => 2018-05-20 19:24:25
            [students] => Array
                (
                    [0] => Array
                        (
                            [id] => 1
                            [name] => 桃太郎
                            [created_at] => 2018-05-20 19:24:25
                            [updated_at] => 2018-05-20 19:24:25
                            [pivot] => Array
                                (
                                    [hobby_id] => 2
                                    [student_id] => 1
                                )

                        )

                )

        )

    [2] => Array
        (
            [id] => 3
            [name] => 旅行
            [created_at] => 2018-05-20 19:24:25
            [updated_at] => 2018-05-20 19:24:25
            [students] => Array
                (
                    [0] => Array
                        (
                            [id] => 1
                            [name] => 桃太郎
                            [created_at] => 2018-05-20 19:24:25
                            [updated_at] => 2018-05-20 19:24:25
                            [pivot] => Array
                                (
                                    [hobby_id] => 3
                                    [student_id] => 1
                                )

                        )

                )

        )

)

今度は、Hobby側から見たデータなので、中身は「料理」の中に桃太郎、そして「旅行」の中にも桃太郎のデータが存在しています。

もちろん、データ追加もHobby側からできます。やり方は、さっき見たStudent側の「逆バージョン」です。

$hobby = \App\Hobby::find(1);       // 読書
$hobby->students()->sync([2, 3]);   // 金太郎、かぐや姫

現在のデータを図にしてみると、こうなります。

おわりに

こうやってMany-toManyでリレーションシップを作っておくと、どちら側からでもデータを呼び出せて便利ですね。

ということで、今回はMany-to-Manyのリレーションシップを紹介しました。

ぜひ開発にご活用くださいね!

ではでは〜。