Compare commits

...

15 Commits
2.x ... master

@ -2,10 +2,20 @@
"name": "jrosset/collections", "name": "jrosset/collections",
"description": "Classes for collections", "description": "Classes for collections",
"keywords": [ ], "keywords": [ ],
"type": "library",
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
},
"minimum-stability": "stable", "minimum-stability": "stable",
"require": { "require": {
"php": "^7.4 || ^8.0" "php": "^8.1"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
@ -30,4 +40,4 @@
"docs": "https://git.jrosset.ovh/jrosset/PhpCollections/wiki", "docs": "https://git.jrosset.ovh/jrosset/PhpCollections/wiki",
"source": "https://git.jrosset.ovh/jrosset/PhpCollections" "source": "https://git.jrosset.ovh/jrosset/PhpCollections"
} }
} }

@ -2,52 +2,55 @@
namespace jrosset\Collections; namespace jrosset\Collections;
use Closure;
/** /**
* A collection * A collection
* *
* @psalm-template TKey of array-key * @template TKey of array-key
* @template-covariant TValue * @template TValue
* @template-extends ImmutableCollection<TKey, TValue> *
* @template-implements ICollection<TKey, TValue> * @implements ImmutableCollection<TKey, TValue>
* @implements ICollection<TKey, TValue>
*/ */
class Collection extends ImmutableCollection implements ICollection { class Collection extends ImmutableCollection implements ICollection {
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function set ($key, $value): self { public function set ($key, $value): static {
return $this->_set($key, $value); return $this->_set($key, $value);
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function merge (IImmutableCollection ...$collections): ICollection { public function merge (IImmutableCollection ...$collections): static {
return $this->_merge(...$collections); return $this->_merge(...$collections);
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function add (...$values): self { public function add (...$values): static {
return $this->_add(...$values); return $this->_add(...$values);
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function addCollection (IImmutableCollection ...$collections): ICollection { public function addCollection (IImmutableCollection ...$collections): static {
return $this->_addCollection(...$collections); return $this->_addCollection(...$collections);
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function prepend (...$values): ICollection { public function prepend (...$values): static {
array_unshift($this->elements, ...$values); array_unshift($this->elements, ...$values);
return $this; return $this;
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function prependCollection (IImmutableCollection ...$collections): ICollection { public function prependCollection (IImmutableCollection ...$collections): static {
$prepend = array_merge( $prepend = array_merge(
...array_map( ...array_map(
function (IImmutableCollection $collection): array { function (IImmutableCollection $collection): array {
@ -62,21 +65,21 @@ class Collection extends ImmutableCollection implements ICollection {
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function clear (): self { public function clear (): static {
$this->_initialize(); $this->_initialize();
return $this; return $this;
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function remove ($key): self { public function remove ($key): static {
unset($this->elements[$this->_normalizeKey($key)]); unset($this->elements[$this->_normalizeKey($key)]);
return $this; return $this;
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function removeValue ($value, bool $strict = false): self { public function removeValue ($value, bool $strict = false): static {
foreach ($this->elements as $currentKey => $currentValue) { foreach ($this->elements as $currentKey => $currentValue) {
if (($strict && $value === $currentValue) || (!$strict && $value == $currentValue)) { if (($strict && $value === $currentValue) || (!$strict && $value == $currentValue)) {
unset($this->elements[$currentKey]); unset($this->elements[$currentKey]);
@ -88,13 +91,86 @@ class Collection extends ImmutableCollection implements ICollection {
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function offsetSet ($offset, $value): void { public function removeFirst () {
$firstKey = $this->firstKey();
$firstValue = $this->get($firstKey);
$this->remove($firstKey);
return $firstValue;
}
/**
* @inheritDoc
*/
public function removeLast () {
$lastKey = $this->lastKey();
$lastValue = $this->get($lastKey);
$this->remove($lastKey);
return $lastValue;
}
/**
* @inheritDoc
*/
public function offsetSet (mixed $offset, mixed $value): void {
$this->set($offset, $value); $this->set($offset, $value);
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function offsetUnset ($offset): void { public function offsetUnset (mixed $offset): void {
$this->remove($offset); $this->remove($offset);
} }
/**
* @inheritDoc
*/
public function sliceSelf (int $offset, ?int $length = null): static {
if ($this->count() === 0) {
$this->elements = [];
}
else {
$this->_checkOffset($offset);
$this->elements = array_slice($this->elements, $offset, $length, true);
}
return $this;
}
/**
* @inheritDoc
*/
public function filterSelf (Closure $filter): static {
$this->elements = array_filter($this->elements, $filter);
return $this;
}
/**
* @inheritDoc
*/
public function removeEmpties (): static {
return $this->filterSelf(function ($value) {
return empty($value);
});
}
/**
* @inheritDoc
*/
public function mapSelf (Closure $process): static {
$this->elements = $this->map($process)->elements;
return $this;
}
/**
* @inheritDoc
*/
public function sortSelf (Closure|IComparator|null $sorter = null): static {
uasort($this->elements, self::_normalizeSorter($sorter));
return $this;
}
/**
* @inheritDoc
*/
public function sortSelfByKey (Closure|IComparator $sorter): static {
uksort($this->elements, self::_normalizeSorter($sorter));
return $this;
}
} }

@ -5,15 +5,14 @@ namespace jrosset\Collections;
/** /**
* Interface for PHP-native array cast * Interface for PHP-native array cast
* *
* @psalm-template TKey of array-key * @template TKey of array-key
* @template-covariant TValue * @template TValue
*/ */
interface IArrayCast { interface IArrayCast {
/** /**
* Transform to a PHP-native array * Transform to a PHP-native array
* *
* @return array The native array * @return array<TKey, TValue> The native array
* @psalm-return array<TKey, TValue>
*/ */
public function toArray (): array; public function toArray (): array;
} }

@ -2,98 +2,176 @@
namespace jrosset\Collections; namespace jrosset\Collections;
use Closure;
use OutOfBoundsException;
use Throwable;
/** /**
* Interface for a collection * Interface for a collection
* *
* @psalm-template TKey of array-key * @template TKey of array-key
* @template-covariant TValue * @template TValue
* @template-extends IImmutableCollection<TKey, TValue> *
* @implements IImmutableCollection<TKey, TValue>
*/ */
interface ICollection extends IImmutableCollection { interface ICollection extends IImmutableCollection {
/** /**
* Set a value to a key * Set a value to a key
* *
* @param array-key $key The key * @param TKey $key The key
* @param mixed $value The value * @param TValue $value The value
*
* @psalm-param TKey $key
* @psalm-param TValue $value
* *
* @return $this * @return $this
*/ */
public function set ($key, $value): self; public function set ($key, $value): static;
/** /**
* Merge one or multiple collections in the current one * Merge one or multiple collections into the current one
* *
* @param IImmutableCollection ...$collections The collections to merge * @param IImmutableCollection<TKey, TValue> ...$collections The collections to merge
* *
* @return $this * @return $this
*/ */
public function merge (IImmutableCollection ...$collections): self; public function merge (IImmutableCollection ...$collections): static;
/** /**
* Add one or multiple values * Add one or multiple values
* *
* @param mixed ...$values The values * @param TValue ...$values The values
*
* @psalm-param TValue ...$values
* *
* @return $this * @return $this
*/ */
public function add (...$values): self; public function add (...$values): static;
/** /**
* Add the <b>values</b> of one or multiple collections * Add the <b>values</b> of one or multiple collections
* *
* @param IImmutableCollection ...$collections The collections to add * @param IImmutableCollection<TKey, TValue> ...$collections The collections to add
* *
* @return mixed * @return $this
*/ */
public function addCollection (IImmutableCollection ...$collections): self; public function addCollection (IImmutableCollection ...$collections): static;
/** /**
* Add one or multiple values at the beginning of the current collection * Add one or multiple values at the beginning of the current collection
* *
* @param mixed ...$values The values * @param TValue ...$values The values
*
* @psalm-param TValue ...$values
* *
* @return $this * @return $this
*/ */
public function prepend (...$values): self; public function prepend (...$values): static;
/** /**
* Add the <b>values</b> of one or multiple collections at the beginning of the current collection * Add the <b>values</b> of one or multiple collections at the beginning of the current collection
* *
* @param IImmutableCollection ...$collections The collections to add * @param IImmutableCollection<TKey, TValue> ...$collections The collections to add
* *
* @return mixed * @return $this
*/ */
public function prependCollection (IImmutableCollection ...$collections): self; public function prependCollection (IImmutableCollection ...$collections): static;
/** /**
* Empties the collection * Empty the collection
* *
* @return $this * @return $this
*/ */
public function clear (): self; public function clear (): static;
/** /**
* Delete a key * Remove a value from it's key
* *
* @param array-key $key The key * @param TKey $key The key
*
* @psalm-param TKey $key
* *
* @return $this * @return $this
*/ */
public function remove ($key): self; public function remove ($key): static;
/** /**
* Delete all instances of a value * Delete all instances of a value
* *
* @param mixed $value The value * @param TValue $value The value
* @param bool $strict Strict comparison ? * @param bool $strict Strict comparison ?
* *
* @psalm-param TValue $value * @return $this
*/
public function removeValue ($value, bool $strict = false): static;
/**
* Get and remove the first element
*
* @return TValue The value
*
* @throws OutOfBoundsException If the collection is empty
*/
public function removeFirst ();
/**
* Get and remove the last element
*
* @return TValue The value
*
* @throws OutOfBoundsException If the collection is empty
*/
public function removeLast ();
/**
* Keep only 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 $this * @return $this
*
* @throws OutOfBoundsException If the offset is not valid
*/
public function sliceSelf (int $offset, ?int $length = null): static;
/**
* Keep only elements that satisfy predicate $filter
*
* @param Closure(TKey, TValue):bool $filter The filtering predicate
*
* @return $this
*/
public function filterSelf (Closure $filter): static;
/**
* Keep only non-empty elements
*
* Use {@see https://www.php.net/manual/function.empty.php empty} to check if element is empty or not
*
* @return $this
*/
public function removeEmpties (): static;
/**
* Applied <b>$process</b> on all elements
*
* @param Closure(TKey, TValue): TValue $process The process function to apply on each element
*
* @return $this
*/
public function mapSelf (Closure $process): static;
/**
* Sort the elements (by value)
*
* @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 $this
*
* @throws Throwable If an error occurs
*/
public function sortSelf (Closure|IComparator|null $sorter = null): static;
/**
* Sort the elements by key
*
* @param IComparator|Closure(TKey, TKey):int $sorter The sorting method. Return :
* <br>&lt; 0 if key1 is before key2
* <br>= 0 if key1 equals key2
* <br>&gt; 0 if key1 is after key2
*
* @return $this
*
* @throws Throwable If an error occurs
*/ */
public function removeValue ($value, bool $strict = false): self; public function sortSelfByKey (Closure|IComparator $sorter): static;
} }

@ -0,0 +1,23 @@
<?php
namespace jrosset\Collections;
/**
* Interface for comparable objects
*
* @template TObject of object
*/
interface IComparable {
/**
* Compare to another instance
*
* @param TObject $other The other instance
*
* @return int <br>&lt; 0 if current instance is classed before the other instance
* <br>= 0 if current instance is classed equals the other instance
* <br>&gt; 0 if current instance is classed after the other instance
*
* @noinspection PhpMissingParamTypeInspection
*/
public function compareTo ($other): int;
}

@ -0,0 +1,24 @@
<?php
namespace jrosset\Collections;
/**
* Interface for classes comparing external objects
*
* @template TObject of object
*/
interface IComparator {
/**
* Compare two objects
*
* @param TObject $object1 The first object
* @param TObject $object2 The second object
*
* @return int <br>&lt; 0 if the first object is classed before the second object
* <br>= 0 if the first object is classed equals the second object
* <br>&gt; 0 if the first object is classed after the second object
*
* @noinspection PhpMissingParamTypeInspection
*/
public function compare ($object1, $object2): int;
}

@ -7,18 +7,20 @@ use Closure;
use Countable; use Countable;
use IteratorAggregate; use IteratorAggregate;
use JsonSerializable; use JsonSerializable;
use Serializable; use OutOfBoundsException;
use Throwable;
/** /**
* Interface for an immutable (read-only) collection * Interface for an immutable (read-only) collection
* *
* @psalm-template TKey of array-key * @template TKey of array-key
* @template-covariant TValue * @template TValue
* @template-extends IteratorAggregate<TKey, TValue> *
* @template-extends ArrayAccess<TKey, TValue> * @implements IteratorAggregate<TKey, TValue>
* @template-extends IArrayCast<TKey, TValue> * @implements ArrayAccess<TKey, TValue>
* @implements IArrayCast<TKey, TValue>
*/ */
interface IImmutableCollection extends IteratorAggregate, JsonSerializable, Serializable, Countable, ArrayAccess, IArrayCast { interface IImmutableCollection extends IteratorAggregate, JsonSerializable, Countable, ArrayAccess, IArrayCast {
/** /**
* Checks if the collection is empty * Checks if the collection is empty
* *
@ -29,9 +31,7 @@ interface IImmutableCollection extends IteratorAggregate, JsonSerializable, Seri
/** /**
* Checks if a key exists * Checks if a key exists
* *
* @param array-key $key The key * @param TKey $key The key
*
* @psalm-param TKey $key
* *
* @return bool TRUE if the key exists, FALSE otherwise * @return bool TRUE if the key exists, FALSE otherwise
*/ */
@ -39,10 +39,8 @@ interface IImmutableCollection extends IteratorAggregate, JsonSerializable, Seri
/** /**
* Checks if contains a value * Checks if contains a value
* *
* @param mixed $value The value * @param TValue $value The value
* @param bool $strict Strict comparison ? * @param bool $strict Strict comparison ?
*
* @psalm-param TValue $value
* *
* @return bool TRUE if the value exists, FALSE otherwise * @return bool TRUE if the value exists, FALSE otherwise
*/ */
@ -51,26 +49,75 @@ interface IImmutableCollection extends IteratorAggregate, JsonSerializable, Seri
/** /**
* Get the value of a key or null if not found * Get the value of a key or null if not found
* *
* @param array-key $key The key * @param TKey $key The key
*
* @psalm-param TKey $key
* *
* @return mixed The value * @return TValue|null The value
* @psalm-return TValue|null
*/ */
public function get ($key); public function get ($key);
/** /**
* Get the first key of a value or null if not found * Get the value at an offset
* *
* @param mixed $value The value * @param int $offset The offset
* @param bool $strict Strict comparison ?
* *
* @psalm-param TValue $value * @return TValue The value
* *
* @return array-key * @throws OutOfBoundsException If the offset is not valid
* @psalm-return TKey|null */
public function getFromOffset (int $offset);
/**
* Get the first key of a value or null if not found
*
* @param TValue $value The value
* @param bool $strict Strict comparison ?
*
* @return TKey|null
*/ */
public function key ($value, bool $strict = false); public function key ($value, bool $strict = false);
/**
* Get the key at an offset
*
* @param int $offset The offset
*
* @return TKey The key
*
* @throws OutOfBoundsException If the offset is not valid
*/
public function keyFromOffset (int $offset);
/**
* Get the value of the first element
*
* @return TValue The value
*
* @throws OutOfBoundsException If the collection is empty
*/
public function first ();
/**
* Get the key of the first element
*
* @return TKey The key
*
* @throws OutOfBoundsException If the collection is empty
*/
public function firstKey ();
/**
* Get the value of the last element
*
* @return TValue The value
*
* @throws OutOfBoundsException If the collection is empty
*/
public function last ();
/**
* Get the key of the last element
*
* @return TKey The key
*
* @throws OutOfBoundsException If the collection is empty
*/
public function lastKey ();
/** /**
* Extract a slice of the collection * Extract a slice of the collection
@ -80,66 +127,79 @@ interface IImmutableCollection extends IteratorAggregate, JsonSerializable, Seri
* @param int $offset The start offset * @param int $offset The start offset
* @param int|null $length The maximum length. Null if until end of the collection * @param int|null $length The maximum length. Null if until end of the collection
* *
* @return static * @return static<TKey, TValue> The result collection
* @psalm-return static<TKey, TValue> *
* @throws OutOfBoundsException If the offset is not valid
*/ */
public function slice (int $offset, ?int $length = null): self; public function slice (int $offset, ?int $length = null): static;
/** /**
* Get a collection of all elements that satisfy predicate $filter * Get a collection of all elements that satisfy predicate $filter
* *
* @param Closure $filter The filtering predicate * @param Closure(TKey, TValue):bool $filter The filtering predicate
*
* @psalm-param Closure(TKey, TValue):bool $filter
* *
* @return static The result collection * @return static<TKey, TValue> The result collection
* @psalm-return static<TKey, TValue>
*/ */
public function filter (Closure $filter): self; public function filter (Closure $filter): static;
/** /**
* Get a collection of all not empty elements * Get a collection of all not empty elements
* *
* Use {@see https://www.php.net/manual/function.empty.php empty} to check if element is empty or not * Use {@see https://www.php.net/manual/function.empty.php empty} to check if element is empty or not
* *
* @return static The result collection * @return static<TKey, TValue> The result collection
*/ */
public function withoutEmpties (): self; public function withoutEmpties (): static;
/** /**
* Remove all empty elements on current collection * A new collection with <b>$process</b> applied on all elements
* *
* Use {@see https://www.php.net/manual/function.empty.php empty} to check if element is empty or not * @template TResultValue
*
* @param Closure(TKey, TValue): TResultValue $process The process function to apply on each element
* *
* @return $this * @return static<TKey, TResultValue> The result collection
*/ */
public function removeEmpties (): self; public function map (Closure $process): static;
/** /**
* A new collection with $process applied on all elements * Get a collection with the elements sorted (by value)
* *
* @psalm-template TResultValue * @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
* *
* @param Closure $process The process function to apply on each element * @return static<TKey, TValue> The result collection
* *
* @psalm-param Closure(TKey, TValue): TResultValue $process * @throws Throwable If an error occurs
*/
public function sort (Closure|IComparator|null $sorter = null): static;
/**
* Get a collection with the elements sorted by key
*
* @param IComparator|Closure(TKey, TKey):int $sorter The sorting method. Return :
* <br>&lt; 0 if key1 is before key2
* <br>= 0 if key1 equals key2
* <br>&gt; 0 if key1 is after key2
*
* @return static<TKey, TValue> The result collection
* *
* @return static * @throws Throwable If an error occurs
* @psalm-return static<TKey, TResultValue>
*/ */
public function map (Closure $process): self; public function sortByKey (Closure|IComparator $sorter): static;
/** /**
* The list of all keys * The list of all keys
* *
* @return static The list of all keys * @return IImmutableCollection<int, TKey> The list of all keys
* @psalm-return static<int, TKey>
*/ */
public function keys (): self; public function keys (): IImmutableCollection;
/** /**
* The list of all values * The list of all values
* *
* @return static The list of all values * @return static<int, TValue> The list of all values
* @psalm-return static<int, TValue>
*/ */
public function values (): self; public function values (): static;
/** /**
* Join all values with a separator * Join all values with a separator
@ -155,16 +215,35 @@ interface IImmutableCollection extends IteratorAggregate, JsonSerializable, Seri
* @param string $value The value to split * @param string $value The value to split
* @param string $separator The split separator * @param string $separator The split separator
* *
* @return static * @return static<int, string> The result collection
*/
public static function split (string $value, string $separator): static;
/**
* Create a collection filled with the same element
*
* @param int $size The collection size
* @param TValue $value The value to fill the collection
*
* @return static<int, TValue> The result collection
*/
public static function fill (int $size, $value): static;
/**
* Create a collection filled with the same element, using a list of keys
*
* @param IImmutableCollection<TKey> $keys The list of keys
* @param TValue $value The value to fill the collection
*
* @return static<TKey, TValue> The result collection
*/ */
public static function split (string $value, string $separator): self; public static function fillWithKeys (IImmutableCollection $keys, $value): static;
/** /**
* Combine the current collection with one or multiple other collections * Merge the current collection with one or multiple collections
* *
* @param IImmutableCollection ...$collections The other collections to combine * @param IImmutableCollection<TKey, TValue> ...$collections The other collections to merge
* *
* @return static The combine collection * @return static<TKey, TValue> The result collection
*/ */
public function combineWith (IImmutableCollection ...$collections): self; public function mergeWith (IImmutableCollection ...$collections): static;
} }

@ -5,50 +5,45 @@ namespace jrosset\Collections;
use ArrayIterator; use ArrayIterator;
use Closure; use Closure;
use InvalidArgumentException; use InvalidArgumentException;
use JsonException; use OutOfBoundsException;
use ReflectionFunction;
use Traversable; use Traversable;
/** /**
* An immutable collection * An immutable collection
* *
* @psalm-template TKey of array-key * @template TKey of array-key
* @template-covariant TValue * @template TValue
* @template-implements IImmutableCollection<TKey, TValue> *
* @implements IImmutableCollection<TKey, TValue>
*/ */
class ImmutableCollection implements IImmutableCollection { class ImmutableCollection implements IImmutableCollection {
/** /**
* @var array The internal array of elements * @var array<TKey, TValue> The internal array of elements
* @psalm-var array<TKey, TValue>
*
* @internal
*/ */
protected array $elements; protected array $elements;
/** /**
* Initialise a new collection * Initialise a new collection
* *
* @param array|Traversable|null $other The initial values * @param iterable|null $other The initial values
*
* @psalm-param array<TKey, TValue>|Traversable<TKey, TValue>|null
* *
* @throws InvalidArgumentException If the initial values aren't valid * @throws InvalidArgumentException If the initial values aren't valid
*/ */
public function __construct ($other = null) { public function __construct (?iterable $other = null) {
$this->_initialize($other); $this->_initialize($other);
} }
/** /**
* Initialise the internals elements * Initialise the internals elements
* *
* @param array|Traversable|null $other The initial values * @param iterable|null $other The initial values
*
* @psalm-param array<TKey, TValue>|Traversable<TKey, TValue>|null
* *
* @throws InvalidArgumentException If the initial values aren't valid * @throws InvalidArgumentException If the initial values aren't valid
* @internal
* *
* @internal
*/ */
protected function _initialize ($other = null) { protected function _initialize (?iterable $other = null): void {
$this->elements = []; $this->elements = [];
if ($other !== null) { if ($other !== null) {
if (!is_array($other) && !$other instanceof Traversable) { if (!is_array($other) && !$other instanceof Traversable) {
@ -63,17 +58,14 @@ class ImmutableCollection implements IImmutableCollection {
/** /**
* Internally set a value to a key * Internally set a value to a key
* *
* @param array-key $key The key * @param TKey $key The key
* @param mixed $value The value * @param TValue $value The value
*
* @psalm-param TKey $key
* @psalm-param TValue $value
* *
* @return $this * @return $this
* @internal
* *
* @internal
*/ */
protected function _set ($key, $value): self { protected function _set ($key, $value): static {
$this->elements[$this->_normalizeKey($key)] = $value; $this->elements[$this->_normalizeKey($key)] = $value;
return $this; return $this;
} }
@ -83,8 +75,10 @@ class ImmutableCollection implements IImmutableCollection {
* @param IImmutableCollection ...$collections The collections to merge * @param IImmutableCollection ...$collections The collections to merge
* *
* @return $this * @return $this
*
* @internal
*/ */
protected function _merge (IImmutableCollection ...$collections): self { protected function _merge (IImmutableCollection ...$collections): static {
foreach ($collections as $collection) { foreach ($collections as $collection) {
foreach ($collection as $key => $value) { foreach ($collection as $key => $value) {
$this->_set($key, $value); $this->_set($key, $value);
@ -95,15 +89,13 @@ class ImmutableCollection implements IImmutableCollection {
/** /**
* Internally add a value * Internally add a value
* *
* @param mixed ...$values The value * @param TValue ...$values The value
*
* @psalm-param TValue ...$value
* *
* @return $this * @return $this
* @internal
* *
* @internal
*/ */
protected function _add (...$values): self { protected function _add (...$values): static {
foreach ($values as $value) { foreach ($values as $value) {
$this->elements[] = $value; $this->elements[] = $value;
} }
@ -114,9 +106,9 @@ class ImmutableCollection implements IImmutableCollection {
* *
* @param IImmutableCollection ...$collections The collections to add * @param IImmutableCollection ...$collections The collections to add
* *
* @return mixed * @return $this
*/ */
protected function _addCollection (IImmutableCollection ...$collections): self { protected function _addCollection (IImmutableCollection ...$collections): static {
foreach ($collections as $collection) { foreach ($collections as $collection) {
foreach ($collection as $value) { foreach ($collection as $value) {
$this->_add($value); $this->_add($value);
@ -127,60 +119,79 @@ class ImmutableCollection implements IImmutableCollection {
/** /**
* Normalize a key * Normalize a key
* *
* @param array-key $key The key to normalize * @param TKey $key The key to normalize
* *
* @psalm-param TKey $key * @return TKey The normalized key
*
* @return array-key The normalized key
* @psalm-return TKey
*/ */
protected function _normalizeKey ($key) { protected function _normalizeKey ($key) {
return $key; return $key;
} }
/**
* Vérifie un offset (&ge; 0, &lt; {@see static::count()})
*
* @param int $offset L'offset à vérifier
*
* @return void
*
* @throws OutOfBoundsException Si l'offset est invalide
*/
protected function _checkOffset (int $offset): void {
if ($offset < 0 || $offset >= $this->count()) {
throw new OutOfBoundsException('The offset must be between 0 and ' . ($this->count() - 1));
}
}
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function getIterator () { public function getIterator (): ArrayIterator {
return new ArrayIterator($this->elements); return new ArrayIterator($this->elements);
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function offsetExists ($offset): bool { public function offsetExists (mixed $offset): bool {
return $this->exists($offset); return $this->exists($offset);
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function offsetGet ($offset) { public function offsetGet (mixed $offset): mixed {
return $this->get($offset); return $this->get($offset);
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function offsetSet ($offset, $value): void { public function offsetSet (mixed $offset, mixed $value): void {
throw new ImmutableException(); throw new ImmutableException();
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function offsetUnset ($offset): void { public function offsetUnset (mixed $offset): void {
throw new ImmutableException(); throw new ImmutableException();
} }
/** /**
* @inheritDoc * 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 (): ?string { public function __serialize (): array {
return serialize($this->elements); return [
'elements' => $this->elements,
];
} }
/** /**
* @inheritDoc * {@see https://www.php.net/manual/function.unserialize.php Unserialize} from the list of serialized properties
*
* @param array $data The list of serialized properties
*
* @return void
*/ */
public function unserialize ($data) { public function __unserialize (array $data): void {
$this->_initialize(unserialize($data)); $this->_initialize($data['elements']);
} }
/** /**
@ -219,9 +230,17 @@ class ImmutableCollection implements IImmutableCollection {
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function get ($key) { public function get ($key): mixed {
return $this->elements[$this->_normalizeKey($key)] ?? null; return $this->elements[$this->_normalizeKey($key)] ?? null;
} }
/**
* @inheritDoc
*/
public function getFromOffset (int $offset) {
$this->_checkOffset($offset);
return array_values($this->elements)[$offset];
}
/** /**
* @inheritDoc * @inheritDoc
*/ */
@ -233,12 +252,50 @@ class ImmutableCollection implements IImmutableCollection {
} }
return null; return null;
} }
/**
* @inheritDoc
*/
public function keyFromOffset (int $offset) {
$this->_checkOffset($offset);
return array_keys($this->elements)[$offset];
}
/**
* @inheritDoc
*/
public function first () {
return $this->getFromOffset(0);
}
/**
* @inheritDoc
*/
public function firstKey () {
return $this->keyFromOffset(0);
}
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function slice (int $offset, ?int $length = null): self { public function last () {
return $this->getFromOffset($this->count() - 1);
}
/**
* @inheritDoc
*/
public function lastKey () {
return $this->keyFromOffset($this->count() - 1);
}
/**
* @inheritDoc
*/
public function slice (int $offset, ?int $length = null): static {
$output = new static(); $output = new static();
if ($this->count() === 0) {
return $output;
}
$this->_checkOffset($offset);
$currentIndex = 0; $currentIndex = 0;
foreach ($this->elements as $key => $value) { foreach ($this->elements as $key => $value) {
@ -258,7 +315,7 @@ class ImmutableCollection implements IImmutableCollection {
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function filter (Closure $filter): self { public function filter (Closure $filter): static {
$output = new static(); $output = new static();
foreach ($this->elements as $key => $value) { foreach ($this->elements as $key => $value) {
if (!$filter($key, $value)) { if (!$filter($key, $value)) {
@ -271,7 +328,7 @@ class ImmutableCollection implements IImmutableCollection {
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function withoutEmpties (): self { public function withoutEmpties (): static {
return $this->filter(function ($value) { return $this->filter(function ($value) {
return empty($value); return empty($value);
}); });
@ -279,41 +336,93 @@ class ImmutableCollection implements IImmutableCollection {
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function removeEmpties (): self { public function map (Closure $process): static {
$this->_initialize($this->withoutEmpties()); //region Regarde si $process doit être appelée avec la clé en plus de la valeur
return $this; $callProcessWithKey = false;
/** @noinspection PhpUnhandledExceptionInspection */
$processReflection = new ReflectionFunction($process);
if (count($processReflection->getParameters()) >= 2) {
if (!$processReflection->getParameters()[1]->isDefaultValueAvailable()) {
$callProcessWithKey = true;
}
}
//endregion
$output = new static();
foreach ($this->elements as $key => $value) {
$output->_set($key, $callProcessWithKey ? $process($key, $value) : $process($value));
}
return $output;
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function map (Closure $process): self { public function sort (Closure|IComparator|null $sorter = null): static {
$output = new static(); $elements = $this->toArray();
foreach ($this->elements as $key => $value) { uasort($elements, self::_normalizeSorter($sorter));
$output->_set($key, $process($key, $value));
return new static($elements);
}
/**
* @inheritDoc
*/
public function sortByKey (Closure|IComparator $sorter): static {
$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 (Closure|IComparator|null $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;
} }
return $output;
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function keys (): self { public function keys (): IImmutableCollection {
return new static(array_keys($this->elements)); return new static(array_keys($this->elements));
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function values (): self { public function values (): static {
return new static(array_values($this->elements)); return new static(array_values($this->elements));
} }
/** /**
* @inheritDoc * @inheritDoc
*
* @throws JsonException
*/ */
public function jsonSerialize () { public function jsonSerialize (): mixed {
return json_encode($this->elements, JSON_THROW_ON_ERROR); return $this->elements;
} }
/** /**
@ -325,14 +434,36 @@ class ImmutableCollection implements IImmutableCollection {
/** /**
* @inheritDoc * @inheritDoc
*/ */
public static function split (string $value, string $separator): self { public static function split (string $value, string $separator): static {
return new static (explode($separator, $value)); return new static (explode($separator, $value));
} }
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function combineWith (IImmutableCollection ...$collections): IImmutableCollection { public static function fill (int $size, $value): static {
$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): static {
$collection = new static();
foreach ($keys as $key) {
$collection->_set($key, $value);
}
return $collection;
}
/**
* @inheritDoc
*/
public function mergeWith (IImmutableCollection ...$collections): static {
return (clone $this)->_merge(...$collections); return (clone $this)->_merge(...$collections);
} }
} }

@ -5,9 +5,10 @@ namespace jrosset\Collections;
/** /**
* A collection with insensitive case keys * A collection with insensitive case keys
* *
* @psalm-template TKey of array-key * @template TKey of array-key
* @template-covariant TValue * @template TValue
* @template-extends Collection<TKey, TValue> *
* @implements Collection<TKey, TValue>
*/ */
class InsensitiveCaseKeyCollection extends Collection { class InsensitiveCaseKeyCollection extends Collection {
use TInsensitiveCaseKey; use TInsensitiveCaseKey;

@ -5,9 +5,10 @@ namespace jrosset\Collections;
/** /**
* An immutable collection with insensitive case keys * An immutable collection with insensitive case keys
* *
* @psalm-template TKey of array-key * @template TKey of array-key
* @template-covariant TValue * @template TValue
* @template-extends ImmutableCollection<TKey, TValue> *
* @implements ImmutableCollection<TKey, TValue>
*/ */
class InsensitiveCaseKeyImmutableCollection extends ImmutableCollection implements IImmutableCollection { class InsensitiveCaseKeyImmutableCollection extends ImmutableCollection implements IImmutableCollection {
use TInsensitiveCaseKey; use TInsensitiveCaseKey;

@ -7,11 +7,9 @@ namespace jrosset\Collections;
*/ */
trait TInsensitiveCaseKey { trait TInsensitiveCaseKey {
/** /**
* Normalize a key * @inheritDoc
* *
* @param array-key $key The key to normalize * @noinspection PhpMissingReturnTypeInspection
*
* @return array-key The normalized key
*/ */
protected function _normalizeKey ($key) { protected function _normalizeKey ($key) {
return mb_strtolower($key); return mb_strtolower($key);

@ -0,0 +1,35 @@
<?php
namespace jrosset\Collections;
/**
* Implementation for unique collection
*
* @template TKey of array-key
* @template TValue
*/
trait TUniqueValues {
/**
* @inheritDoc
*/
protected function _set ($key, $value): static {
if ($this->contains($value)) {
return $this;
}
$this->elements[$this->_normalizeKey($key)] = $value;
return $this;
}
/**
* @inheritDoc
*/
protected function _add (...$values): static {
foreach ($values as $value) {
if ($this->contains($value)) {
continue;
}
$this->elements[] = $value;
}
return $this;
}
}

@ -0,0 +1,16 @@
<?php
namespace jrosset\Collections;
/**
* A collection with unique values
*
* @template TKey of array-key
* @template TValue
*
* @implements Collection<TKey, TValue>
* @implements TUniqueValues<TKey, TValue>
*/
class UniqueCollection extends Collection {
use TUniqueValues;
}

@ -0,0 +1,16 @@
<?php
namespace jrosset\Collections;
/**
* An immutable collection with unique values
*
* @template TKey of array-key
* @template TValue
*
* @implements ImmutableCollection<TKey, TValue>
* @implements TUniqueValues<TKey, TValue>
*/
class UniqueImmutableCollection extends ImmutableCollection {
use TUniqueValues;
}

@ -1,5 +1,6 @@
<?php <?php
use jrosset\Collections\ImmutableCollection;
use jrosset\Collections\InsensitiveCaseKeyCollection; use jrosset\Collections\InsensitiveCaseKeyCollection;
use jrosset\Collections\InsensitiveCaseKeyImmutableCollection; use jrosset\Collections\InsensitiveCaseKeyImmutableCollection;
@ -18,4 +19,9 @@ echo '-----' . PHP_EOL;
$collection = new InsensitiveCaseKeyCollection($readOnlyCollection); $collection = new InsensitiveCaseKeyCollection($readOnlyCollection);
$collection->add(28); $collection->add(28);
echo 'Size : ' . $collection->count() . PHP_EOL; echo 'Size : ' . $collection->count() . PHP_EOL;
echo 'Fill :' . PHP_EOL;
foreach (ImmutableCollection::fill(5, -1) as $key => $value) {
echo "\t#" . $key . ' = ' . $value . PHP_EOL;
}

@ -1,11 +1,19 @@
<?php <?php
/** @noinspection PhpUnhandledExceptionInspection */
/** @noinspection PhpIllegalPsrClassPathInspection */ /** @noinspection PhpIllegalPsrClassPathInspection */
use jrosset\Collections\Collection; use jrosset\Collections\Collection;
use jrosset\Collections\IComparable;
use jrosset\Collections\IComparator;
require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../vendor/autoload.php';
class Measure { /**
* Class Measure
*
* @implements IComparable<static>
*/
class Measure implements IComparable {
private int $value; private int $value;
private string $unit; private string $unit;
@ -14,9 +22,33 @@ class Measure {
$this->unit = $unit; $this->unit = $unit;
} }
public function getValue (): int {
return $this->value;
}
public function asString (): string { public function asString (): string {
return $this->value . $this->unit; return $this->value . $this->unit;
} }
/**
* @inheritDoc
*/
public function compareTo ($other): int {
return $this->value <=> $other->value;
}
}
/**
* Class MeasureReverseComparator
*
* @implements IComparator<Measure>
*/
class MeasureReverseComparator implements IComparator {
/**
* @inheritDoc
*/
public function compare ($object1, $object2): int {
return -($object1->getValue() <=> $object2->getValue());
}
} }
/** /**
@ -33,7 +65,15 @@ function getMeasures (): Collection {
return $measures; return $measures;
} }
$measures = getMeasures();
$measures->sortSelf();
echo 'Measures :' . PHP_EOL; echo 'Measures :' . PHP_EOL;
foreach (getMeasures() as $no => $measure) { foreach ($measures as $no => $measure) {
echo "\t#" . $no . ' = ' . $measure->asString() . PHP_EOL;
}
echo 'Measures (reversed) :' . PHP_EOL;
foreach ($measures->sort(new MeasureReverseComparator()) as $no => $measure) {
echo "\t#" . $no . ' = ' . $measure->asString() . PHP_EOL; echo "\t#" . $no . ' = ' . $measure->asString() . PHP_EOL;
} }
Loading…
Cancel
Save