PHP: Kolekce

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(),
  • 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.

Sdílením článku mi pomůžete a uděláte mi velikou radost :)

Komentáře