diff --git a/composer.json b/composer.json index 7e46304..2cd9539 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { - "name": "jrosset/arrayclasses", - "description": "Classes for array embedding", + "name": "jrosset/collections", + "description": "Classes for collections", "keywords": [ ], "minimum-stability": "stable", @@ -15,7 +15,7 @@ }, "readme": "README.md", - "homepage": "https://git.jrosset.ovh/jrosset/PhpArrayClass", + "homepage": "https://git.jrosset.ovh/jrosset/PhpCollections", "license": "CC-BY-4.0", "authors": [ { @@ -25,9 +25,9 @@ ], "support": { "email": "jul.rosset@gmail.com", - "issues": "https://git.jrosset.ovh/jrosset/PhpArrayClass/issues", - "wiki": "https://git.jrosset.ovh/jrosset/PhpArrayClass/wiki", - "docs": "https://git.jrosset.ovh/jrosset/PhpArrayClass/wiki", - "source": "https://git.jrosset.ovh/jrosset/PhpArrayClass" + "issues": "https://git.jrosset.ovh/jrosset/PhpCollections/issues", + "wiki": "https://git.jrosset.ovh/jrosset/PhpCollections/wiki", + "docs": "https://git.jrosset.ovh/jrosset/PhpCollections/wiki", + "source": "https://git.jrosset.ovh/jrosset/PhpCollections" } } \ No newline at end of file diff --git a/src/ArrayClasses/ArrayClass.php b/src/ArrayClasses/ArrayClass.php deleted file mode 100644 index 88b3ef5..0000000 --- a/src/ArrayClasses/ArrayClass.php +++ /dev/null @@ -1,17 +0,0 @@ -array, $this->throwsForNonExistentElement()); - } -} \ No newline at end of file diff --git a/src/ArrayClasses/IArrayClass.php b/src/ArrayClasses/IArrayClass.php deleted file mode 100644 index d38b7a4..0000000 --- a/src/ArrayClasses/IArrayClass.php +++ /dev/null @@ -1,44 +0,0 @@ -array, $this->throwsForNonExistentElement()); - } -} \ No newline at end of file diff --git a/src/ArrayClasses/ImmutableException.php b/src/ArrayClasses/ImmutableException.php deleted file mode 100644 index 8f7dfb9..0000000 --- a/src/ArrayClasses/ImmutableException.php +++ /dev/null @@ -1,11 +0,0 @@ -__construct__TImmutableInternalArray($initial, $throwsForNonExistentElement); - - $arrayNew = []; - foreach ($this->array as $key => $value) { - $arrayNew[mb_strtolower($key)] = $value; - } - $this->array = $arrayNew; - } - - /** - * @inheritDoc - */ - public function has ($cellName): bool { - return $this->has__TImmutableInternalArray(mb_strtolower($cellName)); - } - /** - * @inheritDoc - */ - public function get ($cellName, ?bool $throwsForNonExistentElement = null) { - return $this->get__TImmutableInternalArray(mb_strtolower($cellName), $throwsForNonExistentElement); - } - - /** - * @inheritDoc - */ - public function toMutable (): IArrayClass { - return new InsensitiveCaseArrayClass($this->array, $this->throwsForNonExistentElement()); - } -} \ No newline at end of file diff --git a/src/ArrayClasses/InsensitiveCaseArrayClass.php b/src/ArrayClasses/InsensitiveCaseArrayClass.php deleted file mode 100644 index 2978ad5..0000000 --- a/src/ArrayClasses/InsensitiveCaseArrayClass.php +++ /dev/null @@ -1,33 +0,0 @@ -set__TInternalArray(mb_strtolower($cellName), $cellValue); - } - /** - * @inheritDoc - */ - public function del ($cellName, ?bool $throwsForNonExistentElement = null): self { - return $this->del__TInternalArray(mb_strtolower($cellName), $throwsForNonExistentElement); - } - - /** - * @inheritDoc - */ - public function toImmutable (): IImmutableArrayClass { - return new ImmutableInsensitiveCaseArrayClass($this->array, $this->throwsForNonExistentElement()); - } -} \ No newline at end of file diff --git a/src/ArrayClasses/TImmutableInternalArray.php b/src/ArrayClasses/TImmutableInternalArray.php deleted file mode 100644 index d8fbce8..0000000 --- a/src/ArrayClasses/TImmutableInternalArray.php +++ /dev/null @@ -1,159 +0,0 @@ -setThrowsForNonExistentElement($throwsForNonExistentElement); - - if (is_array($initial)) { - $this->array = $initial; - } - elseif (is_null($initial)) { - $this->array = []; - } - elseif ($initial instanceof IArrayCast) { - $this->array = $initial->toArray(); - } - else { - throw new InvalidArgumentException('The initial value is not valid: null, array or ' . IArrayCast::class); - } - } - /** - * Set information to dump - * - * @return array The information to dump - * - * @see https://www.php.net/manual/function.var-dump.php - */ - public function __debugInfo (): array { - return [ - 'array' => $this->array, - ]; - } - - /** - * @inheritDoc - */ - public function has ($cellName): bool { - return array_key_exists($cellName, $this->array); - } - /** - * @inheritDoc - */ - public function get ($cellName, ?bool $throwsForNonExistentElement = null) { - if ($this->has($cellName)) { - return $this->array[$cellName]; - } - if ($throwsForNonExistentElement ?? $this->throwsForNonExistentElement()) { - throw new OutOfRangeException(); - } - return null; - } - - /** - * @inheritDoc - */ - public function toArray (): array { - return $this->array; - } - - /** - * @inheritDoc - */ - public function count (): int { - return count($this->array); - } - - /** - * @inheritDoc - */ - public function getIterator (): Traversable { - return new ArrayIterator($this->array); - } - - /** - * @inheritDoc - */ - public function offsetExists ($offset): bool { - return $this->has($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(); - } - - /** - * @inheritDoc - */ - public function serialize (): string { - return serialize($this->array); - } - /** - * @inheritDoc - */ - public function unserialize ($data): void { - $this->array = unserialize($data); - } - - /** - * @inheritDoc - */ - public function jsonSerialize () { - return $this->array; - } - - /** - * Throws an exception for nonexistent elements ? - * - * @return bool Throws an exception for nonexistent elements ? - */ - public function throwsForNonExistentElement (): bool { - return $this->throwsForNonExistentElement; - } - /** - * Set if throws an exception for nonexistent elements - * - * @param bool $throwsForNonExistentElement Throws an exception for nonexistent elements ? - * - * @return $this - */ - public function setThrowsForNonExistentElement (bool $throwsForNonExistentElement): self { - $this->throwsForNonExistentElement = $throwsForNonExistentElement; - return $this; - } -} \ No newline at end of file diff --git a/src/ArrayClasses/TInternalArray.php b/src/ArrayClasses/TInternalArray.php deleted file mode 100644 index f136d31..0000000 --- a/src/ArrayClasses/TInternalArray.php +++ /dev/null @@ -1,45 +0,0 @@ -array[$cellName] = $cellValue; - return $this; - } - /** - * @inheritDoc - */ - public function push ($cellValue): self { - return $this->set($this->count(), $cellValue); - } - /** - * @inheritDoc - */ - public function del ($cellName, ?bool $throwsForNonExistentElement = null): self { - if ($throwsForNonExistentElement ?? $this->throwsForNonExistentElement() && !$this->has($cellName)) { - throw new OutOfRangeException(); - } - unset($this->array[$cellName]); - - return $this; - } - - /** - * @inheritDoc - */ - public function offsetSet ($offset, $value): void { - $this->set($offset, $value); - } - /** - * @inheritDoc - */ - public function offsetUnset ($offset): void { - $this->del($offset); - } -} \ No newline at end of file diff --git a/src/Collections/Collection.php b/src/Collections/Collection.php new file mode 100644 index 0000000..fba5195 --- /dev/null +++ b/src/Collections/Collection.php @@ -0,0 +1,53 @@ + + * @template-implements ICollection + */ +class Collection extends ImmutableCollection implements ICollection { + /** + * @inheritDoc + */ + public function set ($key, $value): self { + return $this->_set($key, $value); + } + /** + * @inheritDoc + */ + public function add ($value): self { + $this->elements[] = $value; + return $this; + } + + /** + * @inheritDoc + */ + public function clear (): self { + $this->_initialize(); + return $this; + } + /** + * @inheritDoc + */ + public function remove ($key): self { + unset($this->elements[$this->_normalizeKey($key)]); + return $this; + } + /** + * @inheritDoc + */ + public function removeValue ($value, bool $strict = false): self { + foreach ($this->elements as $currentKey => $currentValue) { + if (($strict && $value === $currentValue) || (!$strict && $value == $currentValue)) { + unset($this->elements[$currentKey]); + } + } + return $this; + } +} \ No newline at end of file diff --git a/src/ArrayClasses/IArrayCast.php b/src/Collections/IArrayCast.php similarity index 59% rename from src/ArrayClasses/IArrayCast.php rename to src/Collections/IArrayCast.php index 6057ed4..2226353 100644 --- a/src/ArrayClasses/IArrayCast.php +++ b/src/Collections/IArrayCast.php @@ -1,15 +1,19 @@ */ public function toArray (): array; } \ No newline at end of file diff --git a/src/Collections/ICollection.php b/src/Collections/ICollection.php new file mode 100644 index 0000000..b0dff6d --- /dev/null +++ b/src/Collections/ICollection.php @@ -0,0 +1,63 @@ + + */ +interface ICollection extends IImmutableCollection { + /** + * 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 + */ + public function set ($key, $value): self; + /** + * Add a value + * + * @param mixed $value The value + * + * @psalm-param TValue $value + * + * @return $this + */ + public function add ($value): self; + + /** + * Empties the collection + * + * @return $this + */ + public function clear (): self; + /** + * Delete a key + * + * @param array-key $key The key + * + * @psalm-param TKey $key + * + * @return $this + */ + public function remove ($key): self; + /** + * Delete all instances of a value + * + * @param mixed $value The value + * @param bool $strict Strict comparison ? + * + * @psalm-param TValue $value + * + * @return $this + */ + public function removeValue ($value, bool $strict = false): self; +} \ No newline at end of file diff --git a/src/Collections/IImmutableCollection.php b/src/Collections/IImmutableCollection.php new file mode 100644 index 0000000..131963c --- /dev/null +++ b/src/Collections/IImmutableCollection.php @@ -0,0 +1,127 @@ + + * @template-extends ArrayAccess + * @template-extends IArrayCast + */ +interface IImmutableCollection extends IteratorAggregate, JsonSerializable, Serializable, Countable, ArrayAccess, IArrayCast { + /** + * Checks if the collection is empty + * + * @return bool TRUE if the collection is empty, FALSE otherwise + */ + public function empty (): bool; + + /** + * Checks if a key exists + * + * @param array-key $key The key + * + * @psalm-param TKey $key + * + * @return bool TRUE if the key exists, FALSE otherwise + */ + public function exists ($key): bool; + /** + * Checks if contains a value + * + * @param mixed $value The value + * @param bool $strict Strict comparison ? + * + * @psalm-param TValue $value + * + * @return bool TRUE if the value exists, FALSE otherwise + */ + public function contains ($value, bool $strict = false): bool; + + /** + * Get the value of a key or null if not found + * + * @param array-key $key The key + * + * @psalm-param TKey $key + * + * @return mixed The value + * @psalm-return TValue|null + */ + public function get ($key); + /** + * Get the first key of a value or null if not found + * + * @param mixed $value The value + * @param bool $strict Strict comparison ? + * + * @psalm-param TValue $value + * + * @return array-key + * @psalm-return TKey|null + */ + public function key ($value, bool $strict = false); + + /** + * Extract a slice of the collection + * + * Preserves the keys + * + * @param int $offset The start offset + * @param int|null $length The maximum length. Null if until end of the collection + * + * @return static + * @psalm-return static + */ + public function slice (int $offset, ?int $length = null): IImmutableCollection; + + /** + * Get a collection of all elements that satisfy predicate $filter + * + * @param Closure $filter The filtering predicate + * + * @psalm-param Closure(TKey, TValue):bool $filter + * + * @return static The result collection + * @psalm-return static + */ + public function filter (Closure $filter): IImmutableCollection; + /** + * A new collection with $process applied on all elements + * + * @psalm-template TResultValue + * + * @param Closure $process The process function to apply on each element + * + * @psalm-param Closure(TKey, TValue): TResultValue $process + * + * @return static + * @psalm-return static + */ + public function map (Closure $process): IImmutableCollection; + + /** + * The list of all keys + * + * @return static The list of all keys + * @psalm-return static + */ + public function keys (): IImmutableCollection; + /** + * The list of all values + * + * @return static The list of all values + * @psalm-return static + */ + public function values (): IImmutableCollection; +} \ No newline at end of file diff --git a/src/Collections/ImmutableCollection.php b/src/Collections/ImmutableCollection.php new file mode 100644 index 0000000..b58b1da --- /dev/null +++ b/src/Collections/ImmutableCollection.php @@ -0,0 +1,271 @@ + + */ +class ImmutableCollection implements IImmutableCollection { + /** + * @var array The internal array of elements + * @psalm-var array + * + * @internal + */ + protected array $elements; + + /** + * Initialise a new collection + * + * @param array|Traversable|null $other The initial values + * + * @psalm-param array|Traversable|null + * + * @throws InvalidArgumentException If the initial values aren't valid + */ + public function __construct ($other = null) { + $this->_initialize($other); + } + + /** + * Initialise the internals elements + * + * @param array|Traversable|null $other The initial values + * + * @psalm-param array|Traversable|null + * + * @throws InvalidArgumentException If the initial values aren't valid + * @internal + * + */ + protected function _initialize ($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 ($key, $value): self { + $this->elements[$this->_normalizeKey($key)] = $value; + return $this; + } + /** + * Internally add a value + * + * @param mixed $value The value + * + * @psalm-param TValue $value + * + * @return $this + * @internal + * + */ + protected function _add ($value): self { + $this->elements[] = $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 ($key) { + return $key; + } + + /** + * @inheritDoc + */ + public function getIterator () { + 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) { + throw new ImmutableException(); + } + /** + * @inheritDoc + */ + public function offsetUnset ($offset) { + throw new ImmutableException(); + } + + /** + * @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): IImmutableCollection { + $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): IImmutableCollection { + $output = new static(); + foreach ($this->elements as $key => $value) { + if (!$filter($key, $value)) { + continue; + } + $output->_set($key, $value); + } + return $output; + } + /** + * @inheritDoc + */ + public function map (Closure $process): IImmutableCollection { + $output = new static(); + foreach ($this->elements as $key => $value) { + $output->_set($key, $process($key, $value)); + } + return $output; + } + + /** + * @inheritDoc + */ + public function keys (): IImmutableCollection { + return new static(array_keys($this->elements)); + } + /** + * @inheritDoc + */ + public function values (): IImmutableCollection { + return new static(array_values($this->elements)); + } + + /** + * @inheritDoc + * + * @throws JsonException + */ + public function jsonSerialize () { + return json_encode($this->elements, JSON_THROW_ON_ERROR); + } +} \ No newline at end of file diff --git a/src/Collections/ImmutableException.php b/src/Collections/ImmutableException.php new file mode 100644 index 0000000..a517bf7 --- /dev/null +++ b/src/Collections/ImmutableException.php @@ -0,0 +1,11 @@ + + */ +class InsensitiveCaseKeyCollection extends Collection { + use TInsensitiveCaseKey; +} \ No newline at end of file diff --git a/src/Collections/InsensitiveCaseKeyImmutableCollection.php b/src/Collections/InsensitiveCaseKeyImmutableCollection.php new file mode 100644 index 0000000..166b202 --- /dev/null +++ b/src/Collections/InsensitiveCaseKeyImmutableCollection.php @@ -0,0 +1,14 @@ + + */ +class InsensitiveCaseKeyImmutableCollection extends ImmutableCollection implements IImmutableCollection { + use TInsensitiveCaseKey; +} \ No newline at end of file diff --git a/src/Collections/TInsensitiveCaseKey.php b/src/Collections/TInsensitiveCaseKey.php new file mode 100644 index 0000000..d2d4757 --- /dev/null +++ b/src/Collections/TInsensitiveCaseKey.php @@ -0,0 +1,19 @@ + 'bar', + 'foo' => 'foobar', + ] +); + +echo 'Size : ' . $readOnlyCollection->count() . PHP_EOL; +echo 'Foo = ' . $readOnlyCollection->get('Foo') . PHP_EOL; +echo '-----' . PHP_EOL; + +$collection = new InsensitiveCaseKeyCollection($readOnlyCollection); +$collection->add(28); +echo 'Size : ' . $collection->count() . PHP_EOL; \ No newline at end of file diff --git a/tests/test_class.php b/tests/test_class.php new file mode 100644 index 0000000..15fc6d3 --- /dev/null +++ b/tests/test_class.php @@ -0,0 +1,37 @@ +value = $value; + $this->unit = $unit; + } + + public function asString (): string { + return $this->value . $this->unit; + } +} + +/** + * @return Collection + */ +function getMeasures (): Collection { + $measures = new Collection(); + $measures->add(new Measure(38)); + $measures->add(new Measure(27)); + $measures->add(new Measure(34)); + + return $measures; +} + +echo 'Measures :' . PHP_EOL; +foreach (getMeasures() as $no => $measure) { + echo "\t#" . $no . ' = ' . $measure->asString() . PHP_EOL; +} \ No newline at end of file