*/ protected array $items; /** * @param array $items */ public function __construct(array $items) { $this->items = $items; } /** * Cast collection to an array. * * @return array */ public function toArray(): array { return $this->items; } /** * Apply a callback to each item in the collection. * * @param callable(TValue,TKey):void $callback * @return self */ public function each(callable $callback): self { foreach ($this->items as $key => $item) { $callback($item, $key); } return $this; } /** * Filter the collection using a callback. * * Filter callback should return true for values to keep. * * @param callable(TValue,TKey):bool $callback * @return self */ public function filter(callable $callback): self { $filtered = array_filter($this->items, $callback, ARRAY_FILTER_USE_BOTH); return new self($filtered); } /** * Filter the collection using a callback; reject any items matching the callback. * * Filter callback should return true for values to reject. * * @param callable(TValue,TKey):bool $callback * @return self */ public function reject(callable $callback): self { /** @psalm-suppress MixedArgument Psalm is not able to infer the type from the static callable (yet). */ $filtered = array_filter( $this->items, static fn ($value, $key) => ! $callback($value, $key), ARRAY_FILTER_USE_BOTH ); return new self($filtered); } /** * Transform each value in the collection. * * Callback should return the new value to use. * * @template TNewValue * @psalm-param callable(TValue,TKey):TNewValue $callback * @psalm-return self */ public function map(callable $callback): self { $mapped = []; foreach ($this->items as $key => $value) { $mapped[$key] = $callback($value, $key); } return new self($mapped); } /** * Return a new collection containing only unique items. * * @return self * @psalm-immutable */ public function unique(): self { return new self(array_unique($this->items)); } /** * @param TKey $offset */ public function has($offset): bool { return array_key_exists($offset, $this->items); } /** * @param TKey $offset * @return TValue * @throws OutOfRangeException */ public function get($offset) { if (! $this->has($offset)) { throw new OutOfRangeException(sprintf( 'Offset %s does not exist in the collection', $offset )); } return $this->items[$offset]; } /** * @param TKey $offset * @param TValue $value */ public function set($offset, $value): void { $this->items[$offset] = $value; } /** * @param TKey $offset */ public function remove($offset): void { if ($this->has($offset)) { unset($this->items[$offset]); } } /** * Countable: number of items in the collection. */ public function count(): int { return count($this->items); } public function isEmpty(): bool { return $this->items === []; } /** * Traversable: Iterate the collection. * * @return ArrayIterator */ public function getIterator(): ArrayIterator { return new ArrayIterator($this->items); } /** * @param callable(TValue,TKey):bool $callback */ public function anySatisfies(callable $callback): bool { foreach ($this->items as $index => $item) { if ($callback($item, $index)) { return true; } } return false; } /** * @return self */ public function toOrderedCollection(): self { return new self(array_values($this->items)); } /** * @param self $collection * @return self */ public function merge(Collection $collection): self { $this->items = array_merge($this->items, $collection->toArray()); return $this; } /** * @param TValue $value * @return TKey * @throws OutOfRangeException */ public function getKey($value) { $key = array_search($value, $this->items, true); if ($key === false) { throw new OutOfRangeException(sprintf( 'Value %s does not exist in the collection', is_scalar($value) ? (string) $value : gettype($value) )); } return $key; } }