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