phpでファーストクラスコレクション

# phpでファーストクラスコレクションを使ってみる

## ファーストクラスコレクション is 何

クラスオブジェクトを独自クラスでコレクション化したもの

## メリット

* オブジェクトの配列を扱うコードが簡単に書けるようになる
* 配列の子要素をオブジェクトで簡単に書ける
* オブジェクト配列に対する処理（件数やフィルター）をコレクション側に定義できる
 * 例えば「配列を渡して件数カウントする処理」や「特定の条件に一致するものだけ取得する」ような処理を利用者側が作らなくてよくなる

## よくある配列だらけのソース

```php

//DBからなにがしかの一覧を取得
$saleList = Db::getSaleList();

$dataCnt = 0;
$over100List = [];
foreach ( $saleList as $saleData) {
    $dataCnt++;
    if ( $saleData['amount'] >= 100 ) {
       $over100List[] = $saleData;
    }
}

var_dump($dataCnt); //データの個数
var_dump(count($over100List)); //¥100以上のデータの数
```

### ダメなポイント

* $saleData['amount']を何度も使いまわした後に、キー名をvalueに変えたい！みたいな時に影響範囲が広がる
* データが無い時とかにkey undefinedとかなりがち。

## コレクション化してみる
```php

  class ESaleData { //DBの1レコードに対するエンティティクラス
     public $id = null;
     public $amount = null;

     public function __construct($id, $amount) {
         $this->id = $id;
         $this->amount = $amount;
     }
  }

  /**
  * @return ArrayIterator|ESaleData []
  */
  class ESaleCollection implements ArrayAccess, IteratorAggregate{ //ESaleDataを配列化したクラス
     private $_items = [];
     public function __construct($items) {
         foreach ( $items as $item ) {
            $this-_items[] = new EsaleData($item['id'], $item['amount']);
         }
     }
     /**
      * @return $this|EsaleData[]
      */
     public function filterOver100() {
        $filteredCollection = clone($this); //元のインスタンスに影響しないようにcloneする
        foreach ( $tmpCollection as $key => $saleData) {
           if ( $saleData->amount < 100 ) {
               $filteredCollection ->offsetUnset($key);
           }
        }
        return $filteredCollection;
     }
     public function offsetUnset($offset) {
        unset($this->_items[$offset]);
     }
     public function count() {
         return count($this-_items);
     }
     //IteratorAggregateやらのIFでの必須メソッドは調べてネ。
  }
```

```php

//DBからなにがしかの一覧を取得
$saleList = Db::getSaleList();
$saleCollection = new ESaleCollection($saleList);

var_dump($saleCollection->count()); //データの個数
var_dump(saleCollection->filterOver100()); //¥100以上のデータの数
```

## 効果
* 利用者側の責務が圧倒的に減る
 * データの個数を求める際にcollectionのcount()を呼ぶだけの隠微化がされる
 * データの条件によるフィルタリング処理も隠微化される
 * データに対する操作がcollection側にまとめられる

## コーディングする上での小ネタ
```php
  /**
  * @return ArrayIterator|ESaleData []
  */
  class ESaleCollection implements ArrayAccess, IteratorAggregate{ //ESaleDataを配列化したクラス
```
このphpDocsが大事。
素直に読むと**"ArrayIteratorもしくはEsaleData[]をリターンしますよ"**だが、
こう書いておくことでIDE側で**"ArrayIteratorでありEsaleData[]である"**と判断させることができる
「undefineだが実際は動く」ソースは保守性が低い。できるだけコーディング中から正しく繋ぐべき。

```php
//DBからなにがしかの一覧を取得
$saleList = Db::getSaleList();
$saleCollection = new ESaleCollection($saleList);
foreach ( $saleCollection as $saleData) {
   var_dump($saleData->amount); //phpDocsが定義されていると、このamountは予測変換が効く
}
```

```php
//DBからなにがしかの一覧を取得
$saleList = Db::getSaleList();
$saleCollection = new ESaleCollection($saleList);
foreach ( $saleCollection as $saleData) {
   /* @var $saleData ESaleData */ //アノテーションする手もあるが本質的ではない
   var_dump($saleData->amount);
}
```

## 使ってみて感想

* 配列のキーの不安感を払拭できる
* Modelとエンティティ側のメソッドが分離してコードが見やすくなる
 * Fatになりがちなデータ編集処理をCollectionに一任できるのが素晴らしい
* ベースとなるDBのアクセスやデータフローの設計を理解していないと危なっかしい
* トータルのソース記述量は増える。心配は減る。

## 参考
[配列の処理をファーストクラスコレクションに組み替えてみる](https://qiita.com/harunbu/items/0ab2b2e179ae63e5aa8a)
[コレクションオブジェクト/ファーストクラスコレクションについて勉強してみた](https://qiita.com/naoto_koyama/items/7d3222a84336e7e3ea9a)