Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Revision 399 | Zur aktuellen Revision | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

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