【実践】CakePHP3 Collection活用事例

[ユアマイスターアドベントカレンダー2019](https://qiita.com/advent-calendar/2019/yourmystar) の19日目の記事です。

CakePHP3から登場したCollectionクラスの便利メソッド、皆さん活用していますか？

普段CakePHP3で開発している時も実は知らないうちに使っていることが多く、Queryオブジェクトに対してall()を呼び出して得られるデータの型(ResultSetオブジェクト)が実はCollectionTraitをuseしていて便利メソッドを活用できたりします。皆さんもQueryオブジェクトをall()したあと、何気なくcount()やtoArray()などを使っていると思いますが、これらのメソッドは実はCollectionTraitに備わっているのです。ちなみにCollectionクラスもCollectionTraitをuseしています。

今回はそんなCollectionの便利メソッドの実戦活用事例をいくつか紹介してみようと思います。

# each()でメール等の通知系処理
例えば記事(articlesテーブル)を執筆したライター(writersテーブル)全員へメール通知したいとしましょう。DBスキーマはwritersに対してarticlesが1:Nの関係とします。

```php:ArticlesController.php
<?php
namespace App\Controller;

use App\Controller\AppController;
use Cake\Event\Event;
use Cake\ORM\TableRegistry;

/**
 * Articles Controller
 *
 * @property \App\Model\Table\ArticlesTable $Articles
 */
class ArticlesController extends AppController
{

    // ...略

    public function notifyMail()
    {
        $this->Articles
            ->find()
            ->contain(['Writers'])
            ->all()
            ->each(function ($article) {
                $this->sendEmail($article->writer->email);
            });
    }

    private function sendEmail()
    {
        // ...略
    }
}
```

こんな感じです。find()した結果はそのまま変数に詰め込むことなく、each()で処理するために使い捨てる感覚。必要であればuse()を用いて外で定義した変数を持ち込みましょう。

```
            ->each(function ($article) use ($hogehoge) {
```

# map()で特定テーブルの情報をさくっと加工し、手軽に配列構造を作る
テーブルから取得してきたレコード情報をすぐに加工して使いたい時に、foreach()を使わずやれるのでスマート。

```php:WritersController.php
<?php
namespace App\Controller;

use App\Controller\AppController;
use Cake\Event\Event;
use Cake\ORM\TableRegistry;

/**
 * Writers Controller
 *
 * @property \App\Model\Table\WritersTable $Writers
 */
class WritersController extends AppController
{

    // ...略

    public function getBriefs()
    {
        $briefs = $this->Writers
            ->find()
            ->contain(['Articles'])
            ->order(['Writers.id' => 'ASC'])
            ->map(function ($writer) {
                return [
                    'writer_id' => $writer->id,
                    'writer_name' => $writer->name,
                    'article_count' => count($writer->articles)
                ];
            })
            ->toArray();
    }
}
```

```text:pr($briefs);
Array
(
    [0] => Array
        (
            [writer_id] => 1
            [writer_name] => ライターAさん
            [article_count] => 5
        )
    [1] => Array
        (
            [writer_id] => 2
            [writer_name] => ライターBさん
            [article_count] => 100
        )
    )
    [2] => Array
        (
            [writer_id] => 3
            [writer_name] => ライターCさん
            [article_count] => 23
        )
    )
)
```

# extract()で配列構造の設定ファイルから特定のkeyのvalueだけを一気に拾う
今回は設定ファイルをCollectionオブジェクトに変換できるよう、ArticlesControllerにてuseで予めCollectionクラスの使用を宣言しておきます。

```php:article_settings.php
<?php
return [
    'article_settings' => [        
        [
            'id' => 1001, // 記事ID
            'display_ads' => true // 記事内に広告を表示
        ],
        [
            'id' => 1002,
            'display_ads' => false
        ],
    ]
];
```

```php:ArticlesController.php
<?php
namespace App\Controller;

use App\Controller\AppController;
use Cake\Event\Event;
use Cake\ORM\TableRegistry;
use Cake\Collection\Collection; // ここにuseを追加

/**
 * Articles Controller
 *
 * @property \App\Model\Table\ArticlesTable $Articles
 */
class ArticlesController extends AppController
{

    public function beforeFilter(Event $event)
    {
        parent::beforeFilter($event);
        Configure::load('article_settings', 'default'); // 設定ファイルをload
    }

    public function getSettingArticleIds()
    {
        $settings   = Configure::read('article_settings'); // 設定ファイル読み込み
        $articleIds = (new Collection($settings))
            ->extract('id')
            ->toList();
    }
}
```

```text:pr($articleIds);
Array
(
    [0] => 1001
    [1] => 1002
)
```

# 最後に
上記で紹介した以外にもCollectionには便利なメソッドが沢山あります。興味がある方はぜひドキュメントを眺めてみてください。Collectionを活用して開発をもっと楽にしていきましょう

https://book.cakephp.org/3/ja/core-libraries/collections.html