前回においてCollectionのeach()Userのオブジェクトにageの属性を追加する処理をしましたが、今回はこれを発展させて、UserのCollectionにカスタムメソッドを追加します。

目的

例えば、いつも大人(20歳以上)だけのユーザーを対象にした処理を行いたいとします。
DBから取得したレコードにおいては、birth_dateの属性があるので現時点での大人だけのレコードは、以下のようにfilter()を利用して、

User::all()->filter(function ($user) {
    return $user->isAdult();
})

のような処理をすれば大人だけのレコード取得が可能です。

上で使用されているisAdult()は以下のように定義します。歳の計算は、アクセッサ getAgeAttributeで行います。


namespace App;

use Carbon\Carbon;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;
...
    public function getAgeAttribute()
    {
        return Carbon::parse($this->birth_date)->age;
    }

    public function isAdult()
    {
        return $this->age >= 20;
    }
}

さて、これで実際に先ほどのコードをtinkerで実行すると、

>>> User::all()->filter(function ($user) {
        return $user->isAdult();
});

=> Illuminate\Database\Eloquent\Collection {#4179
     all: [
       0 => App\User {#3306
         id: 1,
         name: "坂本 充",
         email: "nishida@example.net",
         email_verified_at: "2021-05-30 21:55:14",
         #password: "$2y$10$snEjTmJvBDu3uxd1bz0QDupihJA3zjvEH5J0pm/aLHG4UvpSfJe/q",
         #remember_token: "j8hPjp8ob7",
         birth_date: "1997-03-02",
         created_at: "2021-05-30 21:55:14",
         updated_at: "2021-05-30 21:55:14",
       },
       2 => App\User {#3986
         id: 3,
         name: "近藤 知実",
         email: "atsushi.ito@example.net",
         email_verified_at: "2021-05-30 21:55:14",
         #password: "$2y$10$A7xldQvOiCfUOyNS57EEYu2/s77a70BPmGoOjzDSgTmveeRlcAD8.",
         #remember_token: "qOFWQFb5ER",
         birth_date: "1972-09-14",
         created_at: "2021-05-30 21:55:14",
         updated_at: "2021-05-30 21:55:14",
       },
     ],
   }

と大人だけのレコードを返してくれます。

さらに、コールバックの定義も必要なしに、以下のようにfilterをあたかも属性のようにしての使用も可能です。短縮されていいですね。

>>> User::all()->filter->isAdult()

=> Illuminate\Database\Eloquent\Collection {#3299
     all: [
       0 => App\User {#4238
         id: 1,
         name: "坂本 充",
         email: "nishida@example.net",
         email_verified_at: "2021-05-30 21:55:14",
         #password: "$2y$10$snEjTmJvBDu3uxd1bz0QDupihJA3zjvEH5J0pm/aLHG4UvpSfJe/q",
         #remember_token: "j8hPjp8ob7",
         birth_date: "1997-03-02",
         created_at: "2021-05-30 21:55:14",
         updated_at: "2021-05-30 21:55:14",
       },
       2 => App\User {#4240
         id: 3,
         name: "近藤 知実",
         email: "atsushi.ito@example.net",
         email_verified_at: "2021-05-30 21:55:14",
         #password: "$2y$10$A7xldQvOiCfUOyNS57EEYu2/s77a70BPmGoOjzDSgTmveeRlcAD8.",
         #remember_token: "qOFWQFb5ER",
         birth_date: "1972-09-14",
         created_at: "2021-05-30 21:55:14",
         updated_at: "2021-05-30 21:55:14",
       },
     ],
   }
>>>

しかし、凄いことに、これをさらに進めて、以下のようにCollectionのカスタムメソッドの作成してUserのCollectionに適用することも可能なのです。

User::all()->adults()

まず、Collectionを継承したクラスを作成して、adults()のカスタムメソッドを定義します。


namespace App\Collections;

use Illuminate\Database\Eloquent\Collection;

class UserCollection extends Collection
{
    public function adults()
    {
        return $this->filter->isAdult();
    }
}

そして、そのカスタムクラスをモデルのクラス(User)のnewCollection()のメソッドで返り値とします。

namespace App;

use App\Collections\UserCollection;
use Carbon\Carbon;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;
...
    public function getAgeAttribute()
    {
        return Carbon::parse($this->birth_date)->age;
    }

    public function isAdult()
    {
        return $this->age >= 20;
    }

    public function newCollection(array $models = [])
    {
        return new UserCollection($models);
    }
}

実行してみましょう。

>>> User::all()->adults();

=> App\Collections\UserCollection {#3305
     all: [
       0 => App\User {#4024
         id: 1,
         name: "坂本 充",
         email: "nishida@example.net",
         email_verified_at: "2021-05-30 21:55:14",
         #password: "$2y$10$snEjTmJvBDu3uxd1bz0QDupihJA3zjvEH5J0pm/aLHG4UvpSfJe/q",
         #remember_token: "j8hPjp8ob7",
         birth_date: "1997-03-02",
         created_at: "2021-05-30 21:55:14",
         updated_at: "2021-05-30 21:55:14",
       },
       2 => App\User {#3986
         id: 3,
         name: "近藤 知実",
         email: "atsushi.ito@example.net",
         email_verified_at: "2021-05-30 21:55:14",
         #password: "$2y$10$A7xldQvOiCfUOyNS57EEYu2/s77a70BPmGoOjzDSgTmveeRlcAD8.",
         #remember_token: "qOFWQFb5ER",
         birth_date: "1972-09-14",
         created_at: "2021-05-30 21:55:14",
         updated_at: "2021-05-30 21:55:14",
       },
     ],
   }

同じ結果となりました。コードが短くなっただけでなくわかりやすくもなりました。

最後に注意としてですが、今回の例はレコード数がとても多いときはパフォーマンスの点からすると非常に悪い例です。すべてのDBレコードを取得してコードにおいて抽出を行うわけですゆえに。パフォーマンスを考えるとDBのSQLレベルにおいて大人の条件を入れて実行すべきです。

By khino