ArrayCollection.php 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  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. * {@inheritDoc}
  148. *
  149. * @psalm-param TKey $offset
  150. *
  151. * @return bool
  152. */
  153. #[ReturnTypeWillChange]
  154. public function offsetExists($offset)
  155. {
  156. return $this->containsKey($offset);
  157. }
  158. /**
  159. * Required by interface ArrayAccess.
  160. *
  161. * {@inheritDoc}
  162. *
  163. * @psalm-param TKey $offset
  164. *
  165. * @return mixed
  166. */
  167. #[ReturnTypeWillChange]
  168. public function offsetGet($offset)
  169. {
  170. return $this->get($offset);
  171. }
  172. /**
  173. * Required by interface ArrayAccess.
  174. *
  175. * {@inheritDoc}
  176. *
  177. * @return void
  178. */
  179. #[ReturnTypeWillChange]
  180. public function offsetSet($offset, $value)
  181. {
  182. if (! isset($offset)) {
  183. $this->add($value);
  184. return;
  185. }
  186. $this->set($offset, $value);
  187. }
  188. /**
  189. * Required by interface ArrayAccess.
  190. *
  191. * {@inheritDoc}
  192. *
  193. * @psalm-param TKey $offset
  194. *
  195. * @return void
  196. */
  197. #[ReturnTypeWillChange]
  198. public function offsetUnset($offset)
  199. {
  200. $this->remove($offset);
  201. }
  202. /**
  203. * {@inheritDoc}
  204. */
  205. public function containsKey($key)
  206. {
  207. return isset($this->elements[$key]) || array_key_exists($key, $this->elements);
  208. }
  209. /**
  210. * {@inheritDoc}
  211. */
  212. public function contains($element)
  213. {
  214. return in_array($element, $this->elements, true);
  215. }
  216. /**
  217. * {@inheritDoc}
  218. */
  219. public function exists(Closure $p)
  220. {
  221. foreach ($this->elements as $key => $element) {
  222. if ($p($key, $element)) {
  223. return true;
  224. }
  225. }
  226. return false;
  227. }
  228. /**
  229. * {@inheritDoc}
  230. */
  231. public function indexOf($element)
  232. {
  233. return array_search($element, $this->elements, true);
  234. }
  235. /**
  236. * {@inheritDoc}
  237. */
  238. public function get($key)
  239. {
  240. return $this->elements[$key] ?? null;
  241. }
  242. /**
  243. * {@inheritDoc}
  244. */
  245. public function getKeys()
  246. {
  247. return array_keys($this->elements);
  248. }
  249. /**
  250. * {@inheritDoc}
  251. */
  252. public function getValues()
  253. {
  254. return array_values($this->elements);
  255. }
  256. /**
  257. * {@inheritDoc}
  258. *
  259. * @return int
  260. */
  261. #[ReturnTypeWillChange]
  262. public function count()
  263. {
  264. return count($this->elements);
  265. }
  266. /**
  267. * {@inheritDoc}
  268. */
  269. public function set($key, $value)
  270. {
  271. $this->elements[$key] = $value;
  272. }
  273. /**
  274. * {@inheritDoc}
  275. *
  276. * @psalm-suppress InvalidPropertyAssignmentValue
  277. *
  278. * This breaks assumptions about the template type, but it would
  279. * be a backwards-incompatible change to remove this method
  280. */
  281. public function add($element)
  282. {
  283. $this->elements[] = $element;
  284. return true;
  285. }
  286. /**
  287. * {@inheritDoc}
  288. */
  289. public function isEmpty()
  290. {
  291. return empty($this->elements);
  292. }
  293. /**
  294. * {@inheritDoc}
  295. *
  296. * @return Traversable<int|string, mixed>
  297. * @psalm-return Traversable<TKey,T>
  298. */
  299. #[ReturnTypeWillChange]
  300. public function getIterator()
  301. {
  302. return new ArrayIterator($this->elements);
  303. }
  304. /**
  305. * {@inheritDoc}
  306. *
  307. * @psalm-param Closure(T=):U $func
  308. *
  309. * @return static
  310. * @psalm-return static<TKey, U>
  311. *
  312. * @psalm-template U
  313. */
  314. public function map(Closure $func)
  315. {
  316. return $this->createFrom(array_map($func, $this->elements));
  317. }
  318. /**
  319. * {@inheritDoc}
  320. *
  321. * @return static
  322. * @psalm-return static<TKey,T>
  323. */
  324. public function filter(Closure $p)
  325. {
  326. return $this->createFrom(array_filter($this->elements, $p, ARRAY_FILTER_USE_BOTH));
  327. }
  328. /**
  329. * {@inheritDoc}
  330. */
  331. public function forAll(Closure $p)
  332. {
  333. foreach ($this->elements as $key => $element) {
  334. if (! $p($key, $element)) {
  335. return false;
  336. }
  337. }
  338. return true;
  339. }
  340. /**
  341. * {@inheritDoc}
  342. */
  343. public function partition(Closure $p)
  344. {
  345. $matches = $noMatches = [];
  346. foreach ($this->elements as $key => $element) {
  347. if ($p($key, $element)) {
  348. $matches[$key] = $element;
  349. } else {
  350. $noMatches[$key] = $element;
  351. }
  352. }
  353. return [$this->createFrom($matches), $this->createFrom($noMatches)];
  354. }
  355. /**
  356. * Returns a string representation of this object.
  357. *
  358. * @return string
  359. */
  360. public function __toString()
  361. {
  362. return self::class . '@' . spl_object_hash($this);
  363. }
  364. /**
  365. * {@inheritDoc}
  366. */
  367. public function clear()
  368. {
  369. $this->elements = [];
  370. }
  371. /**
  372. * {@inheritDoc}
  373. */
  374. public function slice($offset, $length = null)
  375. {
  376. return array_slice($this->elements, $offset, $length, true);
  377. }
  378. /**
  379. * {@inheritDoc}
  380. */
  381. public function matching(Criteria $criteria)
  382. {
  383. $expr = $criteria->getWhereExpression();
  384. $filtered = $this->elements;
  385. if ($expr) {
  386. $visitor = new ClosureExpressionVisitor();
  387. $filter = $visitor->dispatch($expr);
  388. $filtered = array_filter($filtered, $filter);
  389. }
  390. $orderings = $criteria->getOrderings();
  391. if ($orderings) {
  392. $next = null;
  393. foreach (array_reverse($orderings) as $field => $ordering) {
  394. $next = ClosureExpressionVisitor::sortByField($field, $ordering === Criteria::DESC ? -1 : 1, $next);
  395. }
  396. uasort($filtered, $next);
  397. }
  398. $offset = $criteria->getFirstResult();
  399. $length = $criteria->getMaxResults();
  400. if ($offset || $length) {
  401. $filtered = array_slice($filtered, (int) $offset, $length);
  402. }
  403. return $this->createFrom($filtered);
  404. }
  405. }