vendor/doctrine/collections/src/ArrayCollection.php line 49

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\Common\Collections;
  4. use ArrayIterator;
  5. use Closure;
  6. use Doctrine\Common\Collections\Expr\ClosureExpressionVisitor;
  7. use ReturnTypeWillChange;
  8. use Stringable;
  9. use Traversable;
  10. use function array_filter;
  11. use function array_key_exists;
  12. use function array_keys;
  13. use function array_map;
  14. use function array_reduce;
  15. use function array_reverse;
  16. use function array_search;
  17. use function array_slice;
  18. use function array_values;
  19. use function count;
  20. use function current;
  21. use function end;
  22. use function in_array;
  23. use function key;
  24. use function next;
  25. use function reset;
  26. use function spl_object_hash;
  27. use function uasort;
  28. use const ARRAY_FILTER_USE_BOTH;
  29. /**
  30.  * An ArrayCollection is a Collection implementation that wraps a regular PHP array.
  31.  *
  32.  * Warning: Using (un-)serialize() on a collection is not a supported use-case
  33.  * and may break when we change the internals in the future. If you need to
  34.  * serialize a collection use {@link toArray()} and reconstruct the collection
  35.  * manually.
  36.  *
  37.  * @psalm-template TKey of array-key
  38.  * @psalm-template T
  39.  * @template-implements Collection<TKey,T>
  40.  * @template-implements Selectable<TKey,T>
  41.  * @psalm-consistent-constructor
  42.  */
  43. class ArrayCollection implements CollectionSelectableStringable
  44. {
  45.     /**
  46.      * An array containing the entries of this collection.
  47.      *
  48.      * @psalm-var array<TKey,T>
  49.      * @var mixed[]
  50.      */
  51.     private array $elements = [];
  52.     /**
  53.      * Initializes a new ArrayCollection.
  54.      *
  55.      * @param array $elements
  56.      * @psalm-param array<TKey,T> $elements
  57.      */
  58.     public function __construct(array $elements = [])
  59.     {
  60.         $this->elements $elements;
  61.     }
  62.     /**
  63.      * {@inheritDoc}
  64.      */
  65.     public function toArray()
  66.     {
  67.         return $this->elements;
  68.     }
  69.     /**
  70.      * {@inheritDoc}
  71.      */
  72.     public function first()
  73.     {
  74.         return reset($this->elements);
  75.     }
  76.     /**
  77.      * Creates a new instance from the specified elements.
  78.      *
  79.      * This method is provided for derived classes to specify how a new
  80.      * instance should be created when constructor semantics have changed.
  81.      *
  82.      * @param array $elements Elements.
  83.      * @psalm-param array<K,V> $elements
  84.      *
  85.      * @return static
  86.      * @psalm-return static<K,V>
  87.      *
  88.      * @psalm-template K of array-key
  89.      * @psalm-template V
  90.      */
  91.     protected function createFrom(array $elements)
  92.     {
  93.         return new static($elements);
  94.     }
  95.     /**
  96.      * {@inheritDoc}
  97.      */
  98.     public function last()
  99.     {
  100.         return end($this->elements);
  101.     }
  102.     /**
  103.      * {@inheritDoc}
  104.      */
  105.     public function key()
  106.     {
  107.         return key($this->elements);
  108.     }
  109.     /**
  110.      * {@inheritDoc}
  111.      */
  112.     public function next()
  113.     {
  114.         return next($this->elements);
  115.     }
  116.     /**
  117.      * {@inheritDoc}
  118.      */
  119.     public function current()
  120.     {
  121.         return current($this->elements);
  122.     }
  123.     /**
  124.      * {@inheritDoc}
  125.      */
  126.     public function remove(string|int $key)
  127.     {
  128.         if (! isset($this->elements[$key]) && ! array_key_exists($key$this->elements)) {
  129.             return null;
  130.         }
  131.         $removed $this->elements[$key];
  132.         unset($this->elements[$key]);
  133.         return $removed;
  134.     }
  135.     /**
  136.      * {@inheritDoc}
  137.      */
  138.     public function removeElement(mixed $element)
  139.     {
  140.         $key array_search($element$this->elementstrue);
  141.         if ($key === false) {
  142.             return false;
  143.         }
  144.         unset($this->elements[$key]);
  145.         return true;
  146.     }
  147.     /**
  148.      * Required by interface ArrayAccess.
  149.      *
  150.      * @param TKey $offset
  151.      *
  152.      * @return bool
  153.      */
  154.     #[ReturnTypeWillChange]
  155.     public function offsetExists(mixed $offset)
  156.     {
  157.         return $this->containsKey($offset);
  158.     }
  159.     /**
  160.      * Required by interface ArrayAccess.
  161.      *
  162.      * @param TKey $offset
  163.      *
  164.      * @return T|null
  165.      */
  166.     #[ReturnTypeWillChange]
  167.     public function offsetGet(mixed $offset)
  168.     {
  169.         return $this->get($offset);
  170.     }
  171.     /**
  172.      * Required by interface ArrayAccess.
  173.      *
  174.      * @param TKey|null $offset
  175.      * @param T         $value
  176.      *
  177.      * @return void
  178.      */
  179.     #[ReturnTypeWillChange]
  180.     public function offsetSet(mixed $offsetmixed $value)
  181.     {
  182.         if ($offset === null) {
  183.             $this->add($value);
  184.             return;
  185.         }
  186.         $this->set($offset$value);
  187.     }
  188.     /**
  189.      * Required by interface ArrayAccess.
  190.      *
  191.      * @param TKey $offset
  192.      *
  193.      * @return void
  194.      */
  195.     #[ReturnTypeWillChange]
  196.     public function offsetUnset(mixed $offset)
  197.     {
  198.         $this->remove($offset);
  199.     }
  200.     /**
  201.      * {@inheritDoc}
  202.      */
  203.     public function containsKey(string|int $key)
  204.     {
  205.         return isset($this->elements[$key]) || array_key_exists($key$this->elements);
  206.     }
  207.     /**
  208.      * {@inheritDoc}
  209.      */
  210.     public function contains(mixed $element)
  211.     {
  212.         return in_array($element$this->elementstrue);
  213.     }
  214.     /**
  215.      * {@inheritDoc}
  216.      */
  217.     public function exists(Closure $p)
  218.     {
  219.         foreach ($this->elements as $key => $element) {
  220.             if ($p($key$element)) {
  221.                 return true;
  222.             }
  223.         }
  224.         return false;
  225.     }
  226.     /**
  227.      * {@inheritDoc}
  228.      *
  229.      * @psalm-param TMaybeContained $element
  230.      *
  231.      * @return int|string|false
  232.      * @psalm-return (TMaybeContained is T ? TKey|false : false)
  233.      *
  234.      * @template TMaybeContained
  235.      */
  236.     public function indexOf($element)
  237.     {
  238.         return array_search($element$this->elementstrue);
  239.     }
  240.     /**
  241.      * {@inheritDoc}
  242.      */
  243.     public function get(string|int $key)
  244.     {
  245.         return $this->elements[$key] ?? null;
  246.     }
  247.     /**
  248.      * {@inheritDoc}
  249.      */
  250.     public function getKeys()
  251.     {
  252.         return array_keys($this->elements);
  253.     }
  254.     /**
  255.      * {@inheritDoc}
  256.      */
  257.     public function getValues()
  258.     {
  259.         return array_values($this->elements);
  260.     }
  261.     /**
  262.      * {@inheritDoc}
  263.      *
  264.      * @return int<0, max>
  265.      */
  266.     #[ReturnTypeWillChange]
  267.     public function count()
  268.     {
  269.         return count($this->elements);
  270.     }
  271.     /**
  272.      * {@inheritDoc}
  273.      */
  274.     public function set(string|int $keymixed $value)
  275.     {
  276.         $this->elements[$key] = $value;
  277.     }
  278.     /**
  279.      * {@inheritDoc}
  280.      *
  281.      * @psalm-suppress InvalidPropertyAssignmentValue
  282.      *
  283.      * This breaks assumptions about the template type, but it would
  284.      * be a backwards-incompatible change to remove this method
  285.      */
  286.     public function add(mixed $element)
  287.     {
  288.         $this->elements[] = $element;
  289.     }
  290.     /**
  291.      * {@inheritDoc}
  292.      */
  293.     public function isEmpty()
  294.     {
  295.         return empty($this->elements);
  296.     }
  297.     /**
  298.      * {@inheritDoc}
  299.      *
  300.      * @return Traversable<int|string, mixed>
  301.      * @psalm-return Traversable<TKey, T>
  302.      */
  303.     #[ReturnTypeWillChange]
  304.     public function getIterator()
  305.     {
  306.         return new ArrayIterator($this->elements);
  307.     }
  308.     /**
  309.      * {@inheritDoc}
  310.      *
  311.      * @psalm-param Closure(T):U $func
  312.      *
  313.      * @return static
  314.      * @psalm-return static<TKey, U>
  315.      *
  316.      * @psalm-template U
  317.      */
  318.     public function map(Closure $func)
  319.     {
  320.         return $this->createFrom(array_map($func$this->elements));
  321.     }
  322.     /**
  323.      * {@inheritDoc}
  324.      */
  325.     public function reduce(Closure $func$initial null)
  326.     {
  327.         return array_reduce($this->elements$func$initial);
  328.     }
  329.     /**
  330.      * {@inheritDoc}
  331.      *
  332.      * @return static
  333.      * @psalm-return static<TKey,T>
  334.      */
  335.     public function filter(Closure $p)
  336.     {
  337.         return $this->createFrom(array_filter($this->elements$pARRAY_FILTER_USE_BOTH));
  338.     }
  339.     /**
  340.      * {@inheritDoc}
  341.      */
  342.     public function findFirst(Closure $p)
  343.     {
  344.         foreach ($this->elements as $key => $element) {
  345.             if ($p($key$element)) {
  346.                 return $element;
  347.             }
  348.         }
  349.         return null;
  350.     }
  351.     /**
  352.      * {@inheritDoc}
  353.      */
  354.     public function forAll(Closure $p)
  355.     {
  356.         foreach ($this->elements as $key => $element) {
  357.             if (! $p($key$element)) {
  358.                 return false;
  359.             }
  360.         }
  361.         return true;
  362.     }
  363.     /**
  364.      * {@inheritDoc}
  365.      */
  366.     public function partition(Closure $p)
  367.     {
  368.         $matches $noMatches = [];
  369.         foreach ($this->elements as $key => $element) {
  370.             if ($p($key$element)) {
  371.                 $matches[$key] = $element;
  372.             } else {
  373.                 $noMatches[$key] = $element;
  374.             }
  375.         }
  376.         return [$this->createFrom($matches), $this->createFrom($noMatches)];
  377.     }
  378.     /**
  379.      * Returns a string representation of this object.
  380.      * {@inheritDoc}
  381.      *
  382.      * @return string
  383.      */
  384.     #[ReturnTypeWillChange]
  385.     public function __toString()
  386.     {
  387.         return self::class . '@' spl_object_hash($this);
  388.     }
  389.     /**
  390.      * {@inheritDoc}
  391.      */
  392.     public function clear()
  393.     {
  394.         $this->elements = [];
  395.     }
  396.     /**
  397.      * {@inheritDoc}
  398.      */
  399.     public function slice(int $offsetint|null $length null)
  400.     {
  401.         return array_slice($this->elements$offset$lengthtrue);
  402.     }
  403.     /** @psalm-return Collection<TKey, T>&Selectable<TKey,T> */
  404.     public function matching(Criteria $criteria)
  405.     {
  406.         $expr     $criteria->getWhereExpression();
  407.         $filtered $this->elements;
  408.         if ($expr) {
  409.             $visitor  = new ClosureExpressionVisitor();
  410.             $filter   $visitor->dispatch($expr);
  411.             $filtered array_filter($filtered$filter);
  412.         }
  413.         $orderings $criteria->getOrderings();
  414.         if ($orderings) {
  415.             $next null;
  416.             foreach (array_reverse($orderings) as $field => $ordering) {
  417.                 $next ClosureExpressionVisitor::sortByField($field$ordering === Criteria::DESC ? -1$next);
  418.             }
  419.             uasort($filtered$next);
  420.         }
  421.         $offset $criteria->getFirstResult();
  422.         $length $criteria->getMaxResults();
  423.         if ($offset || $length) {
  424.             $filtered array_slice($filtered, (int) $offset$lengthtrue);
  425.         }
  426.         return $this->createFrom($filtered);
  427.     }
  428. }