【自分用のメモ】
POPO(Plain Old PHP Object)でそれっぽいものを作ってみたいな〜と思っていたところ、IteratorAggeregateを使えば簡単にできるよ!と教えていただいたので、下記の記事を参考にそれっぽいものを作ってみました。
参考:Iteratorをimplementsする奴は情弱。IteratorAggregateを使え
基底クラス
use ArrayIterator;
use InvalidArgumentException;
use IteratorAggregate;
use Traversable;
/**
* Foundation class for first class collection
* @template T
*/
abstract class Collection implements IteratorAggregate
{
/**
* @var array<T>
*/
public readonly array $items;
/**
* @param array<T> $items
*/
public function __construct(array $items)
{
$this->items = $this->validate($items);
}
/**
* Validate items before setting it to instance variable
*
* @param array<T> $items
* @return array<T> $items
*/
private function validate(array $items)
{
$rules = $this->collectionRules();
// if no set rules, return all items as validated
if (count($rules) <= 0) {
return $items;
}
// apply rules
foreach($rules as $key => $rule) {
if (!$rule($items)) {
throw new InvalidArgumentException("Rule $key violated");
}
}
return $items;
}
/**
* returns sets of rules to be applied to values given
*
* @return array<string,Callable>
*/
abstract protected function collectionRules(): array;
/**
* @inheritDoc
*/
public function getIterator(): Traversable
{
return new ArrayIterator($this->items);
}
}
上記の基底クラスを利用したサンプル
use Collection;
class SampleCollection extends Collection
{
private const MIN = 1;
private const MAX = 4;
/**
* @inheritDoc
*/
protected function collectionRules(): array
{
return [
'is_valid_class' => function(array $items) {
foreach($items as $item) {
if ($item instanceof Sample) {
continue;
}
return false;
}
return true;
},
'is_enough' => function(array $items) {
return count($items) >= self::MIN;
},
'is_not_too_many' => function(array $items) {
return count($items) <= self::MAX;
}
];
}
}
こんな感じかな?という雰囲気はなんとなくつかめたかな...?
実際に使うとなるとおそらくCollectionに対するいろんな操作がクラスに集まってきそうな気もするので、その場合は生成時のルールは別オブジェクトに切り出したい気もする?
こんな感じで切り出してあげればルールだけでユニットテストが書けそうだな?
class SampleCollectionRule
{
private const MIN = 1;
private const MAX = 4;
public function __invoke(array $items): bool
{
if ($this->isNotValidClass($items)) {
return false;
}
if ($this->isNotEnough($items)) {
return false;
}
if ($this->isTooMany($items)) {
return false;
}
return true;
}
private function isNotValidClass(array $items)
{
foreach($items as $item) {
if ($item instanceof Sample) {
continue;
}
return true;
}
return false;
}
private function isNotEnough(array $items)
{
return count($items) < self::MIN;
}
private function isTooMany(array $items)
{
return count($items) > self::MAX;
}
}
こんな感じで切り出してあげればCollectionの方は実際の操作ロジックをメインにできそう
でもこれだとドメインロジックが別のクラスになってしまう?
Psalmを入れるなどしてPackage Privateな仕組みがないとだめか...?
この辺のバランス感覚が難しいなぁ...