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. /** @phpstan-var TKey $offset */
  189. $this->set($offset, $value);
  190. }
  191. /**
  192. * Required by interface ArrayAccess.
  193. *
  194. * @param TKey $offset
  195. *
  196. * @return void
  197. */
  198. #[ReturnTypeWillChange]
  199. public function offsetUnset(mixed $offset)
  200. {
  201. $this->remove($offset);
  202. }
  203. /**
  204. * {@inheritDoc}
  205. */
  206. public function containsKey(string|int $key)
  207. {
  208. return isset($this->elements[$key]) || array_key_exists($key, $this->elements);
  209. }
  210. /**
  211. * {@inheritDoc}
  212. */
  213. public function contains(mixed $element)
  214. {
  215. return in_array($element, $this->elements, true);
  216. }
  217. /**
  218. * {@inheritDoc}
  219. */
  220. public function exists(Closure $p)
  221. {
  222. return array_any(
  223. $this->elements,
  224. static fn (mixed $element, mixed $key): bool => (bool) $p($key, $element),
  225. );
  226. }
  227. /**
  228. * {@inheritDoc}
  229. *
  230. * @phpstan-param TMaybeContained $element
  231. *
  232. * @return int|string|false
  233. * @phpstan-return (TMaybeContained is T ? TKey|false : false)
  234. *
  235. * @template TMaybeContained
  236. */
  237. public function indexOf($element)
  238. {
  239. return array_search($element, $this->elements, true);
  240. }
  241. /**
  242. * {@inheritDoc}
  243. */
  244. public function get(string|int $key)
  245. {
  246. return $this->elements[$key] ?? null;
  247. }
  248. /**
  249. * {@inheritDoc}
  250. */
  251. public function getKeys()
  252. {
  253. return array_keys($this->elements);
  254. }
  255. /**
  256. * {@inheritDoc}
  257. */
  258. public function getValues()
  259. {
  260. return array_values($this->elements);
  261. }
  262. /**
  263. * {@inheritDoc}
  264. *
  265. * @return int<0, max>
  266. */
  267. #[ReturnTypeWillChange]
  268. public function count()
  269. {
  270. return count($this->elements);
  271. }
  272. /**
  273. * {@inheritDoc}
  274. */
  275. public function set(string|int $key, mixed $value)
  276. {
  277. $this->elements[$key] = $value;
  278. }
  279. /**
  280. * {@inheritDoc}
  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. * @phpstan-return Traversable<TKey, T>
  301. */
  302. #[ReturnTypeWillChange]
  303. public function getIterator()
  304. {
  305. return new ArrayIterator($this->elements);
  306. }
  307. /**
  308. * {@inheritDoc}
  309. *
  310. * @phpstan-param Closure(T):U $func
  311. *
  312. * @return static
  313. * @phpstan-return static<TKey, U>
  314. *
  315. * @phpstan-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. * @phpstan-param Closure(T, TKey):bool $p
  332. *
  333. * @return static
  334. * @phpstan-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. return array_find(
  346. $this->elements,
  347. static fn (mixed $element, mixed $key): bool => (bool) $p($key, $element),
  348. );
  349. }
  350. /**
  351. * {@inheritDoc}
  352. */
  353. public function forAll(Closure $p)
  354. {
  355. return array_all(
  356. $this->elements,
  357. static fn (mixed $element, mixed $key): bool => (bool) $p($key, $element),
  358. );
  359. }
  360. /**
  361. * {@inheritDoc}
  362. */
  363. public function partition(Closure $p)
  364. {
  365. $matches = $noMatches = [];
  366. foreach ($this->elements as $key => $element) {
  367. if ($p($key, $element)) {
  368. $matches[$key] = $element;
  369. } else {
  370. $noMatches[$key] = $element;
  371. }
  372. }
  373. return [$this->createFrom($matches), $this->createFrom($noMatches)];
  374. }
  375. /**
  376. * Returns a string representation of this object.
  377. * {@inheritDoc}
  378. *
  379. * @return string
  380. */
  381. #[ReturnTypeWillChange]
  382. public function __toString()
  383. {
  384. return self::class . '@' . spl_object_hash($this);
  385. }
  386. /**
  387. * {@inheritDoc}
  388. */
  389. public function clear()
  390. {
  391. $this->elements = [];
  392. }
  393. /**
  394. * {@inheritDoc}
  395. */
  396. public function slice(int $offset, int|null $length = null)
  397. {
  398. return array_slice($this->elements, $offset, $length, true);
  399. }
  400. /** @phpstan-return Collection<TKey, T>&Selectable<TKey,T> */
  401. public function matching(Criteria $criteria)
  402. {
  403. $accessRawFieldValues = $criteria->isRawFieldValueAccessEnabled();
  404. $expr = $criteria->getWhereExpression();
  405. $filtered = $this->elements;
  406. if ($expr) {
  407. $visitor = new ClosureExpressionVisitor($accessRawFieldValues);
  408. $filter = $visitor->dispatch($expr);
  409. $filtered = array_filter($filtered, $filter);
  410. }
  411. $orderings = $criteria->orderings();
  412. if ($orderings) {
  413. $next = null;
  414. foreach (array_reverse($orderings) as $field => $ordering) {
  415. $next = ClosureExpressionVisitor::sortByField($field, $ordering === Order::Descending ? -1 : 1, $next, $accessRawFieldValues);
  416. }
  417. uasort($filtered, $next);
  418. }
  419. $offset = $criteria->getFirstResult();
  420. $length = $criteria->getMaxResults();
  421. if ($offset !== null && $offset > 0 || $length !== null && $length > 0) {
  422. $filtered = array_slice($filtered, (int) $offset, $length, true);
  423. }
  424. return $this->createFrom($filtered);
  425. }
  426. }