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