Laravelで多対多リレーションを扱う

こんにちは!むちょこです。今日もPHPが楽しいです?

今日はLaravelで多対多リレーションシップを扱う方法について解説したいと思います。

環境はPHP7、Laravel5.6です。

1. テーブル設計

Laravelの$php artisan make:authで生成されたusersテーブルが予めあるものとします。今回は、そのユーザに任意のスキルを登録できる機能を作りたいと思います。
ここでの”スキル”は、現時点で用意されているもの以外にも今後追加される可能性が高い類のデータとします。
そのため、usersテーブルに直接追加するのではなくskillsテーブルを別途用意し、多対多の関係となった各テーブルを中間テーブルで繋ぎます。

Laravelの場合、中間テーブルの命名はアルファベット順でテーブル名を並べるという規則がありますので、今回の場合はskill_userテーブルという名前になります。

ER図(物理)

2. Laravelのマイグレーションを生成&実行

usersテーブルは既にあるので、他の2つのテーブルを新規作成します。

コマンド
$ php artisan make:migration create_skills_table
Created Migration: 2018_11_05_051521_create_skills_table
$ php artisan make:migration create_skill_user_table
Created Migration: 2018_11_05_052621_create_skill_user_table
database/migrations/2018_11_05_051521_create_skills_table.php
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('skills', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name')->unique();
            $table->tinyInteger('type');
        });
    }
database/migrations/2018_11_05_052621_create_skill_user_table.php
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('skill_user', function (Blueprint $table) {
            $table->unsignedInteger('skill_id');
            $table->unsignedInteger('user_id');
            $table->primary(['skill_id', 'user_id']);

            //外部キー制約
            $table->foreign('skill_id')
                ->references('id')
                ->on('skills')
                ->onDelete('cascade');
            $table->foreign('user_id')
                ->references('id')
                ->on('users')
                ->onDelete('cascade');

        });
    }

skill_userテーブルでは、skill_idとuser_idの組み合わせが一意であることを保証するために複合キーを使います。Laravelでは、primaryメソッドに配列としてキーにしたいカラム名を与えることで設定できます。

また、skill_idとuser_idはそれぞれskillsテーブルとusersテーブルの各idに依存するため外部キー制約を設定します。onDeleteメソッドは依存先のデータが削除されたときに中間テーブルのデータも削除するために設定しています。

マイグレーションファイルが用意できたら、以下のコマンドで実行します。

コマンド
$ php artisan migrate

3. Eloquentでリレーションを定義

UserモデルとSkillモデルに多対多リレーションを定義します。

Userモデルは既に存在するので、以下を追記します。

app/User.php
    /**
     * userのスキルを取得
     */
    public function skills()
    {
        return $this->belongsToMany('App\Skill');
    }

Skillモデルは新規作成します。

コマンド
$ php artisan make:model Skill
Model created successfully.
app/Skill.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Skill extends Model
{
    /**
     * skillを所有するユーザを取得
     */
    public function users()
    {
        return $this->belongsToMany('App\User');
    }

}

これでリレーションの定義が完了しました✨

4. 中間テーブルにデータを保存

ユーザとスキルの関係を中間テーブルに保存していきましょう。

予めusersテーブルとskillsテーブルには、以下のようなデータが入っているものとします。

usersテーブル

idname
1鈴木
2田中
3佐藤

skillsテーブル

idname
1
PHP
2JavaScript
3Webデザイン
4イラスト
5HTML
6CSS

ここでは、ユーザがフォームから自分でスキルを登録できる機能を想定して進めていきます。

フォーム例
MEMO

本筋からずれるのでフォームやバリデーションなどの細かいところは割愛します。画像ファイル版ですが、こちらの記事には詳しく書いてありますのでよろしければどうぞ!

Laravelで画像をアップロードする方法
app/Http/Controllers/ProfileController.php
    /**
     * プロフィールの保存
     *
     * @param ProfileRequest $request
     * @return Response
     */
    public function store(ProfileRequest $request)
    {
        //スキルの登録
        if (is_array($request->skills)) {
            $user = Auth::user();
            $user->skills()->detach(); //ユーザの登録済みのスキルを全て削除
            $user->skills()->attach($request->skills); //改めて登録
        }

        return redirect('profile')->with('success', '新しいプロフィールを登録しました');
    }

$request->skillsには、array(2) { [0]=> string(1) “3” [1]=> string(1) “4” }などのskill_idが格納された配列が代入されています。これをattachメソッドに渡すことで、簡単に中間テーブルへ保存することができます。

また、ユーザとスキルの組み合わせは一意でなくてはいけないため、登録前にdetachメソッドで登録済みのスキルを全て削除しています。

各ユーザでログインし、フォームから送信することでskill_userテーブルにこのようなデータが入りました。

skill_iduser_id
21
31
51
61
12
22
52
62
13
23

5. 双方向からデータを取得

最後に、ユーザからスキルを取得する方法と、スキルからユーザを取得する方法です。

任意のコントローラ

        //ユーザが持っているスキルを取得
        $user = User::find(1);
        foreach($user->skills as $skill) {
            var_dump($skill->name); //string(10) "JavaScript" string(15) "Webデザイン" string(4) "HTML" string(3) "CSS"
        }

        //スキルを持っているユーザを取得
        $skill = Skill::find(1);
        foreach ($skill->users as $user) {
            var_dump($user->name); //string(6) "田中" string(6) "佐藤"
        }

コントローラから各モデルを呼び出したいときは、use App\Userやuse App\Skillなど、冒頭でuse宣言することを忘れないでくださいね。

以上でLaravelで多対多リレーションを扱う方法についての解説を終わります。