Collection.php 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * This file is part of Hyperf.
  5. *
  6. * @link https://www.hyperf.io
  7. * @document https://hyperf.wiki
  8. * @contact group@hyperf.io
  9. * @license https://github.com/hyperf/hyperf/blob/master/LICENSE
  10. */
  11. namespace Hyperf\Collection;
  12. use ArrayAccess;
  13. use ArrayIterator;
  14. use Closure;
  15. use Exception;
  16. use Hyperf\Collection\Traits\EnumeratesValues;
  17. use Hyperf\Contract\Arrayable;
  18. use Hyperf\Contract\Jsonable;
  19. use Hyperf\Macroable\Macroable;
  20. use Hyperf\Stringable\Stringable;
  21. use InvalidArgumentException;
  22. use JsonSerializable;
  23. use stdClass;
  24. use Traversable;
  25. /**
  26. * Most of the methods in this file come from illuminate/collections,
  27. * thanks Laravel Team provide such a useful class.
  28. *
  29. * @template TKey of array-key
  30. * @template TValue
  31. * @template TTimesValue
  32. * @implements ArrayAccess<TKey, TValue>
  33. * @implements Enumerable<TKey, TValue>
  34. *
  35. * @property HigherOrderCollectionProxy $average
  36. * @property HigherOrderCollectionProxy $avg
  37. * @property HigherOrderCollectionProxy $contains
  38. * @property HigherOrderCollectionProxy $each
  39. * @property HigherOrderCollectionProxy $every
  40. * @property HigherOrderCollectionProxy $filter
  41. * @property HigherOrderCollectionProxy $first
  42. * @property HigherOrderCollectionProxy $flatMap
  43. * @property HigherOrderCollectionProxy $groupBy
  44. * @property HigherOrderCollectionProxy $keyBy
  45. * @property HigherOrderCollectionProxy $map
  46. * @property HigherOrderCollectionProxy $max
  47. * @property HigherOrderCollectionProxy $min
  48. * @property HigherOrderCollectionProxy $partition
  49. * @property HigherOrderCollectionProxy $reject
  50. * @property HigherOrderCollectionProxy $sortBy
  51. * @property HigherOrderCollectionProxy $sortByDesc
  52. * @property HigherOrderCollectionProxy $sum
  53. * @property HigherOrderCollectionProxy $unique
  54. */
  55. class Collection implements Enumerable, ArrayAccess
  56. {
  57. use EnumeratesValues;
  58. use Macroable;
  59. /**
  60. * The items contained in the collection.
  61. *
  62. * @var array<TKey, TValue>
  63. */
  64. protected array $items = [];
  65. /**
  66. * Create a new collection.
  67. * @param null|iterable<TKey,TValue>|Jsonable|JsonSerializable $items
  68. */
  69. public function __construct($items = [])
  70. {
  71. $this->items = $this->getArrayableItems($items);
  72. }
  73. /**
  74. * @param null|iterable<TKey,TValue>|Jsonable|JsonSerializable $items
  75. * @return static<TKey, TValue>
  76. */
  77. public function fill($items = [])
  78. {
  79. $this->items = $this->getArrayableItems($items);
  80. return $this;
  81. }
  82. /**
  83. * Get all of the items in the collection.
  84. *
  85. * @return array<TKey, TValue>
  86. */
  87. public function all(): array
  88. {
  89. return $this->items;
  90. }
  91. /**
  92. * Get the median of a given key.
  93. *
  94. * @param null|array<array-key, string>|string $key
  95. * @return null|float|int
  96. */
  97. public function median($key = null)
  98. {
  99. $values = (isset($key) ? $this->pluck($key) : $this)->filter(function ($item) {
  100. return ! is_null($item);
  101. })->sort()->values();
  102. $count = $values->count();
  103. if ($count == 0) {
  104. return null;
  105. }
  106. $middle = (int) ($count / 2);
  107. if ($count % 2) {
  108. return $values->get($middle);
  109. }
  110. return (new static([
  111. $values->get($middle - 1),
  112. $values->get($middle),
  113. ]))->average();
  114. }
  115. /**
  116. * Get the mode of a given key.
  117. *
  118. * @param null|array<array-key, string>|string $key
  119. * @return null|array<int, float|int>
  120. */
  121. public function mode($key = null)
  122. {
  123. if ($this->count() == 0) {
  124. return null;
  125. }
  126. $collection = isset($key) ? $this->pluck($key) : $this;
  127. /**
  128. * @template TValue of array-key
  129. * @phpstan-ignore-next-line
  130. * @var static<TValue, int> $counts
  131. */
  132. $counts = new self();
  133. $collection->each(function ($value) use ($counts) {
  134. $counts[$value] = isset($counts[$value]) ? $counts[$value] + 1 : 1;
  135. });
  136. $sorted = $counts->sort();
  137. $highestValue = $sorted->last();
  138. return $sorted->filter(function ($value) use ($highestValue) {
  139. return $value == $highestValue;
  140. })->sort()->keys()->all();
  141. }
  142. /**
  143. * Collapse the collection of items into a single array.
  144. *
  145. * @return static<int, mixed>
  146. */
  147. public function collapse(): self
  148. {
  149. return new static(Arr::collapse($this->items));
  150. }
  151. /**
  152. * Determine if an item exists in the collection.
  153. *
  154. * @param null|mixed $operator
  155. * @param null|mixed $value
  156. * @param (callable(TValue): bool)|string|TValue $key
  157. */
  158. public function contains($key, $operator = null, $value = null): bool
  159. {
  160. if (func_num_args() === 1) {
  161. if ($this->useAsCallable($key)) {
  162. $placeholder = new stdClass();
  163. return $this->first($key, $placeholder) !== $placeholder;
  164. }
  165. return in_array($key, $this->items);
  166. }
  167. return $this->contains($this->operatorForWhere(...func_get_args()));
  168. }
  169. /**
  170. * Determine if the collection contains a single item.
  171. */
  172. public function containsOneItem(): bool
  173. {
  174. return $this->count() === 1;
  175. }
  176. /**
  177. * Determine if an item exists in the collection using strict comparison.
  178. *
  179. * @param null|TValue $value
  180. * @param callable|TKey|TValue $key
  181. */
  182. public function containsStrict($key, $value = null): bool
  183. {
  184. if (func_num_args() === 2) {
  185. return $this->contains(function ($item) use ($key, $value) {
  186. return data_get($item, $key) === $value;
  187. });
  188. }
  189. if ($this->useAsCallable($key)) {
  190. return ! is_null($this->first($key));
  191. }
  192. return in_array($key, $this->items, true);
  193. }
  194. /**
  195. * Cross join with the given lists, returning all possible permutations.
  196. */
  197. public function crossJoin(...$lists): static
  198. {
  199. return new static(Arr::crossJoin($this->items, ...array_map([$this, 'getArrayableItems'], $lists)));
  200. }
  201. /**
  202. * Determine if an item is not contained in the collection.
  203. *
  204. * @param null|mixed $operator
  205. * @param null|mixed $value
  206. * @param (callable(TValue): bool)|string|TValue $key
  207. */
  208. public function doesntContain($key, $operator = null, $value = null): bool
  209. {
  210. return ! $this->contains(...func_get_args());
  211. }
  212. /**
  213. * Flatten a multi-dimensional associative array with dots.
  214. *
  215. * @return static<TKey, TValue>
  216. */
  217. public function dot(): static
  218. {
  219. return new static(Arr::dot($this->all()));
  220. }
  221. /**
  222. * Convert a flatten "dot" notation array into an expanded array.
  223. */
  224. public function undot(): static
  225. {
  226. return new static(Arr::undot($this->all()));
  227. }
  228. /**
  229. * Get the items in the collection that are not present in the given items.
  230. *
  231. * @param Arrayable<array-key, TValue>|iterable<array-key, TValue> $items
  232. * @return static<TKey, TValue>
  233. */
  234. public function diff($items): static
  235. {
  236. return new static(array_diff($this->items, $this->getArrayableItems($items)));
  237. }
  238. /**
  239. * Get the items in the collection that are not present in the given items.
  240. *
  241. * @param Arrayable<array-key, TValue>|iterable<array-key, TValue> $items
  242. * @param callable(TValue): int $callback
  243. * @return static<TKey, TValue>
  244. */
  245. public function diffUsing($items, callable $callback): static
  246. {
  247. return new static(array_udiff($this->items, $this->getArrayableItems($items), $callback));
  248. }
  249. /**
  250. * Get the items in the collection whose keys and values are not present in the given items.
  251. *
  252. * @param Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
  253. * @return static<TKey, TValue>
  254. */
  255. public function diffAssoc($items): static
  256. {
  257. return new static(array_diff_assoc($this->items, $this->getArrayableItems($items)));
  258. }
  259. /**
  260. * Get the items in the collection whose keys and values are not present in the given items.
  261. *
  262. * @param Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
  263. * @param callable(TKey): int $callback
  264. * @return static<TKey, TValue>
  265. */
  266. public function diffAssocUsing($items, callable $callback): static
  267. {
  268. return new static(array_diff_uassoc($this->items, $this->getArrayableItems($items), $callback));
  269. }
  270. /**
  271. * Get the items in the collection whose keys are not present in the given items.
  272. *
  273. * @param Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
  274. * @return static<TKey, TValue>
  275. */
  276. public function diffKeys($items): static
  277. {
  278. return new static(array_diff_key($this->items, $this->getArrayableItems($items)));
  279. }
  280. /**
  281. * Get the items in the collection whose keys are not present in the given items.
  282. *
  283. * @param Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
  284. * @param callable(TKey): int $callback
  285. * @return static<TKey, TValue>
  286. */
  287. public function diffKeysUsing($items, callable $callback): static
  288. {
  289. return new static(array_diff_ukey($this->items, $this->getArrayableItems($items), $callback));
  290. }
  291. /**
  292. * Get all items except for those with the specified keys.
  293. *
  294. * @param null|array<array-key, TKey>|static<array-key, TKey> $keys
  295. * @return static<TKey, TValue>
  296. */
  297. public function except($keys): static
  298. {
  299. if (is_null($keys)) {
  300. return new static($this->items);
  301. }
  302. if ($keys instanceof self) {
  303. $keys = $keys->all();
  304. } elseif (! is_array($keys)) {
  305. $keys = func_get_args();
  306. }
  307. return new static(Arr::except($this->items, $keys));
  308. }
  309. /**
  310. * Run a filter over each of the items.
  311. *
  312. * @param null|(callable(TValue, TKey): bool) $callback
  313. * @return static<TKey, TValue>
  314. */
  315. public function filter(?callable $callback = null): static
  316. {
  317. if ($callback) {
  318. return new static(Arr::where($this->items, $callback));
  319. }
  320. return new static(array_filter($this->items));
  321. }
  322. /**
  323. * Get the first item from the collection.
  324. *
  325. * @template TFirstDefault
  326. *
  327. * @param null|(callable(TValue, TKey): bool) $callback
  328. * @param (Closure(): TFirstDefault)|TFirstDefault $default
  329. * @return TFirstDefault|TValue
  330. */
  331. public function first(?callable $callback = null, $default = null)
  332. {
  333. return Arr::first($this->items, $callback, $default);
  334. }
  335. /**
  336. * Get the first item by the given key value pair.
  337. *
  338. * @return null|TValue
  339. */
  340. public function firstWhere(callable|string $key, mixed $operator = null, mixed $value = null): mixed
  341. {
  342. return $this->first($this->operatorForWhere(...func_get_args()));
  343. }
  344. /**
  345. * Get a flattened array of the items in the collection.
  346. *
  347. * @param float|int $depth
  348. * @return static<int, mixed>
  349. */
  350. public function flatten($depth = INF): self
  351. {
  352. return new static(Arr::flatten($this->items, $depth));
  353. }
  354. /**
  355. * Flip the items in the collection.
  356. *
  357. * @return static<TKey, TValue>
  358. */
  359. public function flip(): self|static
  360. {
  361. return new static(array_flip($this->items));
  362. }
  363. /**
  364. * Remove an item from the collection by key.
  365. *
  366. * @param \Hyperf\Contract\Arrayable<array-key, TValue>|iterable<array-key, TKey>|TKey $keys
  367. * @return $this
  368. */
  369. public function forget($keys): static
  370. {
  371. foreach ($this->getArrayableItems($keys) as $key) {
  372. $this->offsetUnset($key);
  373. }
  374. return $this;
  375. }
  376. /**
  377. * Get an item from the collection by key.
  378. *
  379. * @template TGetDefault
  380. *
  381. * @param TKey $key
  382. * @param (Closure(): TGetDefault)|TGetDefault $default
  383. * @return TGetDefault|TValue
  384. */
  385. public function get($key, $default = null)
  386. {
  387. if ($this->offsetExists($key)) {
  388. return $this->items[$key];
  389. }
  390. return value($default);
  391. }
  392. /**
  393. * Get an item from the collection by key or add it to collection if it does not exist.
  394. *
  395. * @template TGetOrPutValue
  396. *
  397. * @param (Closure(): TGetOrPutValue)|TGetOrPutValue $value
  398. * @return TGetOrPutValue|TValue
  399. */
  400. public function getOrPut(int|string $key, mixed $value): mixed
  401. {
  402. if (array_key_exists($key, $this->items)) {
  403. return $this->items[$key];
  404. }
  405. $this->offsetSet($key, $value = value($value));
  406. return $value;
  407. }
  408. /**
  409. * Get an item from the collection by key or add it to collection if it does not exist.
  410. *
  411. * @template TGetOrPutValue
  412. *
  413. * @param (Closure(): TGetOrPutValue)|TGetOrPutValue $value
  414. * @return TGetOrPutValue|TValue
  415. */
  416. public function getOrSet(int|string $key, mixed $value): mixed
  417. {
  418. return $this->getOrPut($key, $value);
  419. }
  420. /**
  421. * Group an associative array by a field or using a callback.
  422. * @param mixed $groupBy
  423. */
  424. public function groupBy($groupBy, bool $preserveKeys = false): static
  425. {
  426. if (is_array($groupBy)) {
  427. $nextGroups = $groupBy;
  428. $groupBy = array_shift($nextGroups);
  429. }
  430. $groupBy = $this->valueRetriever($groupBy);
  431. $results = [];
  432. foreach ($this->items as $key => $value) {
  433. $groupKeys = $groupBy($value, $key);
  434. if (! is_array($groupKeys)) {
  435. $groupKeys = [$groupKeys];
  436. }
  437. foreach ($groupKeys as $groupKey) {
  438. $groupKey = is_bool($groupKey) ? (int) $groupKey : $groupKey;
  439. if (! array_key_exists($groupKey, $results)) {
  440. $results[$groupKey] = new static();
  441. }
  442. $results[$groupKey]->offsetSet($preserveKeys ? $key : null, $value);
  443. }
  444. }
  445. $result = new static($results);
  446. if (! empty($nextGroups)) {
  447. return $result->map->groupBy($nextGroups, $preserveKeys);
  448. }
  449. return $result;
  450. }
  451. /**
  452. * Key an associative array by a field or using a callback.
  453. *
  454. * @param array|(callable(TValue, TKey): array-key)|string $keyBy
  455. * @return static<TKey, TValue>
  456. */
  457. public function keyBy($keyBy): static
  458. {
  459. $keyBy = $this->valueRetriever($keyBy);
  460. $results = [];
  461. foreach ($this->items as $key => $item) {
  462. $resolvedKey = $keyBy($item, $key);
  463. if (is_object($resolvedKey)) {
  464. $resolvedKey = (string) $resolvedKey;
  465. }
  466. $results[$resolvedKey] = $item;
  467. }
  468. return new static($results);
  469. }
  470. /**
  471. * Determine if an item exists in the collection by key.
  472. * @param array<array-key, TKey>|TKey $key
  473. */
  474. public function has($key): bool
  475. {
  476. $keys = is_array($key) ? $key : func_get_args();
  477. foreach ($keys as $value) {
  478. if (! $this->offsetExists($value)) {
  479. return false;
  480. }
  481. }
  482. return true;
  483. }
  484. /**
  485. * Determine if any of the keys exist in the collection.
  486. *
  487. * @param array<array-key, TKey>|TKey $key
  488. */
  489. public function hasAny($key): bool
  490. {
  491. if ($this->isEmpty()) {
  492. return false;
  493. }
  494. $keys = is_array($key) ? $key : func_get_args();
  495. foreach ($keys as $value) {
  496. if ($this->has($value)) {
  497. return true;
  498. }
  499. }
  500. return false;
  501. }
  502. /**
  503. * Concatenate values of a given key as a string.
  504. */
  505. public function implode(array|callable|string $value, ?string $glue = null): string
  506. {
  507. if ($this->useAsCallable($value)) {
  508. return implode($glue ?? '', $this->map($value)->all());
  509. }
  510. $first = $this->first();
  511. if (is_array($first) || (is_object($first) && ! $first instanceof Stringable)) {
  512. return implode($glue ?? '', $this->pluck($value)->all());
  513. }
  514. return implode($value ?: '', $this->items);
  515. }
  516. /**
  517. * Intersect the collection with the given items.
  518. *
  519. * @param Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
  520. * @return static<TKey, TValue>
  521. */
  522. public function intersect(mixed $items): static
  523. {
  524. return new static(array_intersect($this->items, $this->getArrayableItems($items)));
  525. }
  526. /**
  527. * Intersect the collection with the given items with additional index check.
  528. *
  529. * @param Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
  530. * @return static<TKey, TValue>
  531. */
  532. public function intersectAssoc($items): static
  533. {
  534. return new static(array_intersect_assoc($this->items, $this->getArrayableItems($items)));
  535. }
  536. /**
  537. * Intersect the collection with the given items by key.
  538. * @param Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
  539. * @return static<TKey, TValue>
  540. */
  541. public function intersectByKeys($items): static
  542. {
  543. return new static(array_intersect_key($this->items, $this->getArrayableItems($items)));
  544. }
  545. /**
  546. * Determine if the collection is empty or not.
  547. */
  548. public function isEmpty(): bool
  549. {
  550. return empty($this->items);
  551. }
  552. /**
  553. * Get the keys of the collection items.
  554. * @return static<int, TKey>
  555. */
  556. public function keys(): self|static
  557. {
  558. return new static(array_keys($this->items));
  559. }
  560. /**
  561. * Get the last item from the collection.
  562. *
  563. * @template TLastDefault
  564. *
  565. * @param null|(callable(TValue, TKey): bool) $callback
  566. * @param (Closure(): TLastDefault)|TLastDefault $default
  567. * @return TLastDefault|TValue
  568. */
  569. public function last(?callable $callback = null, $default = null)
  570. {
  571. return Arr::last($this->items, $callback, $default);
  572. }
  573. /**
  574. * Get the values of a given key.
  575. *
  576. * @param array<array-key, string>|string $value
  577. * @return static<int, mixed>
  578. */
  579. public function pluck(array|string $value, ?string $key = null): self|static
  580. {
  581. return new static(Arr::pluck($this->items, $value, $key));
  582. }
  583. /**
  584. * Run a map over each of the items.
  585. *
  586. * @template TMapValue
  587. *
  588. * @param callable(TValue, TKey): TMapValue $callback
  589. * @return static<TKey, TMapValue>
  590. */
  591. public function map(callable $callback): self|static
  592. {
  593. $result = [];
  594. foreach ($this->items as $key => $value) {
  595. $result[$key] = $callback($value, $key);
  596. }
  597. return new static($result);
  598. }
  599. /**
  600. * Run a dictionary map over the items.
  601. * The callback should return an associative array with a single key/value pair.
  602. *
  603. * @template TMapToDictionaryKey of array-key
  604. * @template TMapToDictionaryValue
  605. *
  606. * @param callable(TValue, TKey): array<TMapToDictionaryKey, TMapToDictionaryValue> $callback
  607. * @return static<TMapToDictionaryKey, array<int, TMapToDictionaryValue>>
  608. */
  609. public function mapToDictionary(callable $callback): self|static
  610. {
  611. $dictionary = [];
  612. foreach ($this->items as $key => $item) {
  613. $pair = $callback($item, $key);
  614. $key = key($pair);
  615. $value = reset($pair);
  616. if (! isset($dictionary[$key])) {
  617. $dictionary[$key] = [];
  618. }
  619. $dictionary[$key][] = $value;
  620. }
  621. return new static($dictionary);
  622. }
  623. /**
  624. * Run an associative map over each of the items.
  625. * The callback should return an associative array with a single key/value pair.
  626. *
  627. * @template TMapWithKeysKey of array-key
  628. * @template TMapWithKeysValue
  629. *
  630. * @param callable(TValue, TKey): array<TMapWithKeysKey, TMapWithKeysValue> $callback
  631. * @return static<TMapWithKeysKey, TMapWithKeysValue>
  632. */
  633. public function mapWithKeys(callable $callback): self|static
  634. {
  635. return new static(Arr::mapWithKeys($this->items, $callback));
  636. }
  637. /**
  638. * Merge the collection with the given items.
  639. * @param Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
  640. * @return static<TKey, TValue>
  641. */
  642. public function merge($items): static
  643. {
  644. return new static(array_merge($this->items, $this->getArrayableItems($items)));
  645. }
  646. /**
  647. * Recursively merge the collection with the given items.
  648. *
  649. * @param Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
  650. * @return static<TKey, TValue>
  651. */
  652. public function mergeRecursive($items): static
  653. {
  654. return new static(array_merge_recursive($this->items, $this->getArrayableItems($items)));
  655. }
  656. /**
  657. * Create a collection by using this collection for keys and another for its values.
  658. *
  659. * @template TCombineValue
  660. *
  661. * @param Arrayable<array-key, TCombineValue>|iterable<array-key, TCombineValue> $values
  662. * @return static<TKey, TCombineValue>
  663. */
  664. public function combine($values): static
  665. {
  666. return new static(array_combine($this->all(), $this->getArrayableItems($values)));
  667. }
  668. /**
  669. * Union the collection with the given items.
  670. *
  671. * @param Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
  672. * @return static<TKey, TValue>
  673. */
  674. public function union($items): static
  675. {
  676. return new static($this->items + $this->getArrayableItems($items));
  677. }
  678. /**
  679. * Create a new collection consisting of every n-th element.
  680. *
  681. * @return static<TKey, TValue>
  682. */
  683. public function nth(int $step, int $offset = 0): static
  684. {
  685. $new = [];
  686. $position = 0;
  687. foreach ($this->items as $item) {
  688. if ($position % $step === $offset) {
  689. $new[] = $item;
  690. }
  691. ++$position;
  692. }
  693. return new static($new);
  694. }
  695. /**
  696. * Get the items with the specified keys.
  697. *
  698. * @param null|array<array-key, TKey>|static<array-key, TKey>|string $keys
  699. * @return static<TKey, TValue>
  700. */
  701. public function only($keys): static
  702. {
  703. if (is_null($keys)) {
  704. return new static($this->items);
  705. }
  706. if ($keys instanceof self) {
  707. $keys = $keys->all();
  708. }
  709. $keys = is_array($keys) ? $keys : func_get_args();
  710. return new static(Arr::only($this->items, $keys));
  711. }
  712. /**
  713. * Get and remove the last item from the collection.
  714. */
  715. public function pop()
  716. {
  717. return array_pop($this->items);
  718. }
  719. /**
  720. * Push an item onto the beginning of the collection.
  721. *
  722. * @param TValue $value
  723. * @param null|TKey $key
  724. * @return $this
  725. */
  726. public function prepend($value, $key = null): static
  727. {
  728. $this->items = Arr::prepend($this->items, $value, $key);
  729. return $this;
  730. }
  731. /**
  732. * Push an item onto the end of the collection.
  733. *
  734. * @param TValue $value
  735. * @return $this
  736. */
  737. public function push($value): static
  738. {
  739. $this->offsetSet(null, $value);
  740. return $this;
  741. }
  742. /**
  743. * Push all of the given items onto the collection.
  744. *
  745. * @param iterable<array-key, TValue> $source
  746. * @return static<TKey, TValue>
  747. */
  748. public function concat($source): static
  749. {
  750. $result = new static($this);
  751. foreach ($source as $item) {
  752. $result->push($item);
  753. }
  754. return $result;
  755. }
  756. /**
  757. * Get and remove an item from the collection.
  758. *
  759. * @template TPullDefault
  760. *
  761. * @param TKey $key
  762. * @param (Closure(): TPullDefault)|TPullDefault $default
  763. * @return TPullDefault|TValue
  764. */
  765. public function pull($key, $default = null)
  766. {
  767. return Arr::pull($this->items, $key, $default);
  768. }
  769. /**
  770. * Put an item in the collection by key.
  771. *
  772. * @param TKey $key
  773. * @param TValue $value
  774. * @return $this
  775. */
  776. public function put($key, $value): static
  777. {
  778. $this->offsetSet($key, $value);
  779. return $this;
  780. }
  781. /**
  782. * Get one or a specified number of items randomly from the collection.
  783. *
  784. * @return static<int, TValue>|TValue
  785. * @throws InvalidArgumentException
  786. */
  787. public function random(?int $number = null)
  788. {
  789. if (is_null($number)) {
  790. return Arr::random($this->items);
  791. }
  792. return new static(Arr::random($this->items, $number));
  793. }
  794. /**
  795. * Create a collection with the given range.
  796. *
  797. * @return static<int, int>
  798. */
  799. public static function range(float|int|string $from, float|int|string $to): static
  800. {
  801. return new static(range($from, $to));
  802. }
  803. /**
  804. * Replace the collection items with the given items.
  805. *
  806. * @param Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
  807. * @return static
  808. */
  809. public function replace($items)
  810. {
  811. return new static(array_replace($this->items, $this->getArrayableItems($items)));
  812. }
  813. /**
  814. * Recursively replace the collection items with the given items.
  815. *
  816. * @param Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
  817. * @return static
  818. */
  819. public function replaceRecursive($items)
  820. {
  821. return new static(array_replace_recursive($this->items, $this->getArrayableItems($items)));
  822. }
  823. /**
  824. * Reverse items order.
  825. *
  826. * @return static<TKey, TValue>
  827. */
  828. public function reverse(): static
  829. {
  830. return new static(array_reverse($this->items, true));
  831. }
  832. /**
  833. * Search the collection for a given value and return the corresponding key if successful.
  834. *
  835. * @param (callable(TValue,TKey): bool)|TValue $value
  836. * @return bool|TKey
  837. */
  838. public function search($value, bool $strict = false)
  839. {
  840. if (! $this->useAsCallable($value)) {
  841. return array_search($value, $this->items, $strict);
  842. }
  843. foreach ($this->items as $key => $item) {
  844. if (call_user_func($value, $item, $key)) {
  845. return $key;
  846. }
  847. }
  848. return false;
  849. }
  850. /**
  851. * Get and remove the first item from the collection.
  852. *
  853. * @return null|TValue
  854. */
  855. public function shift()
  856. {
  857. return array_shift($this->items);
  858. }
  859. /**
  860. * Shuffle the items in the collection.
  861. *
  862. * @return static<TKey, TValue>
  863. */
  864. public function shuffle(?int $seed = null): static
  865. {
  866. return new static(Arr::shuffle($this->items, $seed));
  867. }
  868. /**
  869. * Skip the first {$count} items.
  870. *
  871. * @return static<TKey, TValue>
  872. */
  873. public function skip(int $count): static
  874. {
  875. return $this->slice($count);
  876. }
  877. /**
  878. * Slice the underlying collection array.
  879. *
  880. * @return static<TKey, TValue>
  881. */
  882. public function slice(int $offset, ?int $length = null): static
  883. {
  884. return new static(array_slice($this->items, $offset, $length, true));
  885. }
  886. /**
  887. * Create chunks representing a "sliding window" view of the items in the collection.
  888. *
  889. * @return static<int, TTimesValue>
  890. */
  891. public function sliding(int $size = 2, int $step = 1): static
  892. {
  893. $chunks = (int) floor(($this->count() - $size) / $step) + 1;
  894. return static::times($chunks, fn ($number) => $this->slice(($number - 1) * $step, $size));
  895. }
  896. /**
  897. * Split a collection into a certain number of groups.
  898. *
  899. * @return static<int, static<TKey, TValue>>
  900. */
  901. public function split(int $numberOfGroups): static
  902. {
  903. if ($this->isEmpty()) {
  904. return new static();
  905. }
  906. $groups = new static();
  907. $groupSize = (int) floor($this->count() / $numberOfGroups);
  908. $remain = $this->count() % $numberOfGroups;
  909. $start = 0;
  910. for ($i = 0; $i < $numberOfGroups; ++$i) {
  911. $size = $groupSize;
  912. if ($i < $remain) {
  913. ++$size;
  914. }
  915. if ($size) {
  916. $groups->push(new static(array_slice($this->items, $start, $size)));
  917. $start += $size;
  918. }
  919. }
  920. return $groups;
  921. }
  922. /**
  923. * Chunk the underlying collection array.
  924. *
  925. * @return static<int, static<TKey, TValue>>
  926. */
  927. public function chunk(int $size): static
  928. {
  929. if ($size <= 0) {
  930. return new static();
  931. }
  932. $chunks = [];
  933. foreach (array_chunk($this->items, $size, true) as $chunk) {
  934. $chunks[] = new static($chunk);
  935. }
  936. return new static($chunks);
  937. }
  938. /**
  939. * Sort through each item with a callback.
  940. *
  941. * @param callable(TValue, TValue): int $callback
  942. * @return static<TKey, TValue>
  943. */
  944. public function sort(?callable $callback = null): static
  945. {
  946. $items = $this->items;
  947. $callback ? uasort($items, $callback) : asort($items);
  948. return new static($items);
  949. }
  950. /**
  951. * Sort the collection using the given callback.
  952. *
  953. * @param array|(callable(TValue, TKey): mixed)|string $callback
  954. * @return static<TKey, TValue>
  955. */
  956. public function sortBy($callback, int $options = SORT_REGULAR, bool $descending = false): static
  957. {
  958. if (is_array($callback) && ! is_callable($callback)) {
  959. return $this->sortByMany($callback);
  960. }
  961. $results = [];
  962. $callback = $this->valueRetriever($callback);
  963. // First we will loop through the items and get the comparator from a callback
  964. // function which we were given. Then, we will sort the returned values and
  965. // and grab the corresponding values for the sorted keys from this array.
  966. foreach ($this->items as $key => $value) {
  967. $results[$key] = $callback($value, $key);
  968. }
  969. $descending ? arsort($results, $options) : asort($results, $options);
  970. // Once we have sorted all of the keys in the array, we will loop through them
  971. // and grab the corresponding model so we can set the underlying items list
  972. // to the sorted version. Then we'll just return the collection instance.
  973. foreach (array_keys($results) as $key) {
  974. $results[$key] = $this->items[$key];
  975. }
  976. return new static($results);
  977. }
  978. /**
  979. * Sort the collection in descending order using the given callback.
  980. *
  981. * @param (callable(TValue, TKey): mixed)|string $callback
  982. * @return static<TKey, TValue>
  983. */
  984. public function sortByDesc($callback, int $options = SORT_REGULAR): static
  985. {
  986. return $this->sortBy($callback, $options, true);
  987. }
  988. /**
  989. * Sort items in descending order.
  990. *
  991. * @return static<TKey, TValue>
  992. */
  993. public function sortDesc(int $options = SORT_REGULAR): static
  994. {
  995. $items = $this->items;
  996. arsort($items, $options);
  997. return new static($items);
  998. }
  999. /**
  1000. * Sort the collection keys.
  1001. *
  1002. * @return static<TKey, TValue>
  1003. */
  1004. public function sortKeys(int $options = SORT_REGULAR, bool $descending = false): static
  1005. {
  1006. $items = $this->items;
  1007. $descending ? krsort($items, $options) : ksort($items, $options);
  1008. return new static($items);
  1009. }
  1010. /**
  1011. * Sort the collection keys in descending order.
  1012. *
  1013. * @return static<TKey, TValue>
  1014. */
  1015. public function sortKeysDesc(int $options = SORT_REGULAR): static
  1016. {
  1017. return $this->sortKeys($options, true);
  1018. }
  1019. /**
  1020. * Sort the collection keys using a callback.
  1021. *
  1022. * @param callable(TKey, TKey): int $callback
  1023. * @return static<TKey, TValue>
  1024. */
  1025. public function sortKeysUsing(callable $callback): static
  1026. {
  1027. $items = $this->items;
  1028. uksort($items, $callback);
  1029. return new static($items);
  1030. }
  1031. /**
  1032. * Splice a portion of the underlying collection array.
  1033. *
  1034. * @param array<array-key, TValue> $replacement
  1035. * @return static<TKey, TValue>
  1036. */
  1037. public function splice(int $offset, ?int $length = null, $replacement = []): static
  1038. {
  1039. if (func_num_args() === 1) {
  1040. return new static(array_splice($this->items, $offset));
  1041. }
  1042. return new static(array_splice($this->items, $offset, $length, $replacement));
  1043. }
  1044. /**
  1045. * Split a collection into a certain number of groups, and fill the first groups completely.
  1046. *
  1047. * @return static<int, static<TKey, TValue>>
  1048. */
  1049. public function splitIn(int $numberOfGroups)
  1050. {
  1051. return $this->chunk((int) ceil($this->count() / $numberOfGroups));
  1052. }
  1053. /**
  1054. * Take the first or last {$limit} items.
  1055. *
  1056. * @return static<TKey, TValue>
  1057. */
  1058. public function take(int $limit): static
  1059. {
  1060. if ($limit < 0) {
  1061. return $this->slice($limit, abs($limit));
  1062. }
  1063. return $this->slice(0, $limit);
  1064. }
  1065. /**
  1066. * Transform each item in the collection using a callback.
  1067. *
  1068. * @param callable(TValue, TKey): TValue $callback
  1069. * @return $this
  1070. */
  1071. public function transform(callable $callback): static
  1072. {
  1073. $this->items = $this->map($callback)->all();
  1074. return $this;
  1075. }
  1076. /**
  1077. * Prepend one or more items to the beginning of the collection.
  1078. *
  1079. * @param TValue ...$values
  1080. * @return $this
  1081. */
  1082. public function unshift(...$values)
  1083. {
  1084. array_unshift($this->items, ...$values);
  1085. return $this;
  1086. }
  1087. /**
  1088. * Reset the keys on the underlying array.
  1089. *
  1090. * @return static<TKey, TValue>
  1091. */
  1092. public function values(): static
  1093. {
  1094. return new static(array_values($this->items));
  1095. }
  1096. /**
  1097. * Zip the collection together with one or more arrays.
  1098. * e.g. new Collection([1, 2, 3])->zip([4, 5, 6]);
  1099. * => [[1, 4], [2, 5], [3, 6]].
  1100. *
  1101. * @template TZipValue
  1102. *
  1103. * @param Arrayable<array-key, TZipValue>|iterable<array-key, TZipValue> ...$items
  1104. * @return static<int, static<int, TValue|TZipValue>>
  1105. */
  1106. public function zip($items): self|static
  1107. {
  1108. $arrayableItems = array_map(function ($items) {
  1109. return $this->getArrayableItems($items);
  1110. }, func_get_args());
  1111. $params = array_merge([
  1112. function () {
  1113. return new static(func_get_args());
  1114. },
  1115. $this->items,
  1116. ], $arrayableItems);
  1117. return new static(call_user_func_array('array_map', $params));
  1118. }
  1119. /**
  1120. * Pad collection to the specified length with a value.
  1121. *
  1122. * @template TPadValue
  1123. *
  1124. * @param TPadValue $value
  1125. * @return static<int, TPadValue|TValue>
  1126. */
  1127. public function pad(int $size, $value): self|static
  1128. {
  1129. return new static(array_pad($this->items, $size, $value));
  1130. }
  1131. /**
  1132. * Retrieve duplicate items from the collection.
  1133. *
  1134. * @param null|(callable(TValue): bool)|string $callback
  1135. * @param bool $strict
  1136. * @return static
  1137. */
  1138. public function duplicates($callback = null, $strict = false)
  1139. {
  1140. $items = $this->map($this->valueRetriever($callback));
  1141. $uniqueItems = $items->unique(null, $strict);
  1142. $compare = $this->duplicateComparator($strict);
  1143. $duplicates = new static();
  1144. foreach ($items as $key => $value) {
  1145. if ($uniqueItems->isNotEmpty() && $compare($value, $uniqueItems->first())) {
  1146. $uniqueItems->shift();
  1147. } else {
  1148. $duplicates[$key] = $value;
  1149. }
  1150. }
  1151. return $duplicates;
  1152. }
  1153. /**
  1154. * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception.
  1155. *
  1156. * @param (callable(TValue, TKey): bool)|string $key
  1157. * @param mixed $operator
  1158. * @param mixed $value
  1159. * @return TValue
  1160. *
  1161. * @throws ItemNotFoundException
  1162. * @throws MultipleItemsFoundException
  1163. */
  1164. public function sole($key = null, $operator = null, $value = null)
  1165. {
  1166. $filter = func_num_args() > 1
  1167. ? $this->operatorForWhere(...func_get_args())
  1168. : $key;
  1169. $items = $this->unless($filter == null)->filter($filter);
  1170. $count = $items->count();
  1171. if ($count === 0) {
  1172. throw new ItemNotFoundException();
  1173. }
  1174. if ($count > 1) {
  1175. throw new MultipleItemsFoundException($count);
  1176. }
  1177. return $items->first();
  1178. }
  1179. /**
  1180. * Get an iterator for the items.
  1181. *
  1182. * @return ArrayIterator<TKey, TValue>
  1183. */
  1184. public function getIterator(): ArrayIterator
  1185. {
  1186. return new ArrayIterator($this->items);
  1187. }
  1188. /**
  1189. * Count the number of items in the collection.
  1190. */
  1191. public function count(): int
  1192. {
  1193. return count($this->items);
  1194. }
  1195. /**
  1196. * Get a base Support collection instance from this collection.
  1197. *
  1198. * @return Collection<TKey, TValue>
  1199. */
  1200. public function toBase()
  1201. {
  1202. return new self($this);
  1203. }
  1204. /**
  1205. * Determine if an item exists at an offset.
  1206. *
  1207. * @param TKey $offset
  1208. */
  1209. public function offsetExists(mixed $offset): bool
  1210. {
  1211. return isset($this->items[$offset]);
  1212. }
  1213. /**
  1214. * Get an item at a given offset.
  1215. *
  1216. * @param TKey $offset
  1217. * @return TValue
  1218. */
  1219. public function offsetGet(mixed $offset): mixed
  1220. {
  1221. return $this->items[$offset];
  1222. }
  1223. /**
  1224. * Set the item at a given offset.
  1225. *
  1226. * @param null|TKey $offset
  1227. * @param TValue $value
  1228. */
  1229. public function offsetSet(mixed $offset, mixed $value): void
  1230. {
  1231. if (is_null($offset)) {
  1232. $this->items[] = $value;
  1233. } else {
  1234. $this->items[$offset] = $value;
  1235. }
  1236. }
  1237. /**
  1238. * Unset the item at a given offset.
  1239. *
  1240. * @param TKey $offset
  1241. */
  1242. public function offsetUnset(mixed $offset): void
  1243. {
  1244. unset($this->items[$offset]);
  1245. }
  1246. /**
  1247. * Get the first item in the collection but throw an exception if no matching items exist.
  1248. *
  1249. * @param (callable(TValue, TKey): bool)|string $key
  1250. * @param mixed $operator
  1251. * @param mixed $value
  1252. * @return TValue
  1253. *
  1254. * @throws ItemNotFoundException
  1255. */
  1256. public function firstOrFail($key = null, $operator = null, $value = null)
  1257. {
  1258. $filter = func_num_args() > 1
  1259. ? $this->operatorForWhere(...func_get_args())
  1260. : $key;
  1261. $placeholder = new stdClass();
  1262. $item = $this->first($filter, $placeholder);
  1263. if ($item === $placeholder) {
  1264. throw new ItemNotFoundException();
  1265. }
  1266. return $item;
  1267. }
  1268. /**
  1269. * Join all items from the collection using a string. The final items can use a separate glue string.
  1270. *
  1271. * @param string $glue
  1272. * @param string $finalGlue
  1273. * @return string
  1274. */
  1275. public function join($glue, $finalGlue = '')
  1276. {
  1277. if ($finalGlue === '') {
  1278. return $this->implode($glue);
  1279. }
  1280. $count = $this->count();
  1281. if ($count === 0) {
  1282. return '';
  1283. }
  1284. if ($count === 1) {
  1285. return $this->last();
  1286. }
  1287. $collection = new static($this->items);
  1288. $finalItem = $collection->pop();
  1289. return $collection->implode($glue) . $finalGlue . $finalItem;
  1290. }
  1291. /**
  1292. * Intersect the collection with the given items with additional index check, using the callback.
  1293. *
  1294. * @param Arrayable<array-key, TValue>|iterable<array-key, TValue> $items
  1295. * @param callable(TValue, TValue): int $callback
  1296. * @return static
  1297. */
  1298. public function intersectAssocUsing($items, callable $callback)
  1299. {
  1300. return new static(array_intersect_uassoc($this->items, $this->getArrayableItems($items), $callback));
  1301. }
  1302. /**
  1303. * Intersect the collection with the given items, using the callback.
  1304. *
  1305. * @param Arrayable<array-key, TValue>|iterable<array-key, TValue> $items
  1306. * @param callable(TValue, TValue): int $callback
  1307. * @return static
  1308. */
  1309. public function intersectUsing($items, callable $callback)
  1310. {
  1311. return new static(array_uintersect($this->items, $this->getArrayableItems($items), $callback));
  1312. }
  1313. /**
  1314. * Retrieve duplicate items from the collection using strict comparison.
  1315. *
  1316. * @param null|(callable(TValue): bool)|string $callback
  1317. * @return static
  1318. */
  1319. public function duplicatesStrict($callback = null)
  1320. {
  1321. return $this->duplicates($callback, true);
  1322. }
  1323. /**
  1324. * Get a lazy collection for the items in this collection.
  1325. *
  1326. * @return LazyCollection<TKey, TValue>
  1327. */
  1328. public function lazy()
  1329. {
  1330. return new LazyCollection($this->items);
  1331. }
  1332. /**
  1333. * Skip items in the collection until the given condition is met.
  1334. *
  1335. * @param callable(TValue,TKey): bool|TValue $value
  1336. * @return static
  1337. */
  1338. public function skipUntil($value)
  1339. {
  1340. return new static($this->lazy()->skipUntil($value)->all());
  1341. }
  1342. /**
  1343. * Skip items in the collection while the given condition is met.
  1344. *
  1345. * @param callable(TValue,TKey): bool|TValue $value
  1346. * @return static
  1347. */
  1348. public function skipWhile($value)
  1349. {
  1350. return new static($this->lazy()->skipWhile($value)->all());
  1351. }
  1352. /**
  1353. * Chunk the collection into chunks with a callback.
  1354. *
  1355. * @param callable(TValue, TKey, static<int, TValue>): bool $callback
  1356. * @return static<int, static<int, TValue>>
  1357. */
  1358. public function chunkWhile(callable $callback)
  1359. {
  1360. return new static(
  1361. $this->lazy()->chunkWhile($callback)->mapInto(static::class)
  1362. );
  1363. }
  1364. /**
  1365. * Take items in the collection until the given condition is met.
  1366. *
  1367. * @param callable(TValue,TKey): bool|TValue $value
  1368. * @return static
  1369. */
  1370. public function takeUntil($value)
  1371. {
  1372. return new static($this->lazy()->takeUntil($value)->all());
  1373. }
  1374. /**
  1375. * Take items in the collection while the given condition is met.
  1376. *
  1377. * @param callable(TValue,TKey): bool|TValue $value
  1378. * @return static
  1379. */
  1380. public function takeWhile($value)
  1381. {
  1382. return new static($this->lazy()->takeWhile($value)->all());
  1383. }
  1384. /**
  1385. * Count the number of items in the collection by a field or using a callback.
  1386. *
  1387. * @param null|(callable(TValue, TKey): array-key)|string $countBy
  1388. * @return static<array-key, int>
  1389. */
  1390. public function countBy($countBy = null)
  1391. {
  1392. return new static($this->lazy()->countBy($countBy)->all());
  1393. }
  1394. /**
  1395. * Sort the collection using multiple comparisons.
  1396. *
  1397. * @return static
  1398. */
  1399. protected function sortByMany(array $comparisons = [])
  1400. {
  1401. $items = $this->items;
  1402. usort($items, function ($a, $b) use ($comparisons) {
  1403. foreach ($comparisons as $comparison) {
  1404. $comparison = Arr::wrap($comparison);
  1405. $prop = $comparison[0];
  1406. $ascending = Arr::get($comparison, 1, true) === true
  1407. || Arr::get($comparison, 1, true) === 'asc';
  1408. $result = 0;
  1409. if (! is_string($prop) && is_callable($prop)) {
  1410. $result = $prop($a, $b);
  1411. } else {
  1412. $values = [data_get($a, $prop), data_get($b, $prop)];
  1413. if (! $ascending) {
  1414. $values = array_reverse($values);
  1415. }
  1416. $result = $values[0] <=> $values[1];
  1417. }
  1418. if ($result === 0) {
  1419. continue;
  1420. }
  1421. return $result;
  1422. }
  1423. });
  1424. return new static($items);
  1425. }
  1426. /**
  1427. * Get the comparison function to detect duplicates.
  1428. *
  1429. * @param bool $strict
  1430. * @return callable(TValue, TValue): bool
  1431. */
  1432. protected function duplicateComparator($strict)
  1433. {
  1434. if ($strict) {
  1435. return fn ($a, $b) => $a === $b;
  1436. }
  1437. return fn ($a, $b) => $a == $b;
  1438. }
  1439. /**
  1440. * Results array of items from Collection or Arrayable.
  1441. * @param null|Arrayable<TKey,TValue>|iterable<TKey,TValue>|Jsonable|JsonSerializable|static<TKey,TValue> $items
  1442. * @return array<TKey,TValue>
  1443. */
  1444. protected function getArrayableItems($items): array
  1445. {
  1446. return match (true) {
  1447. is_array($items) => $items,
  1448. $items instanceof self => $items->all(),
  1449. $items instanceof Arrayable => $items->toArray(),
  1450. $items instanceof Jsonable => json_decode($items->__toString(), true),
  1451. $items instanceof JsonSerializable => $items->jsonSerialize(),
  1452. $items instanceof Traversable => iterator_to_array($items),
  1453. default => (array) $items,
  1454. };
  1455. }
  1456. }