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のアクセスやデータフローの設計を理解していないと危なっかしい
- トータルのソース記述量は増える。心配は減る。
参考
配列の処理をファーストクラスコレクションに組み替えてみる
コレクションオブジェクト/ファーストクラスコレクションについて勉強してみた