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