Laravelでカスタムコレクションクラスを利用する

今回はLaravelのお話です。
先日5.5(LTS)がリリースされるなど最近何かと話題のLaravelですが、今回はそんな新しい機能の話ではありません。

Laravel5.xやCakePHP3.xでは、PHPの残念な配列(連想配列含む)を良い感じに扱えるCollectionというクラスが存在します。
上記2フレームワークでは、ORMを使ってDBへ問い合わせた結果がCollection型で返ってくるケースが多いこともあって、new Collection()するよりはそのインスタンスを使うケースのほうが多いように感じます。

通常のforforeachでループできることはもちろん、map()filter()といったデータの整形や絞り込みをメソッドチェーンで行えるので、直感的にコードを書くことができます。
そこで、「拡張コレクションを作って特定モデルの結果はそいつを使おうぜ」というのが今回のテーマです。

<?php
// ArticleCollection.php

namespace App\Models\Collections;

use Illuminate\Database\Eloquent\Collection;

class ArticleCollection extends Collection
{
    /**
     * 偶数記事だけを取り出す。
     *
     * @return static
     */
    public function evenPosts()
    { 
        return $this->filter(function($item) {
            // 偶数IDの記事のみ
            return $item->id % 2 === 0;
        })->map(function($item, $idx) {
            // 必要なものだけ抜き出した配列を返却
            return [
                'id' => $item->id,
                'title' => $item->title,
                'content' => $item->content,
            ];
        });
    }

    public function toBase()
    {
        return new self($this);
    }
}
<?php
// Article.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use App\Models\Collections\ArticleCollection;

class Article extends Model
{
    /**
     * newCollection関数をオーバーライドし、使いたいコレクションのインスタンスを返却
     */
    public function newCollection(array $models = [])
    {
        return new ArticleCollection($models);
    }

注意しないといけないのは、map()メソッドのように内部でコレクションを生成する関数を呼び出した場合です。
map()関数は、内部で保持しているインスタンスがModelでなかった場合、Collectionクラスを返します。
上記evenPosts()の例だと、メソッドが最終的に返却するArticleCollectionクラスが内包しているのは配列なので、この戻り値からさらにmap()すると戻り値はCollection型になってしまいます。
つまり、evenPosts()map()evenPosts()といった呼び出し方をするとエラーになります。

その場合は上記コードにあるように、継承元であるIlluminate\Support\CollectiontoBase()メソッドをオーバーライドすれば良いです。

同様のことがCakePHP3でもできるんじゃないかと思っているんですが、こちらは今のところIteratorクラスを使うことになりそうです。

Laravelはドキュメントも充実しているので、大体はそこから情報を得られます。
以下に私が利用しているサイトのリンクを掲載しておきます。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA