Completed
Push — master ( 29c7dc...1875a0 )
by Antonio Carlos
02:00
created

src/Support/Collection.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace IlluminateAgnostic\Arr\Support;
4
5
use stdClass;
6
use Countable;
7
use Exception;
8
use ArrayAccess;
9
use Traversable;
10
use ArrayIterator;
11
use CachingIterator;
12
use JsonSerializable;
13
use IteratorAggregate;
14
use IlluminateAgnostic\Arr\Support\Debug\Dumper;
15
use IlluminateAgnostic\Arr\Support\Traits\Macroable;
16
use IlluminateAgnostic\Arr\Contracts\Support\Jsonable;
17
use IlluminateAgnostic\Arr\Contracts\Support\Arrayable;
18
19
class Collection implements ArrayAccess, Arrayable, Countable, IteratorAggregate, Jsonable, JsonSerializable
20
{
21
    use Macroable;
22
23
    /**
24
     * The items contained in the collection.
25
     *
26
     * @var array
27
     */
28
    protected $items = [];
29
30
    /**
31
     * The methods that can be proxied.
32
     *
33
     * @var array
34
     */
35
    protected static $proxies = [
36
        'average', 'avg', 'contains', 'each', 'every', 'filter', 'first', 'flatMap',
37
        'keyBy', 'map', 'partition', 'reject', 'sortBy', 'sortByDesc', 'sum', 'unique',
38
    ];
39
40
    /**
41
     * Create a new collection.
42
     *
43
     * @param  mixed  $items
44
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

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