PHP: Kolekce

POZOR! Článek jsem napsal před více jak rokem, a tudíž už nemusí reflektovat můj nynější názor nebo může být zastaralý.

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.

Znáte někoho, komu by článek mohl pomoct? Zasdílejte mu ho :)

Komentáře