You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
PhpCollections/src/Collections/ImmutableCollection.php

406 lines
10 KiB
PHP

<?php
namespace jrosset\Collections;
use ArrayIterator;
use Closure;
use InvalidArgumentException;
use Traversable;
/**
* An immutable collection
*
* @template TKey of array-key
* @template TValue
*
* @implements IImmutableCollection<TKey, TValue>
*/
class ImmutableCollection implements IImmutableCollection {
/**
* @var array<TKey, TValue> 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 <b>values</b> 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}.
* <br>Return :
* <br>&lt; 0 if value1 is before value2
* <br>= 0 if value1 equals value2
* <br>&gt; 0 if value1 is after value2
*
* @return Closure(TValue, TValue):int The normalized sorter method
* <br>Return :
* <br>&lt; 0 if value1 is before value2
* <br>= 0 if value1 equals value2
* <br>&gt; 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);
}
}