開発ブログ

株式会社Nextatのスタッフがお送りする技術コラムメインのブログ。

電話でのお問合わせ 075-744-6842 ([月]-[金] 10:00〜17:00)

  1. top >
  2. 開発ブログ >
  3. PHP >
  4. Laravel >
  5. 【Laravel 10】Laravel 10に追加されたリレーションの「one(One Of Many)」を紹介

【Laravel 10】Laravel 10に追加されたリレーションの「one(One Of Many)」を紹介

こんにちは、モリです。 今回は、Laravel 10.4で追加された機能を紹介したいと思います。といいたいところですが、Laravel 8.xで既に追加された機能についてのおさらいがメインになるかと思います。

Laravel 10.4ではリレーションに関わるone()メソッドが追加されました。(参考リンク:https://laravel-news.com/laravel-10-4-0)

one()メソッドは、Has ManyリレーションをHas Oneリレーションに変換してくれる機能となります。 ただし、1対多のリレーションから単一モデルを返す機能自体はLaravel 8.42で既に追加されてます。one()メソッドの前提となるその機能を個人的に利用する機会があまりなかったので、そちらから見ていきたいと思います。

環境

  • PHP: 8.1
  • Laravel Framework: 10.11.0

One Of Many

1対多の関係にあるdepartments(部署)テーブルとemployees(社員)テーブルを用意します。
スクリーンショット 2023-05-22 15.15.24.png
スクリーンショット 2023-05-22 15.15.47.png

特定の部署に所属する社員から特定の一人を取得するメソッド(Laravel 8.42で追加されたOne Of Manyと呼ばれる機能になります)に以下があるので使ってみたいと思います。

  • latestOfMany
  • oldestOfMany
  • ofMany


まず下記のようなDepartmentモデルを作成し、各リレーションを定義していきます(Employeeモデルは作成済みとします)。

<?php

namespace App\Models;

use App\Enum\Gender;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;

/**
 * Class Department
 * @package App\Models
 */
class Department extends Model
{
    use HasFactory;

    /**
     * @return HasMany
     */
    public function employees(): HasMany
    {
        return $this->hasMany(Employee::class);
    }

    /**
     * 最新の社員を取得する
     * @return HasOne
     */
    public function latestEmployee(): HasOne
    {
        return $this->hasOne(Employee::class)->latestOfMany();
    }

    /**
     * 最古の社員を取得する
     * @return HasOne
     */
    public function oldestEmployee(): HasOne
    {
        return $this->hasOne(Employee::class)->oldestOfMany();
    }

    /**
     * 一番身長が高い社員を取得する
     * @return HasOne
     */
    public function highestEmployee(): HasOne
    {
        return $this->hasOne(Employee::class)->ofMany('height', 'max');
    }
    
    /**
     * 一番身長が高い女性を取得する
     * 同じ身長が存在した場合、IDが小さい社員を取得する
     * @return HasOne
     */
    public function highestFemaleEmployee(): HasOne
    {
        return $this->hasOne(Employee::class)->ofMany(
            [
                'height' => 'max',
                'id' => 'min',
            ],
            function (Builder $query) {
                $query->where('gender', '=', Gender::FEMALE->value);
            });
    }
}

それぞれの取得結果を見ていきます

■ latestOfMany()

idが一番小さい社員を取得しています。
また、こちらのメソッドは引数をとることができ、デフォルトで"id"が指定されているためidが最小のデータが取得されます。ですので、例えばlatestOfMany('created_at')のようにtimestamp型の作成日カラムを指定すれば、idの大小に関わりなく、作成日が最新のデータを取得することができます。

dd(Department::query()->find(1)->latestEmployee->toArray());

array [
  "id" => 6
  "department_id" => 1
  "name" => "井上真奈美"
  "gender" => 2
  "birthday" => "2003-10-02"
  "height" => 161
  "assigned_at" => "2021-04-08 09:00:00"
  "created_at" => "2023-04-07T09:00:00.000000Z"
  "updated_at" => null
]

■ oldestOfMany()

latestOfMany()とは反対にidが一番大きい社員を取得しています。
そして、こちらもlatestOfMany()と同様、oldestOfMany('created_at')のように引数を指定すれば、作成日が最も古いデータを取得することができます。

dd(Department::query()->find(1)->oldestEmployee->toArray());

array [
  "id" => 1
  "department_id" => 1
  "name" => "山田太郎"
  "gender" => 1
  "birthday" => "2000-01-01"
  "height" => 170
  "assigned_at" => "2020-04-07 09:00:00"
  "created_at" => "2021-04-07T09:00:00.000000Z"
  "updated_at" => null
]

■ ofMany()

カラムと集計関数(max or min)を指定して 身長が一番大きい社員を取得しています

dd(Department::query()->find(1)->highestEmployee->toArray());

array [
  "id" => 2
  "department_id" => 1
  "name" => "高橋正樹"
  "gender" => 1
  "birthday" => "2001-06-13"
  "height" => 180
  "assigned_at" => "2020-04-07 09:00:00"
  "created_at" => "2021-04-07T09:00:00.000000Z"
  "updated_at" => null
]

■ ofMany()(複数条件あり)

ofMany()を利用して身長が一番大きい社員を取得しようとして同じ身長の場合が複数人いたとします。こうした場合、配列形式で2番目以降の条件を指定することができます。今回は'id' => 'min'を追加して、同身長がいた場合はidが一番小さい人を取得しています。 また、追加で集計範囲を絞り込むといった条件を加えたい場合は、第二引数にクロージャを渡して、女性だけに絞り込むということもできます。

dd(Department::query()->find(1)->highestFemaleEmployee->toArray());

array [
  "id" => 4
  "department_id" => 1
  "name" => "石川綾"
  "gender" => 2
  "birthday" => "2002-11-18"
  "height" => 161
  "assigned_at" => "2021-04-08 09:00:00"
  "created_at" => "2022-04-07T09:00:00.000000Z"
  "updated_at" => null
]

以上が、Laravel 8.42で追加されていた、本来はHas Manyリレーションである関係を、擬似的にHas Oneリレーションとして定義できる「One Of Many」の話でした。

One()

「One Of Many」の話を踏まえた所で、Laravel 10.4で追加されたone()メソッドに移りたいと思います。
one()メソッドは、Has ManyリレーションをHas Oneリレーションに変換してくれるメソッドとなります。 実際にone()メソッドを使用して、One Of Manyの一部メソッドを書き換えてみたいと思います。

     /**
     * one()を利用して一番身長が高い社員を取得する
     * @return HasOne
     */
    public function highestEmployee(): HasOne
    {
        return $this->employees()->one()->ofMany('height', 'max');
    }

    /**
     * 一番身長が高い女性を取得する
     * 同じ身長が存在した場合、IDが小さい社員を取得する
     * @return HasOne
     */
    public function highestWoman(): HasOne
    {
        return $this->employees()->one()->ofMany(
            [
                'height' => 'max',
                'id' => 'min',
            ],
            function (Builder $query) {
                $query->where('gender', '=', Gender::FEMALE->value);
            });
    }

上記のようにhasOne(Employee::class)と書かれていた部分が、employees()->one()に置き換わっています。モデルが1対多の関係を持っているなら往々にしてhasManyが定義されているため、one()メソッドを呼び出せばhasOneに変換できます。

ちなみにemployees()->one();のようにofMany()などを使用せずone()でとどめるとidが一番小さいレコードを取得します。

最後に

今回は、既存機能の「One of Many 」を抑えてから、Laravel 10.4で追加されたone()メソッドをご紹介しました。
laravelに新しく追加・拡張された機能を調べることは、今まで自分が有効活用してこなかった機能を新たに発見できる機会にもなりえるのがよいですね。
以上となります。ありがとうございました。

TOPに戻る