This commit is contained in:
Paolo A
2024-08-13 13:44:16 +00:00
parent 1bbb23088d
commit e796d76612
4001 changed files with 30101 additions and 40075 deletions

View File

@@ -17,27 +17,27 @@ namespace Ramsey\Collection;
use Closure;
use Ramsey\Collection\Exception\CollectionMismatchException;
use Ramsey\Collection\Exception\InvalidArgumentException;
use Ramsey\Collection\Exception\InvalidSortOrderException;
use Ramsey\Collection\Exception\OutOfBoundsException;
use Ramsey\Collection\Exception\InvalidPropertyOrMethod;
use Ramsey\Collection\Exception\NoSuchElementException;
use Ramsey\Collection\Exception\UnsupportedOperationException;
use Ramsey\Collection\Tool\TypeTrait;
use Ramsey\Collection\Tool\ValueExtractorTrait;
use Ramsey\Collection\Tool\ValueToStringTrait;
use function array_filter;
use function array_key_first;
use function array_key_last;
use function array_map;
use function array_merge;
use function array_reduce;
use function array_search;
use function array_udiff;
use function array_uintersect;
use function current;
use function end;
use function in_array;
use function is_int;
use function is_object;
use function reset;
use function spl_object_id;
use function sprintf;
use function unserialize;
use function usort;
/**
@@ -55,27 +55,24 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
use ValueExtractorTrait;
/**
* @inheritDoc
* @throws InvalidArgumentException if $element is of the wrong type.
*/
public function add($element): bool
public function add(mixed $element): bool
{
$this[] = $element;
return true;
}
/**
* @inheritDoc
*/
public function contains($element, bool $strict = true): bool
public function contains(mixed $element, bool $strict = true): bool
{
return in_array($element, $this->data, $strict);
}
/**
* @inheritDoc
* @throws InvalidArgumentException if $element is of the wrong type.
*/
public function offsetSet($offset, $value): void
public function offsetSet(mixed $offset, mixed $value): void
{
if ($this->checkType($this->getType(), $value) === false) {
throw new InvalidArgumentException(
@@ -91,10 +88,7 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
}
}
/**
* @inheritDoc
*/
public function remove($element): bool
public function remove(mixed $element): bool
{
if (($position = array_search($element, $this->data, true)) !== false) {
unset($this[$position]);
@@ -106,6 +100,11 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
}
/**
* @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist
* on the elements in this collection.
* @throws UnsupportedOperationException if unable to call column() on this
* collection.
*
* @inheritDoc
*/
public function column(string $propertyOrMethod): array
@@ -113,55 +112,55 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
$temp = [];
foreach ($this->data as $item) {
/** @var mixed $value */
$value = $this->extractValue($item, $propertyOrMethod);
/** @psalm-suppress MixedAssignment */
$temp[] = $value;
$temp[] = $this->extractValue($item, $propertyOrMethod);
}
return $temp;
}
/**
* @inheritDoc
* @return T
*
* @throws NoSuchElementException if this collection is empty.
*/
public function first()
public function first(): mixed
{
if ($this->isEmpty()) {
throw new OutOfBoundsException('Can\'t determine first item. Collection is empty');
$firstIndex = array_key_first($this->data);
if ($firstIndex === null) {
throw new NoSuchElementException('Can\'t determine first item. Collection is empty');
}
reset($this->data);
/** @var T $first */
$first = current($this->data);
return $first;
return $this->data[$firstIndex];
}
/**
* @inheritDoc
* @return T
*
* @throws NoSuchElementException if this collection is empty.
*/
public function last()
public function last(): mixed
{
if ($this->isEmpty()) {
throw new OutOfBoundsException('Can\'t determine last item. Collection is empty');
$lastIndex = array_key_last($this->data);
if ($lastIndex === null) {
throw new NoSuchElementException('Can\'t determine last item. Collection is empty');
}
/** @var T $item */
$item = end($this->data);
reset($this->data);
return $item;
return $this->data[$lastIndex];
}
public function sort(string $propertyOrMethod, string $order = self::SORT_ASC): CollectionInterface
/**
* @return CollectionInterface<T>
*
* @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist
* on the elements in this collection.
* @throws UnsupportedOperationException if unable to call sort() on this
* collection.
*/
public function sort(?string $propertyOrMethod = null, Sort $order = Sort::Ascending): CollectionInterface
{
if (!in_array($order, [self::SORT_ASC, self::SORT_DESC], true)) {
throw new InvalidSortOrderException('Invalid sort order given: ' . $order);
}
$collection = clone $this;
usort(
@@ -170,20 +169,25 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
* @param T $a
* @param T $b
*/
function ($a, $b) use ($propertyOrMethod, $order): int {
function (mixed $a, mixed $b) use ($propertyOrMethod, $order): int {
/** @var mixed $aValue */
$aValue = $this->extractValue($a, $propertyOrMethod);
/** @var mixed $bValue */
$bValue = $this->extractValue($b, $propertyOrMethod);
return ($aValue <=> $bValue) * ($order === self::SORT_DESC ? -1 : 1);
return ($aValue <=> $bValue) * ($order === Sort::Descending ? -1 : 1);
},
);
return $collection;
}
/**
* @param callable(T): bool $callback A callable to use for filtering elements.
*
* @return CollectionInterface<T>
*/
public function filter(callable $callback): CollectionInterface
{
$collection = clone $this;
@@ -193,23 +197,66 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
}
/**
* {@inheritdoc}
* @return CollectionInterface<T>
*
* @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist
* on the elements in this collection.
* @throws UnsupportedOperationException if unable to call where() on this
* collection.
*/
public function where(string $propertyOrMethod, $value): CollectionInterface
public function where(?string $propertyOrMethod, mixed $value): CollectionInterface
{
return $this->filter(function ($item) use ($propertyOrMethod, $value) {
/** @var mixed $accessorValue */
$accessorValue = $this->extractValue($item, $propertyOrMethod);
return $this->filter(
/**
* @param T $item
*/
function (mixed $item) use ($propertyOrMethod, $value): bool {
/** @var mixed $accessorValue */
$accessorValue = $this->extractValue($item, $propertyOrMethod);
return $accessorValue === $value;
});
return $accessorValue === $value;
},
);
}
/**
* @param callable(T): TCallbackReturn $callback A callable to apply to each
* item of the collection.
*
* @return CollectionInterface<TCallbackReturn>
*
* @template TCallbackReturn
*/
public function map(callable $callback): CollectionInterface
{
/** @var Collection<TCallbackReturn> */
return new Collection('mixed', array_map($callback, $this->data));
}
/**
* @param callable(TCarry, T): TCarry $callback A callable to apply to each
* item of the collection to reduce it to a single value.
* @param TCarry $initial This is the initial value provided to the callback.
*
* @return TCarry
*
* @template TCarry
*/
public function reduce(callable $callback, mixed $initial): mixed
{
/** @var TCarry */
return array_reduce($this->data, $callback, $initial);
}
/**
* @param CollectionInterface<T> $other The collection to check for divergent
* items.
*
* @return CollectionInterface<T>
*
* @throws CollectionMismatchException if the compared collections are of
* differing types.
*/
public function diff(CollectionInterface $other): CollectionInterface
{
$this->compareCollectionTypes($other);
@@ -226,6 +273,15 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
return $collection;
}
/**
* @param CollectionInterface<T> $other The collection to check for
* intersecting items.
*
* @return CollectionInterface<T>
*
* @throws CollectionMismatchException if the compared collections are of
* differing types.
*/
public function intersect(CollectionInterface $other): CollectionInterface
{
$this->compareCollectionTypes($other);
@@ -239,6 +295,15 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
return $collection;
}
/**
* @param CollectionInterface<T> ...$collections The collections to merge.
*
* @return CollectionInterface<T>
*
* @throws CollectionMismatchException if unable to merge any of the given
* collections or items within the given collections due to type
* mismatch errors.
*/
public function merge(CollectionInterface ...$collections): CollectionInterface
{
$mergedCollection = clone $this;
@@ -274,19 +339,10 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
return $mergedCollection;
}
/**
* @inheritDoc
*/
public function unserialize($serialized): void
{
/** @var array<array-key, T> $data */
$data = unserialize($serialized, ['allowed_classes' => [$this->getType()]]);
$this->data = $data;
}
/**
* @param CollectionInterface<T> $other
*
* @throws CollectionMismatchException
*/
private function compareCollectionTypes(CollectionInterface $other): void
{
@@ -307,7 +363,7 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
* @param T $a
* @param T $b
*/
function ($a, $b): int {
function (mixed $a, mixed $b): int {
// If the two values are object, we convert them to unique scalars.
// If the collection contains mixed values (unlikely) where some are objects
// and some are not, we leave them as they are.
@@ -327,15 +383,11 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
*/
private function getUniformType(CollectionInterface $collection): string
{
switch ($collection->getType()) {
case 'integer':
return 'int';
case 'boolean':
return 'bool';
case 'double':
return 'float';
default:
return $collection->getType();
}
return match ($collection->getType()) {
'integer' => 'int',
'boolean' => 'bool',
'double' => 'float',
default => $collection->getType(),
};
}
}