*/ class ImmutableCollection implements IImmutableCollection { /** * @var array The internal array of elements */ protected array $elements; /** * Initialise a new collection * * @param iterable|null $other The initial values * * @throws InvalidArgumentException If the initial values aren't valid */ public function __construct (?iterable $other = null) { $this->_initialize($other); } /** * Initialise the internals elements * * @param iterable|null $other The initial values * * @throws InvalidArgumentException If the initial values aren't valid * * @internal */ protected function _initialize (?iterable $other = null): void { $this->elements = []; if ($other !== null) { if (!is_array($other) && !$other instanceof Traversable) { throw new InvalidArgumentException('The initial values must be an array or a ' . IArrayCast::class . ' instance'); } foreach ($other as $key => $value) { $this->_set($key, $value); } } } /** * Internally set a value to a key * * @param TKey $key The key * @param TValue $value The value * * @return $this * * @internal * * @noinspection PhpMissingReturnTypeInspection */ protected function _set ($key, $value) { $this->elements[$this->_normalizeKey($key)] = $value; return $this; } /** * Internally merge one or multiple collections in the current one * * @param IImmutableCollection ...$collections The collections to merge * * @return $this * * @internal * * @noinspection PhpMissingReturnTypeInspection */ protected function _merge (IImmutableCollection ...$collections) { foreach ($collections as $collection) { foreach ($collection as $key => $value) { $this->_set($key, $value); } } return $this; } /** * Internally add a value * * @param TValue ...$values The value * * @return $this * * @internal * * @noinspection PhpMissingReturnTypeInspection */ protected function _add (...$values) { foreach ($values as $value) { $this->elements[] = $value; } return $this; } /** * Internally add the values of one or multiple collections * * @param IImmutableCollection ...$collections The collections to add * * @return $this * * @noinspection PhpMissingReturnTypeInspection */ protected function _addCollection (IImmutableCollection ...$collections) { foreach ($collections as $collection) { foreach ($collection as $value) { $this->_add($value); } } return $this; } /** * Normalize a key * * @param TKey $key The key to normalize * * @return TKey The normalized key */ protected function _normalizeKey ($key) { return $key; } /** * @inheritDoc */ public function getIterator (): ArrayIterator { return new ArrayIterator($this->elements); } /** * @inheritDoc */ public function offsetExists ($offset): bool { return $this->exists($offset); } /** * @inheritDoc */ public function offsetGet ($offset) { return $this->get($offset); } /** * @inheritDoc */ public function offsetSet ($offset, $value): void { throw new ImmutableException(); } /** * @inheritDoc */ public function offsetUnset ($offset): void { throw new ImmutableException(); } /** * List of properties to {@see https://www.php.net/manual/function.serialize.php serialize} * * @return array[] List of properties to {@see https://www.php.net/manual/function.serialize.php serialize} */ public function __serialize (): array { return [ 'elements' => $this->elements, ]; } /** * @inheritDoc */ public function serialize (): ?string { return serialize($this->elements); } /** * @inheritDoc */ public function unserialize ($data) { $this->_initialize(unserialize($data)); } /** * @inheritDoc */ public function toArray (): array { return $this->elements; } /** * @inheritDoc */ public function count (): int { return count($this->elements); } /** * @inheritDoc */ public function empty (): bool { return $this->count() === 0; } /** * @inheritDoc */ public function exists ($key): bool { return isset($this->elements[$this->_normalizeKey($key)]); } /** * @inheritDoc */ public function contains ($value, bool $strict = false): bool { return in_array($value, $this->elements, $strict); } /** * @inheritDoc */ public function get ($key) { return $this->elements[$this->_normalizeKey($key)] ?? null; } /** * @inheritDoc */ public function key ($value, bool $strict = false) { foreach ($this->elements as $currentKey => $currentValue) { if (($strict && $value === $currentValue) || (!$strict && $value == $currentValue)) { return $currentKey; } } return null; } /** * @inheritDoc */ public function slice (int $offset, ?int $length = null) { $output = new static(); $currentIndex = 0; foreach ($this->elements as $key => $value) { if ($currentIndex++ < $offset) { continue; } $output->_set($key, $value); if ($output->count() >= $length) { break; } } return $output; } /** * @inheritDoc */ public function filter (Closure $filter) { $output = new static(); foreach ($this->elements as $key => $value) { if (!$filter($key, $value)) { continue; } $output->_set($key, $value); } return $output; } /** * @inheritDoc */ public function withoutEmpties () { return $this->filter(function ($value) { return empty($value); }); } /** * @inheritDoc */ public function map (Closure $process) { $output = new static(); foreach ($this->elements as $key => $value) { $output->_set($key, $process($key, $value)); } return $output; } /** * @inheritDoc */ public function sort ($sorter = null) { $elements = $this->toArray(); uasort($elements, self::_normalizeSorter($sorter)); return new static($elements); } /** * @inheritDoc */ public function sortByKey ($sorter) { $elements = $this->toArray(); uksort($elements, self::_normalizeSorter($sorter)); return new static($elements); } /** * Normalize a sorter method * * @param null|IComparator|Closure(TValue, TValue):int $sorter The sorting method ; Null if values are object implementing {@see IComparable}. *
Return : *
< 0 if value1 is before value2 *
= 0 if value1 equals value2 *
> 0 if value1 is after value2 * * @return Closure(TValue, TValue):int The normalized sorter method *
Return : *
< 0 if value1 is before value2 *
= 0 if value1 equals value2 *
> 0 if value1 is after value2 */ public static function _normalizeSorter ($sorter): Closure { if ($sorter === null) { return function (IComparable $value1, IComparable $value2): int { return $value1->compareTo($value2); }; } elseif ($sorter instanceof IComparator) { return function ($value1, $value2) use ($sorter): int { return $sorter->compare($value1, $value2); }; } else { return $sorter; } } /** * @inheritDoc */ public function keys (): IImmutableCollection { return new static(array_keys($this->elements)); } /** * @inheritDoc */ public function values () { return new static(array_values($this->elements)); } /** * @inheritDoc */ public function jsonSerialize () { return $this->elements; } /** * @inheritDoc */ public function join (string $separator): string { return implode($separator, $this->toArray()); } /** * @inheritDoc */ public static function split (string $value, string $separator) { return new static (explode($separator, $value)); } /** * @inheritDoc */ public static function fill (int $size, $value) { $keys = new ImmutableCollection(); for ($curr = 0; $curr < $size; $curr++) { $keys->_add($curr); } return static::fillWithKeys($keys, $value); } /** * @inheritDoc */ public static function fillWithKeys (IImmutableCollection $keys, $value) { $collection = new static(); foreach ($keys as $key) { $collection->_set($key, $value); } return $collection; } /** * @inheritDoc */ public function mergeWith (IImmutableCollection ...$collections) { return (clone $this)->_merge(...$collections); } }