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.
		
		
		
		
		
			
		
			
				
	
	
		
			330 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			PHP
		
	
			
		
		
	
	
			330 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			PHP
		
	
| <?php
 | |
| 
 | |
| namespace jrosset\Collections;
 | |
| 
 | |
| use ArrayIterator;
 | |
| use Closure;
 | |
| use InvalidArgumentException;
 | |
| use Traversable;
 | |
| 
 | |
| /**
 | |
|  * An immutable collection
 | |
|  *
 | |
|  * @psalm-template     TKey of array-key
 | |
|  * @template-covariant TValue
 | |
|  * @template-implements IImmutableCollection<TKey, TValue>
 | |
|  */
 | |
| class ImmutableCollection implements IImmutableCollection {
 | |
|     /**
 | |
|      * @var array The internal array of elements
 | |
|      * @psalm-var array<TKey, TValue>
 | |
|      *
 | |
|      * @internal
 | |
|      */
 | |
|     protected array $elements;
 | |
| 
 | |
|     /**
 | |
|      * Initialise a new collection
 | |
|      *
 | |
|      * @param iterable|null                                            $other The initial values
 | |
|      *
 | |
|      * @psalm-param array<TKey, TValue>|Traversable<TKey, TValue>|null $other
 | |
|      *
 | |
|      * @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
 | |
|      *
 | |
|      * @psalm-param array<TKey, TValue>|Traversable<TKey, TValue>|null $other
 | |
|      *
 | |
|      * @throws InvalidArgumentException If the initial values aren't valid
 | |
|      * @internal
 | |
|      */
 | |
|     protected function _initialize (?iterable $other = null) {
 | |
|         $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 array-key    $key   The key
 | |
|      * @param mixed        $value The value
 | |
|      *
 | |
|      * @psalm-param TKey   $key
 | |
|      * @psalm-param TValue $value
 | |
|      *
 | |
|      * @return $this
 | |
|      * @internal
 | |
|      *
 | |
|      */
 | |
|     protected function _set (int|string $key, mixed $value): self {
 | |
|         $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
 | |
|      */
 | |
|     protected function _merge (IImmutableCollection ...$collections): self {
 | |
|         foreach ($collections as $collection) {
 | |
|             foreach ($collection as $key => $value) {
 | |
|                 $this->_set($key, $value);
 | |
|             }
 | |
|         }
 | |
|         return $this;
 | |
|     }
 | |
|     /**
 | |
|      * Internally add a value
 | |
|      *
 | |
|      * @param mixed        ...$values The value
 | |
|      *
 | |
|      * @psalm-param TValue ...$value
 | |
|      *
 | |
|      * @return $this
 | |
|      * @internal
 | |
|      *
 | |
|      */
 | |
|     protected function _add (mixed ...$values): self {
 | |
|         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 mixed
 | |
|      */
 | |
|     protected function _addCollection (IImmutableCollection ...$collections): self {
 | |
|         foreach ($collections as $collection) {
 | |
|             foreach ($collection as $value) {
 | |
|                 $this->_add($value);
 | |
|             }
 | |
|         }
 | |
|         return $this;
 | |
|     }
 | |
|     /**
 | |
|      * Normalize a key
 | |
|      *
 | |
|      * @param array-key  $key The key to normalize
 | |
|      *
 | |
|      * @psalm-param TKey $key
 | |
|      *
 | |
|      * @return array-key The normalized key
 | |
|      * @psalm-return TKey
 | |
|      */
 | |
|     protected function _normalizeKey (int|string $key): int|string {
 | |
|         return $key;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @inheritDoc
 | |
|      */
 | |
|     public function getIterator (): ArrayIterator {
 | |
|         return new ArrayIterator($this->elements);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @inheritDoc
 | |
|      */
 | |
|     public function offsetExists (mixed $offset): bool {
 | |
|         return $this->exists($offset);
 | |
|     }
 | |
|     /**
 | |
|      * @inheritDoc
 | |
|      */
 | |
|     public function offsetGet (mixed $offset): mixed {
 | |
|         return $this->get($offset);
 | |
|     }
 | |
|     /**
 | |
|      * @inheritDoc
 | |
|      */
 | |
|     public function offsetSet (mixed $offset, mixed $value): void {
 | |
|         throw new ImmutableException();
 | |
|     }
 | |
|     /**
 | |
|      * @inheritDoc
 | |
|      */
 | |
|     public function offsetUnset (mixed $offset): void {
 | |
|         throw new ImmutableException();
 | |
|     }
 | |
| 
 | |
|     public function __serialize (): array {
 | |
|         return [
 | |
|             'elements' => $this->elements,
 | |
|         ];
 | |
|     }
 | |
|     public function __unserialize (array $data): void {
 | |
|         $this->_initialize($data['elements']);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @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 (int|string $key): bool {
 | |
|         return isset($this->elements[$this->_normalizeKey($key)]);
 | |
|     }
 | |
|     /**
 | |
|      * @inheritDoc
 | |
|      */
 | |
|     public function contains (mixed $value, bool $strict = false): bool {
 | |
|         return in_array($value, $this->elements, $strict);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @inheritDoc
 | |
|      */
 | |
|     public function get (int|string $key): mixed {
 | |
|         return $this->elements[$this->_normalizeKey($key)] ?? null;
 | |
|     }
 | |
|     /**
 | |
|      * @inheritDoc
 | |
|      */
 | |
|     public function key (mixed $value, bool $strict = false): int|string|null {
 | |
|         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): self {
 | |
|         $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): self {
 | |
|         $output = new static();
 | |
|         foreach ($this->elements as $key => $value) {
 | |
|             if (!$filter($key, $value)) {
 | |
|                 continue;
 | |
|             }
 | |
|             $output->_set($key, $value);
 | |
|         }
 | |
|         return $output;
 | |
|     }
 | |
|     /**
 | |
|      * @inheritDoc
 | |
|      */
 | |
|     public function withoutEmpties (): self {
 | |
|         return $this->filter(function ($value) {
 | |
|             return empty($value);
 | |
|         });
 | |
|     }
 | |
|     /**
 | |
|      * @inheritDoc
 | |
|      */
 | |
|     public function removeEmpties (): self {
 | |
|         $this->_initialize($this->withoutEmpties());
 | |
|         return $this;
 | |
|     }
 | |
|     /**
 | |
|      * @inheritDoc
 | |
|      */
 | |
|     public function map (Closure $process): self {
 | |
|         $output = new static();
 | |
|         foreach ($this->elements as $key => $value) {
 | |
|             $output->_set($key, $process($key, $value));
 | |
|         }
 | |
|         return $output;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @inheritDoc
 | |
|      */
 | |
|     public function keys (): self {
 | |
|         return new static(array_keys($this->elements));
 | |
|     }
 | |
|     /**
 | |
|      * @inheritDoc
 | |
|      */
 | |
|     public function values (): self {
 | |
|         return new static(array_values($this->elements));
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @inheritDoc
 | |
|      */
 | |
|     public function jsonSerialize (): mixed {
 | |
|         return $this->elements;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @inheritDoc
 | |
|      */
 | |
|     public function join (string $separator): string {
 | |
|         return implode($separator, $this->toArray());
 | |
|     }
 | |
|     /**
 | |
|      * @inheritDoc
 | |
|      */
 | |
|     public static function split (string $value, string $separator): self {
 | |
|         return new static (explode($separator, $value));
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @inheritDoc
 | |
|      */
 | |
|     public function combineWith (IImmutableCollection ...$collections): IImmutableCollection {
 | |
|         return (clone $this)->_merge(...$collections);
 | |
|     }
 | |
| } |