Kolekce jako datovou strukturu můžete znát z různých jazyků, ale v PHP standardně obsažená není. Jedná se de facto o pole na steroidech, které mají výhodu například v tom, že mohou mít omezený typ hodnot.
Existují hotové knihovny například od Laravelu, ale abyste mohli použít čistě jen kolekce, potřebujete celou knihovnu illuminate/support s dalšími závislostmi, případně můžete použít neoficiální tightenco/collect, která má pouze onu část s kolekcemi. Existují samozřejmě i další knihovny, které kolekce implementují, ale mají spoustu metod, které nepotřebuji, proto jsem si vytvořil malou vlastní.
Implementuji tyto interfacy:
- IteratorAggregate, díky kterému lze kolekce procházet jako pole,
- Countable, díky čemuž lze počet položek v kolekci zjistit funkcí count(),
- a JsonSerializable, díky kterému se nad kolekcí dá použít json_encode(), pokud to položky v ní umožňují.
Pro mé účely dále postačí, když bude kolekce podporovat přidání, získání a smazání položky a potom mapování, řazení a filtrování. Časem případně přidám další metody, bude-li potřeba.
Výsledkem je knihovna gephart/collections.
<?php
namespace Gephart\Collections;
use Gephart\Collections\Exception\InvalidTypeException;
class Collection implements \IteratorAggregate, \Countable, \JsonSerializable
{
protected $list = [], $type;
public function __construct(string $type = null)
{
$this->type = $type;
}
public function collect(array $items)
{
foreach ($items as $item) {
$this->add($item);
}
return $this;
}
public function add($item)
{
if ($this->type && !$item instanceof $this->type) {
throw new InvalidTypeException("Item to add to collection must be type of " . $this->type);
}
$this->list[] = $item;
return $this;
}
public function get(int $index)
{
return $this->list[$index];
}
public function all(): array
{
return $this->list;
}
public function remove($index)
{
unset($this->list[$index]);
return $this;
}
public function count()
{
return count($this->list);
}
public function jsonSerialize()
{
return json_encode($this->list);
}
public function getIterator()
{
return new \ArrayIterator($this->list);
}
public function map(callable $callback)
{
$list = array_map($callback, $this->list);
return (new static($this->type))->collect($list);
}
public function filter(callable $callback)
{
$list = array_filter($this->list, $callback);
return (new static($this->type))->collect($list);
}
public function sort(callable $callback)
{
$list = $this->list;
usort($list, $callback);
return (new static($this->type))->collect($list);
}
public function each(callable $callback)
{
foreach ($this->list as $key => $item) {
if (!$callback($item, $key)) {
break;
}
}
return $this;
}
}
Použití
Kolekce bez specifického typu mohou obsahovat jakoukoli položku:
$collection = new Gephart\Collections\Collection();
$collection->add("Something"); // Index - 0
$collection->add(123); // Index - 1
$item = $collection->get(1); // 123
$collection->remove(1); // Delete item with index 1
$items = $collection->all(); // [0 => "Something"];
Kolekce se specifickým typem lze použít pouze pro ten daný typ, jinak dojde k vyhození vyjímky:
class SpecificEntity { public $text = ""; }
$item1 = new SpecificEntity();
$item1->text = "first";
$item2 = new SpecificEntity();
$item2->text = "second";
$collection = new Gephart\Collections\Collection(SpecificEntity::class);
$collection->add($item1);
$collection->add($item2);
// Or use method collect(): $collection->collect([$item1, $item2]);
$item = $collection->get(1);
echo $item->text; // "second"
Celý kód na Githubu: github.com/gephart/collections.
Do Gephart kolekce plánuji přidat od verze 0.5, čeká mě ještě přepsání několika komponent, aby tam, kde to dává smysl, kolekce využíval.