* @license http://opensource.org/licenses/MIT MIT */ declare (strict_types=1); namespace DeliciousBrains\WP_Offload_Media\Gcp\Ramsey\Collection; use Closure; use DeliciousBrains\WP_Offload_Media\Gcp\Ramsey\Collection\Exception\CollectionMismatchException; use DeliciousBrains\WP_Offload_Media\Gcp\Ramsey\Collection\Exception\InvalidArgumentException; use DeliciousBrains\WP_Offload_Media\Gcp\Ramsey\Collection\Exception\InvalidSortOrderException; use DeliciousBrains\WP_Offload_Media\Gcp\Ramsey\Collection\Exception\OutOfBoundsException; use DeliciousBrains\WP_Offload_Media\Gcp\Ramsey\Collection\Tool\TypeTrait; use DeliciousBrains\WP_Offload_Media\Gcp\Ramsey\Collection\Tool\ValueExtractorTrait; use DeliciousBrains\WP_Offload_Media\Gcp\Ramsey\Collection\Tool\ValueToStringTrait; use function array_filter; use function array_map; use function array_merge; 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; /** * This class provides a basic implementation of `CollectionInterface`, to * minimize the effort required to implement this interface * * @template T * @extends AbstractArray * @implements CollectionInterface */ abstract class AbstractCollection extends AbstractArray implements CollectionInterface { use TypeTrait; use ValueToStringTrait; use ValueExtractorTrait; /** * @inheritDoc */ public function add($element) : bool { $this[] = $element; return \true; } /** * @inheritDoc */ public function contains($element, bool $strict = \true) : bool { return in_array($element, $this->data, $strict); } /** * @inheritDoc */ public function offsetSet($offset, $value) : void { if ($this->checkType($this->getType(), $value) === \false) { throw new InvalidArgumentException('Value must be of type ' . $this->getType() . '; value is ' . $this->toolValueToString($value)); } if ($offset === null) { $this->data[] = $value; } else { $this->data[$offset] = $value; } } /** * @inheritDoc */ public function remove($element) : bool { if (($position = array_search($element, $this->data, \true)) !== \false) { unset($this[$position]); return \true; } return \false; } /** * @inheritDoc */ public function column(string $propertyOrMethod) : array { $temp = []; foreach ($this->data as $item) { /** @var mixed $value */ $value = $this->extractValue($item, $propertyOrMethod); /** @psalm-suppress MixedAssignment */ $temp[] = $value; } return $temp; } /** * @inheritDoc */ public function first() { if ($this->isEmpty()) { throw new OutOfBoundsException('Can\'t determine first item. Collection is empty'); } reset($this->data); /** @var T $first */ $first = current($this->data); return $first; } /** * @inheritDoc */ public function last() { if ($this->isEmpty()) { throw new OutOfBoundsException('Can\'t determine last item. Collection is empty'); } /** @var T $item */ $item = end($this->data); reset($this->data); return $item; } public function sort(string $propertyOrMethod, string $order = self::SORT_ASC) : CollectionInterface { if (!in_array($order, [self::SORT_ASC, self::SORT_DESC], \true)) { throw new InvalidSortOrderException('Invalid sort order given: ' . $order); } $collection = clone $this; usort( $collection->data, /** * @param T $a * @param T $b */ function ($a, $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 $collection; } public function filter(callable $callback) : CollectionInterface { $collection = clone $this; $collection->data = array_merge([], array_filter($collection->data, $callback)); return $collection; } /** * {@inheritdoc} */ public function where(string $propertyOrMethod, $value) : CollectionInterface { return $this->filter(function ($item) use($propertyOrMethod, $value) { /** @var mixed $accessorValue */ $accessorValue = $this->extractValue($item, $propertyOrMethod); return $accessorValue === $value; }); } public function map(callable $callback) : CollectionInterface { return new Collection('mixed', array_map($callback, $this->data)); } public function diff(CollectionInterface $other) : CollectionInterface { $this->compareCollectionTypes($other); $diffAtoB = array_udiff($this->data, $other->toArray(), $this->getComparator()); $diffBtoA = array_udiff($other->toArray(), $this->data, $this->getComparator()); /** @var array $diff */ $diff = array_merge($diffAtoB, $diffBtoA); $collection = clone $this; $collection->data = $diff; return $collection; } public function intersect(CollectionInterface $other) : CollectionInterface { $this->compareCollectionTypes($other); /** @var array $intersect */ $intersect = array_uintersect($this->data, $other->toArray(), $this->getComparator()); $collection = clone $this; $collection->data = $intersect; return $collection; } public function merge(CollectionInterface ...$collections) : CollectionInterface { $mergedCollection = clone $this; foreach ($collections as $index => $collection) { if (!$collection instanceof static) { throw new CollectionMismatchException(sprintf('Collection with index %d must be of type %s', $index, static::class)); } // When using generics (Collection.php, Set.php, etc), // we also need to make sure that the internal types match each other if ($this->getUniformType($collection) !== $this->getUniformType($this)) { throw new CollectionMismatchException(sprintf('Collection items in collection with index %d must be of type %s', $index, $this->getType())); } foreach ($collection as $key => $value) { if (is_int($key)) { $mergedCollection[] = $value; } else { $mergedCollection[$key] = $value; } } } return $mergedCollection; } /** * @inheritDoc */ public function unserialize($serialized) : void { /** @var array $data */ $data = unserialize($serialized, ['allowed_classes' => [$this->getType()]]); $this->data = $data; } /** * @param CollectionInterface $other */ private function compareCollectionTypes(CollectionInterface $other) : void { if (!$other instanceof static) { throw new CollectionMismatchException('Collection must be of type ' . static::class); } // When using generics (Collection.php, Set.php, etc), // we also need to make sure that the internal types match each other if ($this->getUniformType($other) !== $this->getUniformType($this)) { throw new CollectionMismatchException('Collection items must be of type ' . $this->getType()); } } private function getComparator() : Closure { return function ($a, $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. // The comparator should still work and the result of $a < $b should // be consistent but unpredictable since not documented. if (is_object($a) && is_object($b)) { $a = spl_object_id($a); $b = spl_object_id($b); } return $a === $b ? 0 : ($a < $b ? 1 : -1); }; } /** * @param CollectionInterface $collection */ 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(); } } }