*/ class Collection implements CollectionInterface, IteratorAggregate, Countable { /** * Create new collection object. * * @param array $items */ public function __construct(protected array $items = []) { // } /** * Static constructor. * * @param array $items * @return self */ public static function create(array $items = []): self { return new self($items); } /** * {@inheritdoc} * * @see CollectionInterface::has() */ public function has(int|string $key): bool { return array_key_exists($key, $this->items); } /** * {@inheritdoc} * * @see CollectionInterface::set() */ public function set(int|string $key, mixed $item): self { $this->items[$key] = $item; return $this; } /** * Returns Iterator. * * @return Traversable */ public function getIterator(): Traversable { return new ArrayIterator($this->items); } /** * {@inheritdoc} * * @see CollectionInterface::toArray() */ public function toArray(): array { return $this->items; } /** * Count items in collection. * * @return int<0, max> */ public function count(): int { return count($this->items); } /** * Append new item to collection. * * @return CollectionInterface */ public function push(mixed $item): CollectionInterface { $this->items[] = $item; return $this; } /** * Return first item in collection. */ public function first(): mixed { if (count($this->items) === 0) { return null; } return reset($this->items); } /** * Returns last item in collection. */ public function last(): mixed { if (count($this->items) === 0) { return null; } return end($this->items); } /** * Return item at given position starting at 0. */ public function at(int $key = 0, mixed $default = null): mixed { if ($this->count() === 0) { return $default; } $positions = array_values($this->items); if (!array_key_exists($key, $positions)) { return $default; } return $positions[$key]; } /** * {@inheritdoc} * * @see CollectionInterface::get() */ public function get(int|string $query, mixed $default = null): mixed { if ($this->count() === 0) { return $default; } if (is_int($query) && array_key_exists($query, $this->items)) { return $this->items[$query]; } if (is_string($query) && !str_contains($query, '.')) { return array_key_exists($query, $this->items) ? $this->items[$query] : $default; } $query = explode('.', (string) $query); $result = $default; $items = $this->items; foreach ($query as $key) { if (!is_array($items) || !array_key_exists($key, $items)) { $result = $default; break; } $result = $items[$key]; $items = $result; } return $result; } /** * {@inheritdoc} * * @see CollectionInterface::map() */ public function map(callable $callback): self { return new self( array_map( fn(mixed $item) => $callback($item), $this->items, ) ); } /** * {@inheritdoc} * * @see CollectionInterface::map() */ public function filter(callable $callback): self { return new self( array_filter( $this->items, fn(mixed $item) => $callback($item), ) ); } /** * {@inheritdoc} * * @see CollectionInterface::clear() */ public function clear(): CollectionInterface { $this->items = []; return $this; } /** * {@inheritdoc} * * @see CollectionInterface::slice() */ public function slice(int $offset, ?int $length = null): CollectionInterface { $this->items = array_slice($this->items, $offset, $length); return $this; } }