NEM LEHETSÉGES
Mégis kiválóan használható!
Tételezzük fel, hogy van egy ilyesmi interface-ünk:
<?php
namespace System\Collections;
interface IEnumerable {
function Avg($callback = NULL);
function Count($callback = NULL);
function Contains($item, $comparer = NULL);
function FirstOrDefault($callback = NULL);
function GroupBy(\Closure $callback);
function OrderBy(\Closure $callback);
function Sum($callback = NULL);
function ToArray();
function ToList();
function ToLookup(\Closure $keyMaker);
function Where(\Closure $callback);
function Any($callback = NULL);
function Union($enumerable);
function UnionAll($enumerable);
function Take($take);
function SelectMany(\Closure $func);
function Zip($other, \Closure $func);
}
Interface-nek nem kicsi, sőt, az igaziban ennél több is van, .NET programozók már észlelhetik, miről van szó.
Mi a közös ezekben a metódusokban, ha implementáljuk őket? Egyiknek sincs szüksége másra, csupán egy bejárható kollekcióra, valamire, amit végig iterálva döntésre juthatnak. PHP van egy IteratorAggregate interface, ami egyetlen metódussal rendelkezik, az is a dokumentáció szerint csak egy Traversable típusú akármit ad vissza, de tételezzük fel, hogy mindenki tud olvasni, és a metódus neve alapján egy Iterator típust ad vissza minden ilyen elem.
Ami a nagy ötlet, hogy az interface (IEnumerable) megvalósíthatjuk egyetlen trait felhasználásával, nyilván, a kódba nem írhatjuk bele, hogy implements \IteratorAggregate, mert teljesen értelmetlen lenne, ezért tilva is van, de rakhatunk bele valami ilyesmit:
trait TEnumerable {
/* ... */
private function _getSource() {
if (!($this instanceof \IteratorAggregate)) {
throw new \Exception("TEnumerable user class must implement \\IteratorAggregate");
}
return $this->getIterator();
}
}
Semmi extra, csak azt írjuk elő, hogy a trait-et használó osztály implementálja már az IteratorAggregate interface-t, mert arra támaszkodunk mindvégig. Az összes IEnumerable interface által előírt metódust tudjuk implementálni a trait-ben a getSource() meghívásával legyen az a Single() vagy épp a SelectMany().
Amit nyerünk vele, az az, hogy az összes különlegesebb osztály pl. egy ObjectGroup, aminek egy szekvenciáját egy ObjectGroupContainer adja GroupBy() után implementálhatja gyorsan az IEnumerable-t a trait felhasználásával és az IteratorAggregate implementálásával.
<?php
namespace System\Collections;
class ObjectGroupContainer implements \IteratorAggregate, IEnumerable {
use TEnumerable;
protected $_container;
public function __construct(\SplFixedArray $source) {
$this->_container = $source;
}
public function Having(\Closure $callback) {
$callback = function (ObjectGroup $og) use ($callback) {
return $callback($og);
};
$tmp = array();
foreach ($this->_container as $value) {
/* @var $value ObjectGroup */
if ($callback($value)) {
$tmp[] = $value;
}
}
return new ObjectGroupContainer(\SplFixedArray::fromArray($tmp));
}
public function getIterator() {
return $this->_container;
}
}
Látható, hogy nem kellett összetörnünk magunkat az IEnumerable működés elérése miatt, szemfülesek észrevehetik, hogy a Having kódját érdemes volna valami értelmesebb osztállyal megvalósítani, de majd egyszer.
Ami nagyon klasz, hogy a trait-ben implementált metódusokat felülírhatjuk, így pl. létrehozhatunk egy WhereEnumerable : IteratorAggregate, IEnumerable típus, amiben készíthetünk olyan Where metódusimplementációt, ami egy deferred execution-t lehetővé tevő Closure-rel és más típusokkal operál, így pl. 50 egymás utána Where() hívás lemegy egyetlen iterációval. Hasonlót lehet elérni Skip-Then esetén, de ugyanez játszik az OrderBy-ThenBy esetében is.
Nagyon király.