LoginSignup
3
7

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-03-27

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

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

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

メリット

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

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


//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とかなりがち。

コレクション化してみる


  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での必須メソッドは調べてネ。
  }

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

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

効果

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

コーディングする上での小ネタ

  /**
  * @return ArrayIterator|ESaleData []
  */
  class ESaleCollection implements ArrayAccess, IteratorAggregate{ //ESaleDataを配列化したクラス

このphpDocsが大事。
素直に読むと"ArrayIteratorもしくはEsaleData[]をリターンしますよ"だが、
こう書いておくことでIDE側で"ArrayIteratorでありEsaleData[]である"と判断させることができる
「undefineだが実際は動く」ソースは保守性が低い。できるだけコーディング中から正しく繋ぐべき。

//DBからなにがしかの一覧を取得
$saleList = Db::getSaleList();
$saleCollection = new ESaleCollection($saleList);
foreach ( $saleCollection as $saleData) {
   var_dump($saleData->amount); //phpDocsが定義されていると、このamountは予測変換が効く
}
//DBからなにがしかの一覧を取得
$saleList = Db::getSaleList();
$saleCollection = new ESaleCollection($saleList);
foreach ( $saleCollection as $saleData) {
   /* @var $saleData ESaleData */ //アノテーションする手もあるが本質的ではない
   var_dump($saleData->amount);
}

使ってみて感想

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

参考

配列の処理をファーストクラスコレクションに組み替えてみる
コレクションオブジェクト/ファーストクラスコレクションについて勉強してみた

3
7
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
7