コハクウェブデザイン

【Laravel】1対多リレーションを便利に使うEloquent

LravelでEloquentを使って1対多のリレーションを確立していろいろ操作してみます。
今回は生徒のデータを入れる「students」テーブルと宿題のレポートを集めた「repors」テーブルを用意します。
一人の生徒は複数のレポートを提出できるので1対多の関係になります。

手順はざっくりとこんな感じです。

  1. studentsテーブルのマイグレーションファイルを作りマイグレーションする(特に特殊な設定は必要なし)
  2. reportsテーブルのマイグレーションを作りマイグレーションする(student_idカラムを持たせる)

まず、studentsテーブルのマイグレーションファイルは特に変わったところはなしです。
カラムは「id」「name」「created_at」「updated_at」

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateStudentsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('reports', function (Blueprint $table) {
            $table->id();            
            $table->string('name');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('students');
    }
}

続けて宿題を保存する「reports」テーブルのマイグレーションファイル。
特筆すべきは「student_id」カラムを持たせることです。
主となるテーブル(今回の場合students)の名前の単数形_idという形で従テーブル(今回はreportsテーブル)にカラムを持たせると、Laravelが自動的に判断して便利にリレーションしてくれます。

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateReportsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('reports', function (Blueprint $table) {
            $table->id();
            $table->integer('student_id'); //(リレーションを持たせる主テーブルの単数形_id)の形でカラム名を定義するとlaravelがリレーションを確立してくれる
            $table->string('title');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('reports');
    }
}

Aくんの情報とAくんが提出したレポートの情報を同時に取得する

とある生徒Aくんの情報をAくんが提出したレポートの一覧と共に取得するにはStudentモデルをカスタマイズすると便利です。

Studentモデルにreports()メソッドを追加してみましょう。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Student extends Model
{
    use HasFactory;
    // ここから下です。
    public function reports() {
        return $this->hasMany('App\Models\Report');
    }
}

hasMany()メソッドを持たせることで、Studentの情報(studentsテーブルのid)に紐づくreportsテーブルの情報も一緒に取ってくることができます。

hasMany()だけではなくhasOne()、belongsTo()、belongsToMany()などいろいろあるのですが、今回の場合は「A君は複数のレポートを提出できる = Studentモデルは複数のReportモデルを持つ = Student model has many report models」という関係になるのでhasMany()メソッドです。

public function reports() {
    return $this->hasMany('App\Models\Report');
}

repors()メソッドを作成してその中でhasMany()メソッドを使っているので使用する時はreportsを使います。


このメソッドを作ることによりコントローラ等から以下の用に実行すると、Aくんが提出したレポート全てが抽出できるようになります。
ちなみにいろいろ試してみたけどメソッド名(今回の場合はreports)は違う名前にしても動きました。

Student::find(1)->reports;

全生徒のデータをレポート情報込で取得する

では次に全生徒のデータを、各生徒に紐付いているレポート情報付きで取得してみましょう。
その場合も先程Studentモデルに定義した「reports()」メソッドを使うと便利です。
生徒一覧 + 各生徒が提出したレポート一覧です。

Student::with('reports')->get();

これでOKです。簡単です。

レポートを提出した生徒一覧を取得する

先程は全生徒でしたが今度はレポートを提出した生徒のみの一覧を取り出してみます。

レポートを提出した生徒一覧です。

Student::whereHas('reports')->get();

レポートを提出した生徒一覧をレポート情報付きで取得する

どんどん行きましょう。
先程はレポートを提出した生徒一覧でしたが、レポートを提出した生徒をレポート情報付きで取得してみましょう。
ややこしくなってきた(笑)

レポートを提出した生徒一覧各+生徒が提出したレポート一覧です。

Student::whereHas('reports')->with('reports')->get();

です!

この「whereHas()」メソッドですが、リレーション相手のテーブルにて検索できるので例えばこんな具合にすると「1月1日以降にレポートを提出した生徒一覧をレポート情報付きで出力する」なんてこともできます。

1月1日以降にレポートを提出した生徒一覧各+生徒が提出したレポート一覧です。

Student::whereHas('reports',
    function($query) {
        $query->where('created_at', '>', '2020-01-01 00:00:00');
    }
)->with('reports')->get();

さらに「whereHas()」メソッドに引数を持たせたい場合は「use()」メソッドをつければOKです。
use($変数)という感じです。

$my_time = '2020-01-01 00:00:00';

Student::whereHas('reports',
    function($query) use ($my_time) {
        $query->where('created_at', '>', $my_time);
    }
)->with('reports')->get();