vendor/doctrine/collections/lib/Doctrine/Common/Collections/ArrayCollection.php line 45

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