1<?php
2
3namespace Illuminate\Support;
4
5use stdClass;
6use Countable;
7use Exception;
8use ArrayAccess;
9use Traversable;
10use ArrayIterator;
11use CachingIterator;
12use JsonSerializable;
13use IteratorAggregate;
14use Illuminate\Support\Traits\Macroable;
15use Illuminate\Contracts\Support\Jsonable;
16use Symfony\Component\VarDumper\VarDumper;
17use Illuminate\Contracts\Support\Arrayable;
18
19/**
20 * @property-read HigherOrderCollectionProxy $average
21 * @property-read HigherOrderCollectionProxy $avg
22 * @property-read HigherOrderCollectionProxy $contains
23 * @property-read HigherOrderCollectionProxy $each
24 * @property-read HigherOrderCollectionProxy $every
25 * @property-read HigherOrderCollectionProxy $filter
26 * @property-read HigherOrderCollectionProxy $first
27 * @property-read HigherOrderCollectionProxy $flatMap
28 * @property-read HigherOrderCollectionProxy $groupBy
29 * @property-read HigherOrderCollectionProxy $keyBy
30 * @property-read HigherOrderCollectionProxy $map
31 * @property-read HigherOrderCollectionProxy $max
32 * @property-read HigherOrderCollectionProxy $min
33 * @property-read HigherOrderCollectionProxy $partition
34 * @property-read HigherOrderCollectionProxy $reject
35 * @property-read HigherOrderCollectionProxy $sortBy
36 * @property-read HigherOrderCollectionProxy $sortByDesc
37 * @property-read HigherOrderCollectionProxy $sum
38 * @property-read HigherOrderCollectionProxy $unique
39 */
40class Collection implements ArrayAccess, Arrayable, Countable, IteratorAggregate, Jsonable, JsonSerializable
41{
42    use Macroable;
43
44    /**
45     * The items contained in the collection.
46     *
47     * @var array
48     */
49    protected $items = [];
50
51    /**
52     * The methods that can be proxied.
53     *
54     * @var array
55     */
56    protected static $proxies = [
57        'average', 'avg', 'contains', 'each', 'every', 'filter', 'first',
58        'flatMap', 'groupBy', 'keyBy', 'map', 'max', 'min', 'partition',
59        'reject', 'some', 'sortBy', 'sortByDesc', 'sum', 'unique',
60    ];
61
62    /**
63     * Create a new collection.
64     *
65     * @param  mixed  $items
66     * @return void
67     */
68    public function __construct($items = [])
69    {
70        $this->items = $this->getArrayableItems($items);
71    }
72
73    /**
74     * Create a new collection instance if the value isn't one already.
75     *
76     * @param  mixed  $items
77     * @return static
78     */
79    public static function make($items = [])
80    {
81        return new static($items);
82    }
83
84    /**
85     * Wrap the given value in a collection if applicable.
86     *
87     * @param  mixed  $value
88     * @return static
89     */
90    public static function wrap($value)
91    {
92        return $value instanceof self
93            ? new static($value)
94            : new static(Arr::wrap($value));
95    }
96
97    /**
98     * Get the underlying items from the given collection if applicable.
99     *
100     * @param  array|static  $value
101     * @return array
102     */
103    public static function unwrap($value)
104    {
105        return $value instanceof self ? $value->all() : $value;
106    }
107
108    /**
109     * Create a new collection by invoking the callback a given amount of times.
110     *
111     * @param  int  $number
112     * @param  callable  $callback
113     * @return static
114     */
115    public static function times($number, callable $callback = null)
116    {
117        if ($number < 1) {
118            return new static;
119        }
120
121        if (is_null($callback)) {
122            return new static(range(1, $number));
123        }
124
125        return (new static(range(1, $number)))->map($callback);
126    }
127
128    /**
129     * Get all of the items in the collection.
130     *
131     * @return array
132     */
133    public function all()
134    {
135        return $this->items;
136    }
137
138    /**
139     * Get the average value of a given key.
140     *
141     * @param  callable|string|null  $callback
142     * @return mixed
143     */
144    public function avg($callback = null)
145    {
146        $callback = $this->valueRetriever($callback);
147
148        $items = $this->map(function ($value) use ($callback) {
149            return $callback($value);
150        })->filter(function ($value) {
151            return ! is_null($value);
152        });
153
154        if ($count = $items->count()) {
155            return $items->sum() / $count;
156        }
157    }
158
159    /**
160     * Alias for the "avg" method.
161     *
162     * @param  callable|string|null  $callback
163     * @return mixed
164     */
165    public function average($callback = null)
166    {
167        return $this->avg($callback);
168    }
169
170    /**
171     * Get the median of a given key.
172     *
173     * @param  string|array|null $key
174     * @return mixed
175     */
176    public function median($key = null)
177    {
178        $values = (isset($key) ? $this->pluck($key) : $this)
179            ->filter(function ($item) {
180                return ! is_null($item);
181            })->sort()->values();
182
183        $count = $values->count();
184
185        if ($count === 0) {
186            return;
187        }
188
189        $middle = (int) ($count / 2);
190
191        if ($count % 2) {
192            return $values->get($middle);
193        }
194
195        return (new static([
196            $values->get($middle - 1), $values->get($middle),
197        ]))->average();
198    }
199
200    /**
201     * Get the mode of a given key.
202     *
203     * @param  string|array|null  $key
204     * @return array|null
205     */
206    public function mode($key = null)
207    {
208        if ($this->count() === 0) {
209            return;
210        }
211
212        $collection = isset($key) ? $this->pluck($key) : $this;
213
214        $counts = new self;
215
216        $collection->each(function ($value) use ($counts) {
217            $counts[$value] = isset($counts[$value]) ? $counts[$value] + 1 : 1;
218        });
219
220        $sorted = $counts->sort();
221
222        $highestValue = $sorted->last();
223
224        return $sorted->filter(function ($value) use ($highestValue) {
225            return $value == $highestValue;
226        })->sort()->keys()->all();
227    }
228
229    /**
230     * Collapse the collection of items into a single array.
231     *
232     * @return static
233     */
234    public function collapse()
235    {
236        return new static(Arr::collapse($this->items));
237    }
238
239    /**
240     * Alias for the "contains" method.
241     *
242     * @param  mixed  $key
243     * @param  mixed  $operator
244     * @param  mixed  $value
245     * @return bool
246     */
247    public function some($key, $operator = null, $value = null)
248    {
249        return $this->contains(...func_get_args());
250    }
251
252    /**
253     * Determine if an item exists in the collection.
254     *
255     * @param  mixed  $key
256     * @param  mixed  $operator
257     * @param  mixed  $value
258     * @return bool
259     */
260    public function contains($key, $operator = null, $value = null)
261    {
262        if (func_num_args() === 1) {
263            if ($this->useAsCallable($key)) {
264                $placeholder = new stdClass;
265
266                return $this->first($key, $placeholder) !== $placeholder;
267            }
268
269            return in_array($key, $this->items);
270        }
271
272        return $this->contains($this->operatorForWhere(...func_get_args()));
273    }
274
275    /**
276     * Determine if an item exists in the collection using strict comparison.
277     *
278     * @param  mixed  $key
279     * @param  mixed  $value
280     * @return bool
281     */
282    public function containsStrict($key, $value = null)
283    {
284        if (func_num_args() === 2) {
285            return $this->contains(function ($item) use ($key, $value) {
286                return data_get($item, $key) === $value;
287            });
288        }
289
290        if ($this->useAsCallable($key)) {
291            return ! is_null($this->first($key));
292        }
293
294        return in_array($key, $this->items, true);
295    }
296
297    /**
298     * Cross join with the given lists, returning all possible permutations.
299     *
300     * @param  mixed  ...$lists
301     * @return static
302     */
303    public function crossJoin(...$lists)
304    {
305        return new static(Arr::crossJoin(
306            $this->items, ...array_map([$this, 'getArrayableItems'], $lists)
307        ));
308    }
309
310    /**
311     * Dump the collection and end the script.
312     *
313     * @param  mixed  ...$args
314     * @return void
315     */
316    public function dd(...$args)
317    {
318        call_user_func_array([$this, 'dump'], $args);
319
320        die(1);
321    }
322
323    /**
324     * Dump the collection.
325     *
326     * @return $this
327     */
328    public function dump()
329    {
330        (new static(func_get_args()))
331            ->push($this)
332            ->each(function ($item) {
333                VarDumper::dump($item);
334            });
335
336        return $this;
337    }
338
339    /**
340     * Get the items in the collection that are not present in the given items.
341     *
342     * @param  mixed  $items
343     * @return static
344     */
345    public function diff($items)
346    {
347        return new static(array_diff($this->items, $this->getArrayableItems($items)));
348    }
349
350    /**
351     * Get the items in the collection that are not present in the given items.
352     *
353     * @param  mixed  $items
354     * @param  callable  $callback
355     * @return static
356     */
357    public function diffUsing($items, callable $callback)
358    {
359        return new static(array_udiff($this->items, $this->getArrayableItems($items), $callback));
360    }
361
362    /**
363     * Get the items in the collection whose keys and values are not present in the given items.
364     *
365     * @param  mixed  $items
366     * @return static
367     */
368    public function diffAssoc($items)
369    {
370        return new static(array_diff_assoc($this->items, $this->getArrayableItems($items)));
371    }
372
373    /**
374     * Get the items in the collection whose keys and values are not present in the given items.
375     *
376     * @param  mixed  $items
377     * @param  callable  $callback
378     * @return static
379     */
380    public function diffAssocUsing($items, callable $callback)
381    {
382        return new static(array_diff_uassoc($this->items, $this->getArrayableItems($items), $callback));
383    }
384
385    /**
386     * Get the items in the collection whose keys are not present in the given items.
387     *
388     * @param  mixed  $items
389     * @return static
390     */
391    public function diffKeys($items)
392    {
393        return new static(array_diff_key($this->items, $this->getArrayableItems($items)));
394    }
395
396    /**
397     * Get the items in the collection whose keys are not present in the given items.
398     *
399     * @param  mixed   $items
400     * @param  callable  $callback
401     * @return static
402     */
403    public function diffKeysUsing($items, callable $callback)
404    {
405        return new static(array_diff_ukey($this->items, $this->getArrayableItems($items), $callback));
406    }
407
408    /**
409     * Retrieve duplicate items from the collection.
410     *
411     * @param  callable|null  $callback
412     * @param  bool  $strict
413     * @return static
414     */
415    public function duplicates($callback = null, $strict = false)
416    {
417        $items = $this->map($this->valueRetriever($callback));
418
419        $uniqueItems = $items->unique(null, $strict);
420
421        $compare = $this->duplicateComparator($strict);
422
423        $duplicates = new static;
424
425        foreach ($items as $key => $value) {
426            if ($uniqueItems->isNotEmpty() && $compare($value, $uniqueItems->first())) {
427                $uniqueItems->shift();
428            } else {
429                $duplicates[$key] = $value;
430            }
431        }
432
433        return $duplicates;
434    }
435
436    /**
437     * Retrieve duplicate items from the collection using strict comparison.
438     *
439     * @param  callable|null  $callback
440     * @return static
441     */
442    public function duplicatesStrict($callback = null)
443    {
444        return $this->duplicates($callback, true);
445    }
446
447    /**
448     * Get the comparison function to detect duplicates.
449     *
450     * @param  bool  $strict
451     * @return \Closure
452     */
453    protected function duplicateComparator($strict)
454    {
455        if ($strict) {
456            return function ($a, $b) {
457                return $a === $b;
458            };
459        }
460
461        return function ($a, $b) {
462            return $a == $b;
463        };
464    }
465
466    /**
467     * Execute a callback over each item.
468     *
469     * @param  callable  $callback
470     * @return $this
471     */
472    public function each(callable $callback)
473    {
474        foreach ($this->items as $key => $item) {
475            if ($callback($item, $key) === false) {
476                break;
477            }
478        }
479
480        return $this;
481    }
482
483    /**
484     * Execute a callback over each nested chunk of items.
485     *
486     * @param  callable  $callback
487     * @return static
488     */
489    public function eachSpread(callable $callback)
490    {
491        return $this->each(function ($chunk, $key) use ($callback) {
492            $chunk[] = $key;
493
494            return $callback(...$chunk);
495        });
496    }
497
498    /**
499     * Determine if all items in the collection pass the given test.
500     *
501     * @param  string|callable  $key
502     * @param  mixed  $operator
503     * @param  mixed  $value
504     * @return bool
505     */
506    public function every($key, $operator = null, $value = null)
507    {
508        if (func_num_args() === 1) {
509            $callback = $this->valueRetriever($key);
510
511            foreach ($this->items as $k => $v) {
512                if (! $callback($v, $k)) {
513                    return false;
514                }
515            }
516
517            return true;
518        }
519
520        return $this->every($this->operatorForWhere(...func_get_args()));
521    }
522
523    /**
524     * Get all items except for those with the specified keys.
525     *
526     * @param  \Illuminate\Support\Collection|mixed  $keys
527     * @return static
528     */
529    public function except($keys)
530    {
531        if ($keys instanceof self) {
532            $keys = $keys->all();
533        } elseif (! is_array($keys)) {
534            $keys = func_get_args();
535        }
536
537        return new static(Arr::except($this->items, $keys));
538    }
539
540    /**
541     * Run a filter over each of the items.
542     *
543     * @param  callable|null  $callback
544     * @return static
545     */
546    public function filter(callable $callback = null)
547    {
548        if ($callback) {
549            return new static(Arr::where($this->items, $callback));
550        }
551
552        return new static(array_filter($this->items));
553    }
554
555    /**
556     * Apply the callback if the value is truthy.
557     *
558     * @param  bool  $value
559     * @param  callable  $callback
560     * @param  callable  $default
561     * @return static|mixed
562     */
563    public function when($value, callable $callback, callable $default = null)
564    {
565        if ($value) {
566            return $callback($this, $value);
567        } elseif ($default) {
568            return $default($this, $value);
569        }
570
571        return $this;
572    }
573
574    /**
575     * Apply the callback if the collection is empty.
576     *
577     * @param  callable  $callback
578     * @param  callable  $default
579     * @return static|mixed
580     */
581    public function whenEmpty(callable $callback, callable $default = null)
582    {
583        return $this->when($this->isEmpty(), $callback, $default);
584    }
585
586    /**
587     * Apply the callback if the collection is not empty.
588     *
589     * @param  callable  $callback
590     * @param  callable  $default
591     * @return static|mixed
592     */
593    public function whenNotEmpty(callable $callback, callable $default = null)
594    {
595        return $this->when($this->isNotEmpty(), $callback, $default);
596    }
597
598    /**
599     * Apply the callback if the value is falsy.
600     *
601     * @param  bool  $value
602     * @param  callable  $callback
603     * @param  callable  $default
604     * @return static|mixed
605     */
606    public function unless($value, callable $callback, callable $default = null)
607    {
608        return $this->when(! $value, $callback, $default);
609    }
610
611    /**
612     * Apply the callback unless the collection is empty.
613     *
614     * @param  callable  $callback
615     * @param  callable  $default
616     * @return static|mixed
617     */
618    public function unlessEmpty(callable $callback, callable $default = null)
619    {
620        return $this->whenNotEmpty($callback, $default);
621    }
622
623    /**
624     * Apply the callback unless the collection is not empty.
625     *
626     * @param  callable  $callback
627     * @param  callable  $default
628     * @return static|mixed
629     */
630    public function unlessNotEmpty(callable $callback, callable $default = null)
631    {
632        return $this->whenEmpty($callback, $default);
633    }
634
635    /**
636     * Filter items by the given key value pair.
637     *
638     * @param  string  $key
639     * @param  mixed  $operator
640     * @param  mixed  $value
641     * @return static
642     */
643    public function where($key, $operator = null, $value = null)
644    {
645        return $this->filter($this->operatorForWhere(...func_get_args()));
646    }
647
648    /**
649     * Get an operator checker callback.
650     *
651     * @param  string  $key
652     * @param  string  $operator
653     * @param  mixed  $value
654     * @return \Closure
655     */
656    protected function operatorForWhere($key, $operator = null, $value = null)
657    {
658        if (func_num_args() === 1) {
659            $value = true;
660
661            $operator = '=';
662        }
663
664        if (func_num_args() === 2) {
665            $value = $operator;
666
667            $operator = '=';
668        }
669
670        return function ($item) use ($key, $operator, $value) {
671            $retrieved = data_get($item, $key);
672
673            $strings = array_filter([$retrieved, $value], function ($value) {
674                return is_string($value) || (is_object($value) && method_exists($value, '__toString'));
675            });
676
677            if (count($strings) < 2 && count(array_filter([$retrieved, $value], 'is_object')) == 1) {
678                return in_array($operator, ['!=', '<>', '!==']);
679            }
680
681            switch ($operator) {
682                default:
683                case '=':
684                case '==':  return $retrieved == $value;
685                case '!=':
686                case '<>':  return $retrieved != $value;
687                case '<':   return $retrieved < $value;
688                case '>':   return $retrieved > $value;
689                case '<=':  return $retrieved <= $value;
690                case '>=':  return $retrieved >= $value;
691                case '===': return $retrieved === $value;
692                case '!==': return $retrieved !== $value;
693            }
694        };
695    }
696
697    /**
698     * Filter items by the given key value pair using strict comparison.
699     *
700     * @param  string  $key
701     * @param  mixed  $value
702     * @return static
703     */
704    public function whereStrict($key, $value)
705    {
706        return $this->where($key, '===', $value);
707    }
708
709    /**
710     * Filter items by the given key value pair.
711     *
712     * @param  string  $key
713     * @param  mixed  $values
714     * @param  bool  $strict
715     * @return static
716     */
717    public function whereIn($key, $values, $strict = false)
718    {
719        $values = $this->getArrayableItems($values);
720
721        return $this->filter(function ($item) use ($key, $values, $strict) {
722            return in_array(data_get($item, $key), $values, $strict);
723        });
724    }
725
726    /**
727     * Filter items by the given key value pair using strict comparison.
728     *
729     * @param  string  $key
730     * @param  mixed  $values
731     * @return static
732     */
733    public function whereInStrict($key, $values)
734    {
735        return $this->whereIn($key, $values, true);
736    }
737
738    /**
739     * Filter items such that the value of the given key is between the given values.
740     *
741     * @param  string  $key
742     * @param  array  $values
743     * @return static
744     */
745    public function whereBetween($key, $values)
746    {
747        return $this->where($key, '>=', reset($values))->where($key, '<=', end($values));
748    }
749
750    /**
751     * Filter items such that the value of the given key is not between the given values.
752     *
753     * @param  string  $key
754     * @param  array  $values
755     * @return static
756     */
757    public function whereNotBetween($key, $values)
758    {
759        return $this->filter(function ($item) use ($key, $values) {
760            return data_get($item, $key) < reset($values) || data_get($item, $key) > end($values);
761        });
762    }
763
764    /**
765     * Filter items by the given key value pair.
766     *
767     * @param  string  $key
768     * @param  mixed  $values
769     * @param  bool  $strict
770     * @return static
771     */
772    public function whereNotIn($key, $values, $strict = false)
773    {
774        $values = $this->getArrayableItems($values);
775
776        return $this->reject(function ($item) use ($key, $values, $strict) {
777            return in_array(data_get($item, $key), $values, $strict);
778        });
779    }
780
781    /**
782     * Filter items by the given key value pair using strict comparison.
783     *
784     * @param  string  $key
785     * @param  mixed  $values
786     * @return static
787     */
788    public function whereNotInStrict($key, $values)
789    {
790        return $this->whereNotIn($key, $values, true);
791    }
792
793    /**
794     * Filter the items, removing any items that don't match the given type.
795     *
796     * @param  string  $type
797     * @return static
798     */
799    public function whereInstanceOf($type)
800    {
801        return $this->filter(function ($value) use ($type) {
802            return $value instanceof $type;
803        });
804    }
805
806    /**
807     * Get the first item from the collection passing the given truth test.
808     *
809     * @param  callable|null  $callback
810     * @param  mixed  $default
811     * @return mixed
812     */
813    public function first(callable $callback = null, $default = null)
814    {
815        return Arr::first($this->items, $callback, $default);
816    }
817
818    /**
819     * Get the first item by the given key value pair.
820     *
821     * @param  string  $key
822     * @param  mixed  $operator
823     * @param  mixed  $value
824     * @return mixed
825     */
826    public function firstWhere($key, $operator = null, $value = null)
827    {
828        return $this->first($this->operatorForWhere(...func_get_args()));
829    }
830
831    /**
832     * Get a flattened array of the items in the collection.
833     *
834     * @param  int  $depth
835     * @return static
836     */
837    public function flatten($depth = INF)
838    {
839        return new static(Arr::flatten($this->items, $depth));
840    }
841
842    /**
843     * Flip the items in the collection.
844     *
845     * @return static
846     */
847    public function flip()
848    {
849        return new static(array_flip($this->items));
850    }
851
852    /**
853     * Remove an item from the collection by key.
854     *
855     * @param  string|array  $keys
856     * @return $this
857     */
858    public function forget($keys)
859    {
860        foreach ((array) $keys as $key) {
861            $this->offsetUnset($key);
862        }
863
864        return $this;
865    }
866
867    /**
868     * Get an item from the collection by key.
869     *
870     * @param  mixed  $key
871     * @param  mixed  $default
872     * @return mixed
873     */
874    public function get($key, $default = null)
875    {
876        if ($this->offsetExists($key)) {
877            return $this->items[$key];
878        }
879
880        return value($default);
881    }
882
883    /**
884     * Group an associative array by a field or using a callback.
885     *
886     * @param  array|callable|string  $groupBy
887     * @param  bool  $preserveKeys
888     * @return static
889     */
890    public function groupBy($groupBy, $preserveKeys = false)
891    {
892        if (is_array($groupBy)) {
893            $nextGroups = $groupBy;
894
895            $groupBy = array_shift($nextGroups);
896        }
897
898        $groupBy = $this->valueRetriever($groupBy);
899
900        $results = [];
901
902        foreach ($this->items as $key => $value) {
903            $groupKeys = $groupBy($value, $key);
904
905            if (! is_array($groupKeys)) {
906                $groupKeys = [$groupKeys];
907            }
908
909            foreach ($groupKeys as $groupKey) {
910                $groupKey = is_bool($groupKey) ? (int) $groupKey : $groupKey;
911
912                if (! array_key_exists($groupKey, $results)) {
913                    $results[$groupKey] = new static;
914                }
915
916                $results[$groupKey]->offsetSet($preserveKeys ? $key : null, $value);
917            }
918        }
919
920        $result = new static($results);
921
922        if (! empty($nextGroups)) {
923            return $result->map->groupBy($nextGroups, $preserveKeys);
924        }
925
926        return $result;
927    }
928
929    /**
930     * Key an associative array by a field or using a callback.
931     *
932     * @param  callable|string  $keyBy
933     * @return static
934     */
935    public function keyBy($keyBy)
936    {
937        $keyBy = $this->valueRetriever($keyBy);
938
939        $results = [];
940
941        foreach ($this->items as $key => $item) {
942            $resolvedKey = $keyBy($item, $key);
943
944            if (is_object($resolvedKey)) {
945                $resolvedKey = (string) $resolvedKey;
946            }
947
948            $results[$resolvedKey] = $item;
949        }
950
951        return new static($results);
952    }
953
954    /**
955     * Determine if an item exists in the collection by key.
956     *
957     * @param  mixed  $key
958     * @return bool
959     */
960    public function has($key)
961    {
962        $keys = is_array($key) ? $key : func_get_args();
963
964        foreach ($keys as $value) {
965            if (! $this->offsetExists($value)) {
966                return false;
967            }
968        }
969
970        return true;
971    }
972
973    /**
974     * Concatenate values of a given key as a string.
975     *
976     * @param  string  $value
977     * @param  string  $glue
978     * @return string
979     */
980    public function implode($value, $glue = null)
981    {
982        $first = $this->first();
983
984        if (is_array($first) || is_object($first)) {
985            return implode($glue, $this->pluck($value)->all());
986        }
987
988        return implode($value, $this->items);
989    }
990
991    /**
992     * Intersect the collection with the given items.
993     *
994     * @param  mixed  $items
995     * @return static
996     */
997    public function intersect($items)
998    {
999        return new static(array_intersect($this->items, $this->getArrayableItems($items)));
1000    }
1001
1002    /**
1003     * Intersect the collection with the given items by key.
1004     *
1005     * @param  mixed  $items
1006     * @return static
1007     */
1008    public function intersectByKeys($items)
1009    {
1010        return new static(array_intersect_key(
1011            $this->items, $this->getArrayableItems($items)
1012        ));
1013    }
1014
1015    /**
1016     * Determine if the collection is empty or not.
1017     *
1018     * @return bool
1019     */
1020    public function isEmpty()
1021    {
1022        return empty($this->items);
1023    }
1024
1025    /**
1026     * Determine if the collection is not empty.
1027     *
1028     * @return bool
1029     */
1030    public function isNotEmpty()
1031    {
1032        return ! $this->isEmpty();
1033    }
1034
1035    /**
1036     * Determine if the given value is callable, but not a string.
1037     *
1038     * @param  mixed  $value
1039     * @return bool
1040     */
1041    protected function useAsCallable($value)
1042    {
1043        return ! is_string($value) && is_callable($value);
1044    }
1045
1046    /**
1047     * Join all items from the collection using a string. The final items can use a separate glue string.
1048     *
1049     * @param  string  $glue
1050     * @param  string  $finalGlue
1051     * @return string
1052     */
1053    public function join($glue, $finalGlue = '')
1054    {
1055        if ($finalGlue === '') {
1056            return $this->implode($glue);
1057        }
1058
1059        $count = $this->count();
1060
1061        if ($count === 0) {
1062            return '';
1063        }
1064
1065        if ($count === 1) {
1066            return $this->last();
1067        }
1068
1069        $collection = new static($this->items);
1070
1071        $finalItem = $collection->pop();
1072
1073        return $collection->implode($glue).$finalGlue.$finalItem;
1074    }
1075
1076    /**
1077     * Get the keys of the collection items.
1078     *
1079     * @return static
1080     */
1081    public function keys()
1082    {
1083        return new static(array_keys($this->items));
1084    }
1085
1086    /**
1087     * Get the last item from the collection.
1088     *
1089     * @param  callable|null  $callback
1090     * @param  mixed  $default
1091     * @return mixed
1092     */
1093    public function last(callable $callback = null, $default = null)
1094    {
1095        return Arr::last($this->items, $callback, $default);
1096    }
1097
1098    /**
1099     * Get the values of a given key.
1100     *
1101     * @param  string|array  $value
1102     * @param  string|null  $key
1103     * @return static
1104     */
1105    public function pluck($value, $key = null)
1106    {
1107        return new static(Arr::pluck($this->items, $value, $key));
1108    }
1109
1110    /**
1111     * Run a map over each of the items.
1112     *
1113     * @param  callable  $callback
1114     * @return static
1115     */
1116    public function map(callable $callback)
1117    {
1118        $keys = array_keys($this->items);
1119
1120        $items = array_map($callback, $this->items, $keys);
1121
1122        return new static(array_combine($keys, $items));
1123    }
1124
1125    /**
1126     * Run a map over each nested chunk of items.
1127     *
1128     * @param  callable  $callback
1129     * @return static
1130     */
1131    public function mapSpread(callable $callback)
1132    {
1133        return $this->map(function ($chunk, $key) use ($callback) {
1134            $chunk[] = $key;
1135
1136            return $callback(...$chunk);
1137        });
1138    }
1139
1140    /**
1141     * Run a dictionary map over the items.
1142     *
1143     * The callback should return an associative array with a single key/value pair.
1144     *
1145     * @param  callable  $callback
1146     * @return static
1147     */
1148    public function mapToDictionary(callable $callback)
1149    {
1150        $dictionary = [];
1151
1152        foreach ($this->items as $key => $item) {
1153            $pair = $callback($item, $key);
1154
1155            $key = key($pair);
1156
1157            $value = reset($pair);
1158
1159            if (! isset($dictionary[$key])) {
1160                $dictionary[$key] = [];
1161            }
1162
1163            $dictionary[$key][] = $value;
1164        }
1165
1166        return new static($dictionary);
1167    }
1168
1169    /**
1170     * Run a grouping map over the items.
1171     *
1172     * The callback should return an associative array with a single key/value pair.
1173     *
1174     * @param  callable  $callback
1175     * @return static
1176     */
1177    public function mapToGroups(callable $callback)
1178    {
1179        $groups = $this->mapToDictionary($callback);
1180
1181        return $groups->map([$this, 'make']);
1182    }
1183
1184    /**
1185     * Run an associative map over each of the items.
1186     *
1187     * The callback should return an associative array with a single key/value pair.
1188     *
1189     * @param  callable  $callback
1190     * @return static
1191     */
1192    public function mapWithKeys(callable $callback)
1193    {
1194        $result = [];
1195
1196        foreach ($this->items as $key => $value) {
1197            $assoc = $callback($value, $key);
1198
1199            foreach ($assoc as $mapKey => $mapValue) {
1200                $result[$mapKey] = $mapValue;
1201            }
1202        }
1203
1204        return new static($result);
1205    }
1206
1207    /**
1208     * Map a collection and flatten the result by a single level.
1209     *
1210     * @param  callable  $callback
1211     * @return static
1212     */
1213    public function flatMap(callable $callback)
1214    {
1215        return $this->map($callback)->collapse();
1216    }
1217
1218    /**
1219     * Map the values into a new class.
1220     *
1221     * @param  string  $class
1222     * @return static
1223     */
1224    public function mapInto($class)
1225    {
1226        return $this->map(function ($value, $key) use ($class) {
1227            return new $class($value, $key);
1228        });
1229    }
1230
1231    /**
1232     * Get the max value of a given key.
1233     *
1234     * @param  callable|string|null  $callback
1235     * @return mixed
1236     */
1237    public function max($callback = null)
1238    {
1239        $callback = $this->valueRetriever($callback);
1240
1241        return $this->filter(function ($value) {
1242            return ! is_null($value);
1243        })->reduce(function ($result, $item) use ($callback) {
1244            $value = $callback($item);
1245
1246            return is_null($result) || $value > $result ? $value : $result;
1247        });
1248    }
1249
1250    /**
1251     * Merge the collection with the given items.
1252     *
1253     * @param  mixed  $items
1254     * @return static
1255     */
1256    public function merge($items)
1257    {
1258        return new static(array_merge($this->items, $this->getArrayableItems($items)));
1259    }
1260
1261    /**
1262     * Recursively merge the collection with the given items.
1263     *
1264     * @param  mixed  $items
1265     * @return static
1266     */
1267    public function mergeRecursive($items)
1268    {
1269        return new static(array_merge_recursive($this->items, $this->getArrayableItems($items)));
1270    }
1271
1272    /**
1273     * Create a collection by using this collection for keys and another for its values.
1274     *
1275     * @param  mixed  $values
1276     * @return static
1277     */
1278    public function combine($values)
1279    {
1280        return new static(array_combine($this->all(), $this->getArrayableItems($values)));
1281    }
1282
1283    /**
1284     * Union the collection with the given items.
1285     *
1286     * @param  mixed  $items
1287     * @return static
1288     */
1289    public function union($items)
1290    {
1291        return new static($this->items + $this->getArrayableItems($items));
1292    }
1293
1294    /**
1295     * Get the min value of a given key.
1296     *
1297     * @param  callable|string|null  $callback
1298     * @return mixed
1299     */
1300    public function min($callback = null)
1301    {
1302        $callback = $this->valueRetriever($callback);
1303
1304        return $this->map(function ($value) use ($callback) {
1305            return $callback($value);
1306        })->filter(function ($value) {
1307            return ! is_null($value);
1308        })->reduce(function ($result, $value) {
1309            return is_null($result) || $value < $result ? $value : $result;
1310        });
1311    }
1312
1313    /**
1314     * Create a new collection consisting of every n-th element.
1315     *
1316     * @param  int  $step
1317     * @param  int  $offset
1318     * @return static
1319     */
1320    public function nth($step, $offset = 0)
1321    {
1322        $new = [];
1323
1324        $position = 0;
1325
1326        foreach ($this->items as $item) {
1327            if ($position % $step === $offset) {
1328                $new[] = $item;
1329            }
1330
1331            $position++;
1332        }
1333
1334        return new static($new);
1335    }
1336
1337    /**
1338     * Get the items with the specified keys.
1339     *
1340     * @param  mixed  $keys
1341     * @return static
1342     */
1343    public function only($keys)
1344    {
1345        if (is_null($keys)) {
1346            return new static($this->items);
1347        }
1348
1349        if ($keys instanceof self) {
1350            $keys = $keys->all();
1351        }
1352
1353        $keys = is_array($keys) ? $keys : func_get_args();
1354
1355        return new static(Arr::only($this->items, $keys));
1356    }
1357
1358    /**
1359     * "Paginate" the collection by slicing it into a smaller collection.
1360     *
1361     * @param  int  $page
1362     * @param  int  $perPage
1363     * @return static
1364     */
1365    public function forPage($page, $perPage)
1366    {
1367        $offset = max(0, ($page - 1) * $perPage);
1368
1369        return $this->slice($offset, $perPage);
1370    }
1371
1372    /**
1373     * Partition the collection into two arrays using the given callback or key.
1374     *
1375     * @param  callable|string  $key
1376     * @param  mixed  $operator
1377     * @param  mixed  $value
1378     * @return static
1379     */
1380    public function partition($key, $operator = null, $value = null)
1381    {
1382        $partitions = [new static, new static];
1383
1384        $callback = func_num_args() === 1
1385                ? $this->valueRetriever($key)
1386                : $this->operatorForWhere(...func_get_args());
1387
1388        foreach ($this->items as $key => $item) {
1389            $partitions[(int) ! $callback($item, $key)][$key] = $item;
1390        }
1391
1392        return new static($partitions);
1393    }
1394
1395    /**
1396     * Pass the collection to the given callback and return the result.
1397     *
1398     * @param  callable $callback
1399     * @return mixed
1400     */
1401    public function pipe(callable $callback)
1402    {
1403        return $callback($this);
1404    }
1405
1406    /**
1407     * Get and remove the last item from the collection.
1408     *
1409     * @return mixed
1410     */
1411    public function pop()
1412    {
1413        return array_pop($this->items);
1414    }
1415
1416    /**
1417     * Push an item onto the beginning of the collection.
1418     *
1419     * @param  mixed  $value
1420     * @param  mixed  $key
1421     * @return $this
1422     */
1423    public function prepend($value, $key = null)
1424    {
1425        $this->items = Arr::prepend($this->items, $value, $key);
1426
1427        return $this;
1428    }
1429
1430    /**
1431     * Push an item onto the end of the collection.
1432     *
1433     * @param  mixed  $value
1434     * @return $this
1435     */
1436    public function push($value)
1437    {
1438        $this->offsetSet(null, $value);
1439
1440        return $this;
1441    }
1442
1443    /**
1444     * Push all of the given items onto the collection.
1445     *
1446     * @param  iterable  $source
1447     * @return static
1448     */
1449    public function concat($source)
1450    {
1451        $result = new static($this);
1452
1453        foreach ($source as $item) {
1454            $result->push($item);
1455        }
1456
1457        return $result;
1458    }
1459
1460    /**
1461     * Get and remove an item from the collection.
1462     *
1463     * @param  mixed  $key
1464     * @param  mixed  $default
1465     * @return mixed
1466     */
1467    public function pull($key, $default = null)
1468    {
1469        return Arr::pull($this->items, $key, $default);
1470    }
1471
1472    /**
1473     * Put an item in the collection by key.
1474     *
1475     * @param  mixed  $key
1476     * @param  mixed  $value
1477     * @return $this
1478     */
1479    public function put($key, $value)
1480    {
1481        $this->offsetSet($key, $value);
1482
1483        return $this;
1484    }
1485
1486    /**
1487     * Get one or a specified number of items randomly from the collection.
1488     *
1489     * @param  int|null  $number
1490     * @return static|mixed
1491     *
1492     * @throws \InvalidArgumentException
1493     */
1494    public function random($number = null)
1495    {
1496        if (is_null($number)) {
1497            return Arr::random($this->items);
1498        }
1499
1500        return new static(Arr::random($this->items, $number));
1501    }
1502
1503    /**
1504     * Reduce the collection to a single value.
1505     *
1506     * @param  callable  $callback
1507     * @param  mixed  $initial
1508     * @return mixed
1509     */
1510    public function reduce(callable $callback, $initial = null)
1511    {
1512        return array_reduce($this->items, $callback, $initial);
1513    }
1514
1515    /**
1516     * Create a collection of all elements that do not pass a given truth test.
1517     *
1518     * @param  callable|mixed  $callback
1519     * @return static
1520     */
1521    public function reject($callback = true)
1522    {
1523        $useAsCallable = $this->useAsCallable($callback);
1524
1525        return $this->filter(function ($value, $key) use ($callback, $useAsCallable) {
1526            return $useAsCallable
1527                ? ! $callback($value, $key)
1528                : $value != $callback;
1529        });
1530    }
1531
1532    /**
1533     * Replace the collection items with the given items.
1534     *
1535     * @param  mixed  $items
1536     * @return static
1537     */
1538    public function replace($items)
1539    {
1540        return new static(array_replace($this->items, $this->getArrayableItems($items)));
1541    }
1542
1543    /**
1544     * Recursively replace the collection items with the given items.
1545     *
1546     * @param  mixed  $items
1547     * @return static
1548     */
1549    public function replaceRecursive($items)
1550    {
1551        return new static(array_replace_recursive($this->items, $this->getArrayableItems($items)));
1552    }
1553
1554    /**
1555     * Reverse items order.
1556     *
1557     * @return static
1558     */
1559    public function reverse()
1560    {
1561        return new static(array_reverse($this->items, true));
1562    }
1563
1564    /**
1565     * Search the collection for a given value and return the corresponding key if successful.
1566     *
1567     * @param  mixed  $value
1568     * @param  bool  $strict
1569     * @return mixed
1570     */
1571    public function search($value, $strict = false)
1572    {
1573        if (! $this->useAsCallable($value)) {
1574            return array_search($value, $this->items, $strict);
1575        }
1576
1577        foreach ($this->items as $key => $item) {
1578            if (call_user_func($value, $item, $key)) {
1579                return $key;
1580            }
1581        }
1582
1583        return false;
1584    }
1585
1586    /**
1587     * Get and remove the first item from the collection.
1588     *
1589     * @return mixed
1590     */
1591    public function shift()
1592    {
1593        return array_shift($this->items);
1594    }
1595
1596    /**
1597     * Shuffle the items in the collection.
1598     *
1599     * @param  int  $seed
1600     * @return static
1601     */
1602    public function shuffle($seed = null)
1603    {
1604        return new static(Arr::shuffle($this->items, $seed));
1605    }
1606
1607    /**
1608     * Slice the underlying collection array.
1609     *
1610     * @param  int  $offset
1611     * @param  int  $length
1612     * @return static
1613     */
1614    public function slice($offset, $length = null)
1615    {
1616        return new static(array_slice($this->items, $offset, $length, true));
1617    }
1618
1619    /**
1620     * Split a collection into a certain number of groups.
1621     *
1622     * @param  int  $numberOfGroups
1623     * @return static
1624     */
1625    public function split($numberOfGroups)
1626    {
1627        if ($this->isEmpty()) {
1628            return new static;
1629        }
1630
1631        $groups = new static;
1632
1633        $groupSize = floor($this->count() / $numberOfGroups);
1634
1635        $remain = $this->count() % $numberOfGroups;
1636
1637        $start = 0;
1638
1639        for ($i = 0; $i < $numberOfGroups; $i++) {
1640            $size = $groupSize;
1641
1642            if ($i < $remain) {
1643                $size++;
1644            }
1645
1646            if ($size) {
1647                $groups->push(new static(array_slice($this->items, $start, $size)));
1648
1649                $start += $size;
1650            }
1651        }
1652
1653        return $groups;
1654    }
1655
1656    /**
1657     * Chunk the underlying collection array.
1658     *
1659     * @param  int  $size
1660     * @return static
1661     */
1662    public function chunk($size)
1663    {
1664        if ($size <= 0) {
1665            return new static;
1666        }
1667
1668        $chunks = [];
1669
1670        foreach (array_chunk($this->items, $size, true) as $chunk) {
1671            $chunks[] = new static($chunk);
1672        }
1673
1674        return new static($chunks);
1675    }
1676
1677    /**
1678     * Sort through each item with a callback.
1679     *
1680     * @param  callable|null  $callback
1681     * @return static
1682     */
1683    public function sort(callable $callback = null)
1684    {
1685        $items = $this->items;
1686
1687        $callback
1688            ? uasort($items, $callback)
1689            : asort($items);
1690
1691        return new static($items);
1692    }
1693
1694    /**
1695     * Sort the collection using the given callback.
1696     *
1697     * @param  callable|string  $callback
1698     * @param  int  $options
1699     * @param  bool  $descending
1700     * @return static
1701     */
1702    public function sortBy($callback, $options = SORT_REGULAR, $descending = false)
1703    {
1704        $results = [];
1705
1706        $callback = $this->valueRetriever($callback);
1707
1708        // First we will loop through the items and get the comparator from a callback
1709        // function which we were given. Then, we will sort the returned values and
1710        // and grab the corresponding values for the sorted keys from this array.
1711        foreach ($this->items as $key => $value) {
1712            $results[$key] = $callback($value, $key);
1713        }
1714
1715        $descending ? arsort($results, $options)
1716            : asort($results, $options);
1717
1718        // Once we have sorted all of the keys in the array, we will loop through them
1719        // and grab the corresponding model so we can set the underlying items list
1720        // to the sorted version. Then we'll just return the collection instance.
1721        foreach (array_keys($results) as $key) {
1722            $results[$key] = $this->items[$key];
1723        }
1724
1725        return new static($results);
1726    }
1727
1728    /**
1729     * Sort the collection in descending order using the given callback.
1730     *
1731     * @param  callable|string  $callback
1732     * @param  int  $options
1733     * @return static
1734     */
1735    public function sortByDesc($callback, $options = SORT_REGULAR)
1736    {
1737        return $this->sortBy($callback, $options, true);
1738    }
1739
1740    /**
1741     * Sort the collection keys.
1742     *
1743     * @param  int  $options
1744     * @param  bool  $descending
1745     * @return static
1746     */
1747    public function sortKeys($options = SORT_REGULAR, $descending = false)
1748    {
1749        $items = $this->items;
1750
1751        $descending ? krsort($items, $options) : ksort($items, $options);
1752
1753        return new static($items);
1754    }
1755
1756    /**
1757     * Sort the collection keys in descending order.
1758     *
1759     * @param  int $options
1760     * @return static
1761     */
1762    public function sortKeysDesc($options = SORT_REGULAR)
1763    {
1764        return $this->sortKeys($options, true);
1765    }
1766
1767    /**
1768     * Splice a portion of the underlying collection array.
1769     *
1770     * @param  int  $offset
1771     * @param  int|null  $length
1772     * @param  mixed  $replacement
1773     * @return static
1774     */
1775    public function splice($offset, $length = null, $replacement = [])
1776    {
1777        if (func_num_args() === 1) {
1778            return new static(array_splice($this->items, $offset));
1779        }
1780
1781        return new static(array_splice($this->items, $offset, $length, $replacement));
1782    }
1783
1784    /**
1785     * Get the sum of the given values.
1786     *
1787     * @param  callable|string|null  $callback
1788     * @return mixed
1789     */
1790    public function sum($callback = null)
1791    {
1792        if (is_null($callback)) {
1793            return array_sum($this->items);
1794        }
1795
1796        $callback = $this->valueRetriever($callback);
1797
1798        return $this->reduce(function ($result, $item) use ($callback) {
1799            return $result + $callback($item);
1800        }, 0);
1801    }
1802
1803    /**
1804     * Take the first or last {$limit} items.
1805     *
1806     * @param  int  $limit
1807     * @return static
1808     */
1809    public function take($limit)
1810    {
1811        if ($limit < 0) {
1812            return $this->slice($limit, abs($limit));
1813        }
1814
1815        return $this->slice(0, $limit);
1816    }
1817
1818    /**
1819     * Pass the collection to the given callback and then return it.
1820     *
1821     * @param  callable  $callback
1822     * @return $this
1823     */
1824    public function tap(callable $callback)
1825    {
1826        $callback(new static($this->items));
1827
1828        return $this;
1829    }
1830
1831    /**
1832     * Transform each item in the collection using a callback.
1833     *
1834     * @param  callable  $callback
1835     * @return $this
1836     */
1837    public function transform(callable $callback)
1838    {
1839        $this->items = $this->map($callback)->all();
1840
1841        return $this;
1842    }
1843
1844    /**
1845     * Return only unique items from the collection array.
1846     *
1847     * @param  string|callable|null  $key
1848     * @param  bool  $strict
1849     * @return static
1850     */
1851    public function unique($key = null, $strict = false)
1852    {
1853        $callback = $this->valueRetriever($key);
1854
1855        $exists = [];
1856
1857        return $this->reject(function ($item, $key) use ($callback, $strict, &$exists) {
1858            if (in_array($id = $callback($item, $key), $exists, $strict)) {
1859                return true;
1860            }
1861
1862            $exists[] = $id;
1863        });
1864    }
1865
1866    /**
1867     * Return only unique items from the collection array using strict comparison.
1868     *
1869     * @param  string|callable|null  $key
1870     * @return static
1871     */
1872    public function uniqueStrict($key = null)
1873    {
1874        return $this->unique($key, true);
1875    }
1876
1877    /**
1878     * Reset the keys on the underlying array.
1879     *
1880     * @return static
1881     */
1882    public function values()
1883    {
1884        return new static(array_values($this->items));
1885    }
1886
1887    /**
1888     * Get a value retrieving callback.
1889     *
1890     * @param  callable|string|null  $value
1891     * @return callable
1892     */
1893    protected function valueRetriever($value)
1894    {
1895        if ($this->useAsCallable($value)) {
1896            return $value;
1897        }
1898
1899        return function ($item) use ($value) {
1900            return data_get($item, $value);
1901        };
1902    }
1903
1904    /**
1905     * Zip the collection together with one or more arrays.
1906     *
1907     * e.g. new Collection([1, 2, 3])->zip([4, 5, 6]);
1908     *      => [[1, 4], [2, 5], [3, 6]]
1909     *
1910     * @param  mixed ...$items
1911     * @return static
1912     */
1913    public function zip($items)
1914    {
1915        $arrayableItems = array_map(function ($items) {
1916            return $this->getArrayableItems($items);
1917        }, func_get_args());
1918
1919        $params = array_merge([function () {
1920            return new static(func_get_args());
1921        }, $this->items], $arrayableItems);
1922
1923        return new static(call_user_func_array('array_map', $params));
1924    }
1925
1926    /**
1927     * Pad collection to the specified length with a value.
1928     *
1929     * @param  int  $size
1930     * @param  mixed  $value
1931     * @return static
1932     */
1933    public function pad($size, $value)
1934    {
1935        return new static(array_pad($this->items, $size, $value));
1936    }
1937
1938    /**
1939     * Get the collection of items as a plain array.
1940     *
1941     * @return array
1942     */
1943    public function toArray()
1944    {
1945        return array_map(function ($value) {
1946            return $value instanceof Arrayable ? $value->toArray() : $value;
1947        }, $this->items);
1948    }
1949
1950    /**
1951     * Convert the object into something JSON serializable.
1952     *
1953     * @return array
1954     */
1955    public function jsonSerialize()
1956    {
1957        return array_map(function ($value) {
1958            if ($value instanceof JsonSerializable) {
1959                return $value->jsonSerialize();
1960            } elseif ($value instanceof Jsonable) {
1961                return json_decode($value->toJson(), true);
1962            } elseif ($value instanceof Arrayable) {
1963                return $value->toArray();
1964            }
1965
1966            return $value;
1967        }, $this->items);
1968    }
1969
1970    /**
1971     * Get the collection of items as JSON.
1972     *
1973     * @param  int  $options
1974     * @return string
1975     */
1976    public function toJson($options = 0)
1977    {
1978        return json_encode($this->jsonSerialize(), $options);
1979    }
1980
1981    /**
1982     * Get an iterator for the items.
1983     *
1984     * @return \ArrayIterator
1985     */
1986    public function getIterator()
1987    {
1988        return new ArrayIterator($this->items);
1989    }
1990
1991    /**
1992     * Get a CachingIterator instance.
1993     *
1994     * @param  int  $flags
1995     * @return \CachingIterator
1996     */
1997    public function getCachingIterator($flags = CachingIterator::CALL_TOSTRING)
1998    {
1999        return new CachingIterator($this->getIterator(), $flags);
2000    }
2001
2002    /**
2003     * Count the number of items in the collection.
2004     *
2005     * @return int
2006     */
2007    public function count()
2008    {
2009        return count($this->items);
2010    }
2011
2012    /**
2013     * Count the number of items in the collection using a given truth test.
2014     *
2015     * @param  callable|null  $callback
2016     * @return static
2017     */
2018    public function countBy($callback = null)
2019    {
2020        if (is_null($callback)) {
2021            $callback = function ($value) {
2022                return $value;
2023            };
2024        }
2025
2026        return new static($this->groupBy($callback)->map(function ($value) {
2027            return $value->count();
2028        }));
2029    }
2030
2031    /**
2032     * Add an item to the collection.
2033     *
2034     * @param  mixed  $item
2035     * @return $this
2036     */
2037    public function add($item)
2038    {
2039        $this->items[] = $item;
2040
2041        return $this;
2042    }
2043
2044    /**
2045     * Get a base Support collection instance from this collection.
2046     *
2047     * @return \Illuminate\Support\Collection
2048     */
2049    public function toBase()
2050    {
2051        return new self($this);
2052    }
2053
2054    /**
2055     * Determine if an item exists at an offset.
2056     *
2057     * @param  mixed  $key
2058     * @return bool
2059     */
2060    public function offsetExists($key)
2061    {
2062        return array_key_exists($key, $this->items);
2063    }
2064
2065    /**
2066     * Get an item at a given offset.
2067     *
2068     * @param  mixed  $key
2069     * @return mixed
2070     */
2071    public function offsetGet($key)
2072    {
2073        return $this->items[$key];
2074    }
2075
2076    /**
2077     * Set the item at a given offset.
2078     *
2079     * @param  mixed  $key
2080     * @param  mixed  $value
2081     * @return void
2082     */
2083    public function offsetSet($key, $value)
2084    {
2085        if (is_null($key)) {
2086            $this->items[] = $value;
2087        } else {
2088            $this->items[$key] = $value;
2089        }
2090    }
2091
2092    /**
2093     * Unset the item at a given offset.
2094     *
2095     * @param  string  $key
2096     * @return void
2097     */
2098    public function offsetUnset($key)
2099    {
2100        unset($this->items[$key]);
2101    }
2102
2103    /**
2104     * Convert the collection to its string representation.
2105     *
2106     * @return string
2107     */
2108    public function __toString()
2109    {
2110        return $this->toJson();
2111    }
2112
2113    /**
2114     * Results array of items from Collection or Arrayable.
2115     *
2116     * @param  mixed  $items
2117     * @return array
2118     */
2119    protected function getArrayableItems($items)
2120    {
2121        if (is_array($items)) {
2122            return $items;
2123        } elseif ($items instanceof self) {
2124            return $items->all();
2125        } elseif ($items instanceof Arrayable) {
2126            return $items->toArray();
2127        } elseif ($items instanceof Jsonable) {
2128            return json_decode($items->toJson(), true);
2129        } elseif ($items instanceof JsonSerializable) {
2130            return (array) $items->jsonSerialize();
2131        } elseif ($items instanceof Traversable) {
2132            return iterator_to_array($items);
2133        }
2134
2135        return (array) $items;
2136    }
2137
2138    /**
2139     * Add a method to the list of proxied methods.
2140     *
2141     * @param  string  $method
2142     * @return void
2143     */
2144    public static function proxy($method)
2145    {
2146        static::$proxies[] = $method;
2147    }
2148
2149    /**
2150     * Dynamically access collection proxies.
2151     *
2152     * @param  string  $key
2153     * @return mixed
2154     *
2155     * @throws \Exception
2156     */
2157    public function __get($key)
2158    {
2159        if (! in_array($key, static::$proxies)) {
2160            throw new Exception("Property [{$key}] does not exist on this collection instance.");
2161        }
2162
2163        return new HigherOrderCollectionProxy($this, $key);
2164    }
2165}
2166