← All changes
|
includes/sdk/google/ramsey/collection/src/AbstractCollection.php
+135
-104
1.2.11
→
1.4.1
View file →
| @@ -9,33 +9,33 @@ | ||
| 9 | 9 | * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com> |
| 10 | 10 | * @license http://opensource.org/licenses/MIT MIT |
| 11 | 11 | */ |
| 12 | 12 | declare (strict_types=1); |
| 13 | -namespace Dudlewebs\WPMCS\Ramsey\Collection; | |
| 13 | +namespace Dudlewebs\WPMCS\GCP\Ramsey\Collection; | |
| 14 | 14 | |
| 15 | 15 | use Closure; |
| 16 | -use Dudlewebs\WPMCS\Ramsey\Collection\Exception\CollectionMismatchException; | |
| 17 | -use Dudlewebs\WPMCS\Ramsey\Collection\Exception\InvalidArgumentException; | |
| 18 | -use Dudlewebs\WPMCS\Ramsey\Collection\Exception\InvalidSortOrderException; | |
| 19 | -use Dudlewebs\WPMCS\Ramsey\Collection\Exception\OutOfBoundsException; | |
| 20 | -use Dudlewebs\WPMCS\Ramsey\Collection\Tool\TypeTrait; | |
| 21 | -use Dudlewebs\WPMCS\Ramsey\Collection\Tool\ValueExtractorTrait; | |
| 22 | -use Dudlewebs\WPMCS\Ramsey\Collection\Tool\ValueToStringTrait; | |
| 16 | +use Dudlewebs\WPMCS\GCP\Ramsey\Collection\Exception\CollectionMismatchException; | |
| 17 | +use Dudlewebs\WPMCS\GCP\Ramsey\Collection\Exception\InvalidArgumentException; | |
| 18 | +use Dudlewebs\WPMCS\GCP\Ramsey\Collection\Exception\InvalidPropertyOrMethod; | |
| 19 | +use Dudlewebs\WPMCS\GCP\Ramsey\Collection\Exception\NoSuchElementException; | |
| 20 | +use Dudlewebs\WPMCS\GCP\Ramsey\Collection\Exception\UnsupportedOperationException; | |
| 21 | +use Dudlewebs\WPMCS\GCP\Ramsey\Collection\Tool\TypeTrait; | |
| 22 | +use Dudlewebs\WPMCS\GCP\Ramsey\Collection\Tool\ValueExtractorTrait; | |
| 23 | +use Dudlewebs\WPMCS\GCP\Ramsey\Collection\Tool\ValueToStringTrait; | |
| 23 | 24 | use function array_filter; |
| 25 | +use function array_key_first; | |
| 26 | +use function array_key_last; | |
| 24 | 27 | use function array_map; |
| 25 | 28 | use function array_merge; |
| 29 | +use function array_reduce; | |
| 26 | 30 | use function array_search; |
| 27 | 31 | use function array_udiff; |
| 28 | 32 | use function array_uintersect; |
| 29 | -use function current; | |
| 30 | -use function end; | |
| 31 | 33 | use function in_array; |
| 32 | 34 | use function is_int; |
| 33 | 35 | use function is_object; |
| 34 | -use function reset; | |
| 35 | 36 | use function spl_object_id; |
| 36 | 37 | use function sprintf; |
| 37 | -use function unserialize; | |
| 38 | 38 | use function usort; |
| 39 | 39 | /** |
| 40 | 40 | * This class provides a basic implementation of `CollectionInterface`, to |
| 41 | 41 | * minimize the effort required to implement this interface |
| @@ -49,26 +49,23 @@ | ||
| 49 | 49 | use TypeTrait; |
| 50 | 50 | use ValueToStringTrait; |
| 51 | 51 | use ValueExtractorTrait; |
| 52 | 52 | /** |
| 53 | - * @inheritDoc | |
| 53 | + * @throws InvalidArgumentException if $element is of the wrong type. | |
| 54 | 54 | */ |
| 55 | - public function add($element): bool | |
| 55 | + public function add(mixed $element) : bool | |
| 56 | 56 | { |
| 57 | 57 | $this[] = $element; |
| 58 | 58 | return \true; |
| 59 | 59 | } |
| 60 | - /** | |
| 61 | - * @inheritDoc | |
| 62 | - */ | |
| 63 | - public function contains($element, bool $strict = \true): bool | |
| 60 | + public function contains(mixed $element, bool $strict = \true) : bool | |
| 64 | 61 | { |
| 65 | 62 | return in_array($element, $this->data, $strict); |
| 66 | 63 | } |
| 67 | 64 | /** |
| 68 | - * @inheritDoc | |
| 65 | + * @throws InvalidArgumentException if $element is of the wrong type. | |
| 69 | 66 | */ |
| 70 | - public function offsetSet($offset, $value): void | |
| 67 | + public function offsetSet(mixed $offset, mixed $value) : void | |
| 71 | 68 | { |
| 72 | 69 | if ($this->checkType($this->getType(), $value) === \false) { |
| 73 | 70 | throw new InvalidArgumentException('Value must be of type ' . $this->getType() . '; value is ' . $this->toolValueToString($value)); |
| 74 | 71 | } |
| @@ -77,12 +74,9 @@ | ||
| 77 | 74 | } else { |
| 78 | 75 | $this->data[$offset] = $value; |
| 79 | 76 | } |
| 80 | 77 | } |
| 81 | - /** | |
| 82 | - * @inheritDoc | |
| 83 | - */ | |
| 84 | - public function remove($element): bool | |
| 78 | + public function remove(mixed $element) : bool | |
| 85 | 79 | { |
| 86 | 80 | if (($position = array_search($element, $this->data, \true)) !== \false) { |
| 87 | 81 | unset($this[$position]); |
| 88 | 82 | return \true; |
| @@ -89,70 +83,73 @@ | ||
| 89 | 83 | } |
| 90 | 84 | return \false; |
| 91 | 85 | } |
| 92 | 86 | /** |
| 87 | + * @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist | |
| 88 | + * on the elements in this collection. | |
| 89 | + * @throws UnsupportedOperationException if unable to call column() on this | |
| 90 | + * collection. | |
| 91 | + * | |
| 93 | 92 | * @inheritDoc |
| 94 | 93 | */ |
| 95 | - public function column(string $propertyOrMethod): array | |
| 94 | + public function column(string $propertyOrMethod) : array | |
| 96 | 95 | { |
| 97 | 96 | $temp = []; |
| 98 | 97 | foreach ($this->data as $item) { |
| 99 | - /** @var mixed $value */ | |
| 100 | - $value = $this->extractValue($item, $propertyOrMethod); | |
| 101 | - /** @psalm-suppress MixedAssignment */ | |
| 102 | - $temp[] = $value; | |
| 98 | + $temp[] = $this->extractValue($item, $propertyOrMethod); | |
| 103 | 99 | } |
| 104 | 100 | return $temp; |
| 105 | 101 | } |
| 106 | 102 | /** |
| 107 | - * @inheritDoc | |
| 103 | + * @return T | |
| 104 | + * | |
| 105 | + * @throws NoSuchElementException if this collection is empty. | |
| 108 | 106 | */ |
| 109 | - public function first() | |
| 107 | + public function first() : mixed | |
| 110 | 108 | { |
| 111 | - if ($this->isEmpty()) { | |
| 112 | - throw new OutOfBoundsException('Can\'t determine first item. Collection is empty'); | |
| 109 | + $firstIndex = array_key_first($this->data); | |
| 110 | + if ($firstIndex === null) { | |
| 111 | + throw new NoSuchElementException('Can\'t determine first item. Collection is empty'); | |
| 113 | 112 | } |
| 114 | - reset($this->data); | |
| 115 | - /** @var T $first */ | |
| 116 | - $first = current($this->data); | |
| 117 | - return $first; | |
| 113 | + return $this->data[$firstIndex]; | |
| 118 | 114 | } |
| 119 | 115 | /** |
| 120 | - * @inheritDoc | |
| 116 | + * @return T | |
| 117 | + * | |
| 118 | + * @throws NoSuchElementException if this collection is empty. | |
| 121 | 119 | */ |
| 122 | - public function last() | |
| 120 | + public function last() : mixed | |
| 123 | 121 | { |
| 124 | - if ($this->isEmpty()) { | |
| 125 | - throw new OutOfBoundsException('Can\'t determine last item. Collection is empty'); | |
| 122 | + $lastIndex = array_key_last($this->data); | |
| 123 | + if ($lastIndex === null) { | |
| 124 | + throw new NoSuchElementException('Can\'t determine last item. Collection is empty'); | |
| 126 | 125 | } |
| 127 | - /** @var T $item */ | |
| 128 | - $item = end($this->data); | |
| 129 | - reset($this->data); | |
| 130 | - return $item; | |
| 126 | + return $this->data[$lastIndex]; | |
| 131 | 127 | } |
| 132 | - public function sort(string $propertyOrMethod, string $order = self::SORT_ASC): CollectionInterface | |
| 128 | + /** | |
| 129 | + * @return CollectionInterface<T> | |
| 130 | + * | |
| 131 | + * @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist | |
| 132 | + * on the elements in this collection. | |
| 133 | + * @throws UnsupportedOperationException if unable to call sort() on this | |
| 134 | + * collection. | |
| 135 | + */ | |
| 136 | + public function sort(?string $propertyOrMethod = null, Sort $order = Sort::Ascending) : CollectionInterface | |
| 133 | 137 | { |
| 134 | - if (!in_array($order, [self::SORT_ASC, self::SORT_DESC], \true)) { | |
| 135 | - throw new InvalidSortOrderException('Invalid sort order given: ' . $order); | |
| 136 | - } | |
| 137 | 138 | $collection = clone $this; |
| 138 | - usort( | |
| 139 | - $collection->data, | |
| 140 | - /** | |
| 141 | - * @param T $a | |
| 142 | - * @param T $b | |
| 143 | - */ | |
| 144 | - function ($a, $b) use ($propertyOrMethod, $order): int { | |
| 145 | - /** @var mixed $aValue */ | |
| 146 | - $aValue = $this->extractValue($a, $propertyOrMethod); | |
| 147 | - /** @var mixed $bValue */ | |
| 148 | - $bValue = $this->extractValue($b, $propertyOrMethod); | |
| 149 | - return ($aValue <=> $bValue) * ($order === self::SORT_DESC ? -1 : 1); | |
| 150 | - } | |
| 151 | - ); | |
| 139 | + usort($collection->data, function (mixed $a, mixed $b) use($propertyOrMethod, $order) : int { | |
| 140 | + $aValue = $this->extractValue($a, $propertyOrMethod); | |
| 141 | + $bValue = $this->extractValue($b, $propertyOrMethod); | |
| 142 | + return ($aValue <=> $bValue) * ($order === Sort::Descending ? -1 : 1); | |
| 143 | + }); | |
| 152 | 144 | return $collection; |
| 153 | 145 | } |
| 154 | - public function filter(callable $callback): CollectionInterface | |
| 146 | + /** | |
| 147 | + * @param callable(T): bool $callback A callable to use for filtering elements. | |
| 148 | + * | |
| 149 | + * @return CollectionInterface<T> | |
| 150 | + */ | |
| 151 | + public function filter(callable $callback) : CollectionInterface | |
| 155 | 152 | { |
| 156 | 153 | $collection = clone $this; |
| 157 | 154 | $collection->data = array_merge([], array_filter($collection->data, $callback)); |
| 158 | 155 | return $collection; |
| @@ -157,43 +154,88 @@ | ||
| 157 | 154 | $collection->data = array_merge([], array_filter($collection->data, $callback)); |
| 158 | 155 | return $collection; |
| 159 | 156 | } |
| 160 | 157 | /** |
| 161 | - * {@inheritdoc} | |
| 158 | + * @return CollectionInterface<T> | |
| 159 | + * | |
| 160 | + * @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist | |
| 161 | + * on the elements in this collection. | |
| 162 | + * @throws UnsupportedOperationException if unable to call where() on this | |
| 163 | + * collection. | |
| 162 | 164 | */ |
| 163 | - public function where(string $propertyOrMethod, $value): CollectionInterface | |
| 165 | + public function where(?string $propertyOrMethod, mixed $value) : CollectionInterface | |
| 164 | 166 | { |
| 165 | - return $this->filter(function ($item) use ($propertyOrMethod, $value) { | |
| 166 | - /** @var mixed $accessorValue */ | |
| 167 | - $accessorValue = $this->extractValue($item, $propertyOrMethod); | |
| 168 | - return $accessorValue === $value; | |
| 169 | - }); | |
| 167 | + return $this->filter(fn(mixed $item): bool => $this->extractValue($item, $propertyOrMethod) === $value); | |
| 170 | 168 | } |
| 171 | - public function map(callable $callback): CollectionInterface | |
| 169 | + /** | |
| 170 | + * @param callable(T): TCallbackReturn $callback A callable to apply to each | |
| 171 | + * item of the collection. | |
| 172 | + * | |
| 173 | + * @return CollectionInterface<TCallbackReturn> | |
| 174 | + * | |
| 175 | + * @template TCallbackReturn | |
| 176 | + */ | |
| 177 | + public function map(callable $callback) : CollectionInterface | |
| 172 | 178 | { |
| 173 | 179 | return new Collection('mixed', array_map($callback, $this->data)); |
| 174 | 180 | } |
| 175 | - public function diff(CollectionInterface $other): CollectionInterface | |
| 181 | + /** | |
| 182 | + * @param callable(TCarry, T): TCarry $callback A callable to apply to each | |
| 183 | + * item of the collection to reduce it to a single value. | |
| 184 | + * @param TCarry $initial This is the initial value provided to the callback. | |
| 185 | + * | |
| 186 | + * @return TCarry | |
| 187 | + * | |
| 188 | + * @template TCarry | |
| 189 | + */ | |
| 190 | + public function reduce(callable $callback, mixed $initial) : mixed | |
| 176 | 191 | { |
| 192 | + return array_reduce($this->data, $callback, $initial); | |
| 193 | + } | |
| 194 | + /** | |
| 195 | + * @param CollectionInterface<T> $other The collection to check for divergent | |
| 196 | + * items. | |
| 197 | + * | |
| 198 | + * @return CollectionInterface<T> | |
| 199 | + * | |
| 200 | + * @throws CollectionMismatchException if the compared collections are of | |
| 201 | + * differing types. | |
| 202 | + */ | |
| 203 | + public function diff(CollectionInterface $other) : CollectionInterface | |
| 204 | + { | |
| 177 | 205 | $this->compareCollectionTypes($other); |
| 178 | 206 | $diffAtoB = array_udiff($this->data, $other->toArray(), $this->getComparator()); |
| 179 | 207 | $diffBtoA = array_udiff($other->toArray(), $this->data, $this->getComparator()); |
| 180 | - /** @var array<array-key, T> $diff */ | |
| 181 | - $diff = array_merge($diffAtoB, $diffBtoA); | |
| 182 | 208 | $collection = clone $this; |
| 183 | - $collection->data = $diff; | |
| 209 | + $collection->data = array_merge($diffAtoB, $diffBtoA); | |
| 184 | 210 | return $collection; |
| 185 | 211 | } |
| 186 | - public function intersect(CollectionInterface $other): CollectionInterface | |
| 212 | + /** | |
| 213 | + * @param CollectionInterface<T> $other The collection to check for | |
| 214 | + * intersecting items. | |
| 215 | + * | |
| 216 | + * @return CollectionInterface<T> | |
| 217 | + * | |
| 218 | + * @throws CollectionMismatchException if the compared collections are of | |
| 219 | + * differing types. | |
| 220 | + */ | |
| 221 | + public function intersect(CollectionInterface $other) : CollectionInterface | |
| 187 | 222 | { |
| 188 | 223 | $this->compareCollectionTypes($other); |
| 189 | - /** @var array<array-key, T> $intersect */ | |
| 190 | - $intersect = array_uintersect($this->data, $other->toArray(), $this->getComparator()); | |
| 191 | 224 | $collection = clone $this; |
| 192 | - $collection->data = $intersect; | |
| 225 | + $collection->data = array_uintersect($this->data, $other->toArray(), $this->getComparator()); | |
| 193 | 226 | return $collection; |
| 194 | 227 | } |
| 195 | - public function merge(CollectionInterface ...$collections): CollectionInterface | |
| 228 | + /** | |
| 229 | + * @param CollectionInterface<T> ...$collections The collections to merge. | |
| 230 | + * | |
| 231 | + * @return CollectionInterface<T> | |
| 232 | + * | |
| 233 | + * @throws CollectionMismatchException if unable to merge any of the given | |
| 234 | + * collections or items within the given collections due to type | |
| 235 | + * mismatch errors. | |
| 236 | + */ | |
| 237 | + public function merge(CollectionInterface ...$collections) : CollectionInterface | |
| 196 | 238 | { |
| 197 | 239 | $mergedCollection = clone $this; |
| 198 | 240 | foreach ($collections as $index => $collection) { |
| 199 | 241 | if (!$collection instanceof static) { |
| @@ -214,20 +256,13 @@ | ||
| 214 | 256 | } |
| 215 | 257 | return $mergedCollection; |
| 216 | 258 | } |
| 217 | 259 | /** |
| 218 | - * @inheritDoc | |
| 219 | - */ | |
| 220 | - public function unserialize($serialized): void | |
| 221 | - { | |
| 222 | - /** @var array<array-key, T> $data */ | |
| 223 | - $data = unserialize($serialized, ['allowed_classes' => [$this->getType()]]); | |
| 224 | - $this->data = $data; | |
| 225 | - } | |
| 226 | - /** | |
| 227 | 260 | * @param CollectionInterface<T> $other |
| 261 | + * | |
| 262 | + * @throws CollectionMismatchException | |
| 228 | 263 | */ |
| 229 | - private function compareCollectionTypes(CollectionInterface $other): void | |
| 264 | + private function compareCollectionTypes(CollectionInterface $other) : void | |
| 230 | 265 | { |
| 231 | 266 | if (!$other instanceof static) { |
| 232 | 267 | throw new CollectionMismatchException('Collection must be of type ' . static::class); |
| 233 | 268 | } |
| @@ -236,11 +271,11 @@ | ||
| 236 | 271 | if ($this->getUniformType($other) !== $this->getUniformType($this)) { |
| 237 | 272 | throw new CollectionMismatchException('Collection items must be of type ' . $this->getType()); |
| 238 | 273 | } |
| 239 | 274 | } |
| 240 | - private function getComparator(): Closure | |
| 275 | + private function getComparator() : Closure | |
| 241 | 276 | { |
| 242 | - return function ($a, $b): int { | |
| 277 | + return function (mixed $a, mixed $b) : int { | |
| 243 | 278 | // If the two values are object, we convert them to unique scalars. |
| 244 | 279 | // If the collection contains mixed values (unlikely) where some are objects |
| 245 | 280 | // and some are not, we leave them as they are. |
| 246 | 281 | // The comparator should still work and the result of $a < $b should |
| @@ -254,18 +289,14 @@ | ||
| 254 | 289 | } |
| 255 | 290 | /** |
| 256 | 291 | * @param CollectionInterface<mixed> $collection |
| 257 | 292 | */ |
| 258 | - private function getUniformType(CollectionInterface $collection): string | |
| 293 | + private function getUniformType(CollectionInterface $collection) : string | |
| 259 | 294 | { |
| 260 | - switch ($collection->getType()) { | |
| 261 | - case 'integer': | |
| 262 | - return 'int'; | |
| 263 | - case 'boolean': | |
| 264 | - return 'bool'; | |
| 265 | - case 'double': | |
| 266 | - return 'float'; | |
| 267 | - default: | |
| 268 | - return $collection->getType(); | |
| 269 | - } | |
| 295 | + return match ($collection->getType()) { | |
| 296 | + 'integer' => 'int', | |
| 297 | + 'boolean' => 'bool', | |
| 298 | + 'double' => 'float', | |
| 299 | + default => $collection->getType(), | |
| 300 | + }; | |
| 270 | 301 | } |
| 271 | 302 | } |