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

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