2
Help us understand the problem. What are the problem?

posted at

Laravel8.42で追加された、Has One Of Many のリレーション機能が便利

概要

  • 2021/05/18 にリリースされた、Laravel v8.42.0 で Eloquent の Has One Of Manyという機能がリリースされました。
  • Eloquentの hasOne では、1:1 のリレーションを定義できますが、
  • 1:N のリレーション関係のテーブルに対して、指定条件で擬似的に、1:1を定義できる機能が追加されたのです!
  • 便利!やった!\(^o^)/

公式ドキュメント

試して見る

  • usersorders テーブルを用意します。
  • users テーブルを起点に、ユーザーの注文履歴を Has One Of Many を活用して様々な形で取得してみましょう。
MySQL [docker]> select id, name from users where id = 1;
+----+---------------+
| id | name          |
+----+---------------+
|  1 | Rowland Sipes |
+----+---------------+
1 row in set (0.001 sec)
MySQL [docker]> select id, user_id, price, paid_at from orders where user_id = 1;
+----+---------+-------+---------------------+
| id | user_id | price | paid_at             |
+----+---------+-------+---------------------+
|  2 |       1 |  1718 | NULL                |
|  8 |       1 |  6117 | NULL                |
| 45 |       1 |  6671 | NULL                |
| 46 |       1 |  1562 | NULL                |
| 71 |       1 |  3806 | NULL                |
| 73 |       1 |  5038 | 2021-05-27 16:21:23 |
| 74 |       1 |  5994 | 2021-05-27 16:21:23 |
| 85 |       1 |  2239 | 2021-05-27 16:21:23 |
| 89 |       1 |  5898 | 2021-05-27 16:21:23 |
+----+---------+-------+---------------------+
9 rows in set (0.001 sec)

以下のように Userモデルに Has One Of Many の リレーションを定義します。

<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use HasFactory, Notifiable;

    public function latestOrder()
    {
        // max(id) の 条件で 1件取得する。
        return $this->hasOne(Order::class)->latestOfMany();
    }

    public function oldestOrder()
    {
        // min(id) の 条件で 1件取得する。
        return $this->hasOne(Order::class)->oldestOfMany();
    }

    public function largestOrder()
    {
        // max(price) の 条件で 1件取得する。
        return $this->hasOne(Order::class)->ofMany('price', 'max');
    }

    public function unpaidLatestOrder()
    {
        // 未払い(paid_at is null) かつ max(id) の 条件で 1件取得する。
        return $this->hasOne(Order::class)->ofMany([
            'id' => 'max',
        ], function ($query) {
            $query->whereNull('paid_at');
        });
    }

}

使ってみる

  • orders の SQLでの SELECT結果と
  • LaravelのEloquentのリレーション経由での取得結果を比べて頂ければわかりますが、期待通りの結果が得られました!
Route::get('/', function () {

    dump(User::find(1)->latestOrder()->first());
    // max(id) の 条件で 1件取得
    //    "id" => 89
    //    "user_id" => 1
    //    "price" => 5898
    //    "paid_at" => "2021-05-27 16:21:23"


    dump(User::find(1)->oldestOrder()->first());
    // min(id) の 条件で 1件取得
    //    "id" => 2
    //    "user_id" => 1
    //    "price" => 1718
    //    "paid_at" => null

    dump(User::find(1)->largestOrder()->first());
    // max(price) の 条件で 1件取得
    //    "id" => 45
    //    "user_id" => 1
    //    "price" => 6671
    //    "paid_at" => null

    dump(User::find(1)->unpaidLatestOrder()->first());
    // 未払い(paid_at is null) かつ max(id) の 条件で 1件取得
    //    "id" => 71
    //    "user_id" => 1
    //    "price" => 3806
    //    "paid_at" => null
});

あとがき

  • Laravelは、新しい機能がどんどんリリースされているので、最新に追随していくのが良いですね。
  • この利用シチュエーション結構あると思います。とても簡単で、直感的で使いやすいですね!
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
2
Help us understand the problem. What are the problem?