LazyCollection   F
last analyzed

Complexity

Total Complexity 193

Size/Duplication

Total Lines 1363
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 324
c 1
b 0
f 0
dl 0
loc 1363
rs 2
wmc 193

80 Methods

Rating   Name   Duplication   Size   Complexity  
A remember() 0 29 5
A replace() 0 17 4
A passthru() 0 4 1
A last() 0 11 5
A sortByDesc() 0 3 1
A shuffle() 0 3 1
A zip() 0 13 2
A mode() 0 3 1
A merge() 0 3 1
A avg() 0 3 1
A __construct() 0 8 4
A sortDesc() 0 3 1
A flatten() 0 15 5
A map() 0 5 2
A get() 0 13 4
A mergeRecursive() 0 3 1
A replaceRecursive() 0 3 1
A sortKeys() 0 3 1
A intersect() 0 3 1
A skipUntil() 0 5 2
A filter() 0 12 4
A mapToDictionary() 0 3 1
A pad() 0 17 4
A random() 0 5 2
A empty() 0 3 1
A split() 0 3 1
A explodePluckParameters() 0 7 4
A crossJoin() 0 3 1
A search() 0 15 5
A join() 0 3 1
A collapse() 0 7 5
A sortKeysDesc() 0 3 1
A keyBy() 0 13 3
A chunk() 0 29 6
A diffUsing() 0 3 1
A mapWithKeys() 0 5 2
A eager() 0 3 1
A diffAssocUsing() 0 3 1
A range() 0 5 2
A flip() 0 5 2
A sortBy() 0 3 1
A countBy() 0 20 4
A nth() 0 11 3
A duplicates() 0 3 1
A getIterator() 0 3 1
A takeUntil() 0 11 4
A count() 0 7 2
A combine() 0 21 4
A implode() 0 3 1
A times() 0 13 4
A groupBy() 0 3 1
A median() 0 3 1
A keys() 0 5 2
A diffAssoc() 0 3 1
B only() 0 22 8
A duplicatesStrict() 0 3 1
A contains() 0 21 6
A diff() 0 3 1
A all() 0 7 2
A takeWhile() 0 6 2
A take() 0 18 5
A except() 0 3 1
A slice() 0 9 4
A skip() 0 13 4
A tapEach() 0 7 2
A intersectByKeys() 0 3 1
A reverse() 0 3 1
A pluck() 0 18 5
A concat() 0 6 1
A sort() 0 3 1
A has() 0 12 5
A diffKeys() 0 3 1
A diffKeysUsing() 0 3 1
A reduce() 0 9 2
A values() 0 5 2
A isEmpty() 0 3 1
A union() 0 3 1
A first() 0 19 5
A makeIterator() 0 11 3
A skipWhile() 0 15 5

How to fix   Complexity   

Complex Class

Complex classes like LazyCollection often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use LazyCollection, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace MuCTS\Collections;
4
5
use ArrayIterator;
6
use Closure;
7
use MuCTS\Collections\Traits\EnumeratesValues;
8
use MuCTS\Contracts\Collections\Enumerable;
9
use MuCTS\Macroable\Traits\Macroable;
10
use IteratorAggregate;
11
use stdClass;
12
13
class LazyCollection implements Enumerable
14
{
15
    use EnumeratesValues, Macroable;
0 ignored issues
show
Bug introduced by
The trait MuCTS\Macroable\Traits\Macroable requires the property $name which is not provided by MuCTS\Collections\LazyCollection.
Loading history...
16
17
    /**
18
     * The source from which to generate items.
19
     *
20
     * @var callable|static
21
     */
22
    public $source;
23
24
    /**
25
     * Create a new lazy collection instance.
26
     *
27
     * @param mixed $source
28
     * @return void
29
     */
30
    public function __construct($source = null)
31
    {
32
        if ($source instanceof Closure || $source instanceof self) {
33
            $this->source = $source;
34
        } elseif (is_null($source)) {
35
            $this->source = static::empty();
36
        } else {
37
            $this->source = $this->getArrayableItems($source);
38
        }
39
    }
40
41
    /**
42
     * Create a new instance with no items.
43
     *
44
     * @return static
45
     */
46
    public static function empty()
47
    {
48
        return new static([]);
49
    }
50
51
    /**
52
     * Create a new instance by invoking the callback a given amount of times.
53
     *
54
     * @param int $number
55
     * @param callable|null $callback
56
     * @return static
57
     */
58
    public static function times($number, callable $callback = null)
59
    {
60
        if ($number < 1) {
61
            return new static;
62
        }
63
64
        $instance = new static(function () use ($number) {
65
            for ($current = 1; $current <= $number; $current++) {
66
                yield $current;
67
            }
68
        });
69
70
        return is_null($callback) ? $instance : $instance->map($callback);
71
    }
72
73
    /**
74
     * Create an enumerable with the given range.
75
     *
76
     * @param int $from
77
     * @param int $to
78
     * @return static
79
     */
80
    public static function range($from, $to)
81
    {
82
        return new static(function () use ($from, $to) {
83
            for (; $from <= $to; $from++) {
84
                yield $from;
85
            }
86
        });
87
    }
88
89
    /**
90
     * Get all items in the enumerable.
91
     *
92
     * @return array
93
     */
94
    public function all()
95
    {
96
        if (is_array($this->source)) {
97
            return $this->source;
98
        }
99
100
        return iterator_to_array($this->getIterator());
101
    }
102
103
    /**
104
     * Eager load all items into a new lazy collection backed by an array.
105
     *
106
     * @return static
107
     */
108
    public function eager()
109
    {
110
        return new static($this->all());
111
    }
112
113
    /**
114
     * Cache values as they're enumerated.
115
     *
116
     * @return static
117
     */
118
    public function remember()
119
    {
120
        $iterator = $this->getIterator();
121
122
        $iteratorIndex = 0;
123
124
        $cache = [];
125
126
        return new static(function () use ($iterator, &$iteratorIndex, &$cache) {
127
            for ($index = 0; true; $index++) {
128
                if (array_key_exists($index, $cache)) {
129
                    yield $cache[$index][0] => $cache[$index][1];
130
131
                    continue;
132
                }
133
134
                if ($iteratorIndex < $index) {
135
                    $iterator->next();
0 ignored issues
show
Bug introduced by
The method next() does not exist on Traversable. It seems like you code against a sub-type of Traversable such as IntlCodePointBreakIterator or Yaf_Config_Simple or Yaf\Session or IntlRuleBasedBreakIterator or Yaf_Session or Yaf\Config\Simple or Yaf\Config\Ini or Iterator or Yaf_Config_Ini or IntlBreakIterator or MongoGridFSCursor or Doctrine\Common\Collections\Collection or MuCTS\Collections\Collection or MuCTS\Collections\LazyCollection or Symfony\Component\Form\Test\FormInterface or SimpleXMLIterator or Symfony\Component\Form\Test\FormBuilderInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

135
                    $iterator->/** @scrutinizer ignore-call */ 
136
                               next();
Loading history...
136
137
                    $iteratorIndex++;
138
                }
139
140
                if (!$iterator->valid()) {
0 ignored issues
show
Bug introduced by
The method valid() does not exist on Traversable. It seems like you code against a sub-type of Traversable such as Yaf_Config_Simple or Yaf\Session or Yaf_Session or Yaf\Config\Simple or Yaf\Config\Ini or Iterator or Yaf_Config_Ini or MongoGridFSCursor or MuCTS\Collections\Collection or MuCTS\Collections\LazyCollection or Symfony\Component\Form\Test\FormInterface or SimpleXMLIterator or Symfony\Component\Form\Test\FormBuilderInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

140
                if (!$iterator->/** @scrutinizer ignore-call */ valid()) {
Loading history...
141
                    break;
142
                }
143
144
                $cache[$index] = [$iterator->key(), $iterator->current()];
0 ignored issues
show
Bug introduced by
The method current() does not exist on Traversable. It seems like you code against a sub-type of Traversable such as IntlCodePointBreakIterator or Yaf_Config_Simple or Yaf\Session or IntlRuleBasedBreakIterator or Yaf_Session or Yaf\Config\Simple or Yaf\Config\Ini or Iterator or Yaf_Config_Ini or IntlBreakIterator or MongoGridFSCursor or Doctrine\Common\Collections\Collection or MuCTS\Collections\Collection or MuCTS\Collections\LazyCollection or Symfony\Component\Form\Test\FormInterface or SimpleXMLIterator or Symfony\Component\Form\Test\FormBuilderInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

144
                $cache[$index] = [$iterator->key(), $iterator->/** @scrutinizer ignore-call */ current()];
Loading history...
Bug introduced by
The method key() does not exist on Traversable. It seems like you code against a sub-type of Traversable such as Yaf_Config_Simple or Yaf\Session or Yaf_Session or Yaf\Config\Simple or Yaf\Config\Ini or Iterator or Yaf_Config_Ini or MongoGridFSCursor or Doctrine\Common\Collections\Collection or MuCTS\Collections\Collection or MuCTS\Collections\LazyCollection or Symfony\Component\Form\Test\FormInterface or SimpleXMLIterator or Symfony\Component\Form\Test\FormBuilderInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

144
                $cache[$index] = [$iterator->/** @scrutinizer ignore-call */ key(), $iterator->current()];
Loading history...
145
146
                yield $cache[$index][0] => $cache[$index][1];
147
            }
148
        });
149
    }
150
151
    /**
152
     * Get the average value of a given key.
153
     *
154
     * @param callable|string|null $callback
155
     * @return mixed
156
     */
157
    public function avg($callback = null)
158
    {
159
        return $this->collect()->avg($callback);
160
    }
161
162
    /**
163
     * Get the median of a given key.
164
     *
165
     * @param string|array|null $key
166
     * @return mixed
167
     */
168
    public function median($key = null)
169
    {
170
        return $this->collect()->median($key);
171
    }
172
173
    /**
174
     * Get the mode of a given key.
175
     *
176
     * @param string|array|null $key
177
     * @return array|null
178
     */
179
    public function mode($key = null)
180
    {
181
        return $this->collect()->mode($key);
182
    }
183
184
    /**
185
     * Collapse the collection of items into a single array.
186
     *
187
     * @return static
188
     */
189
    public function collapse()
190
    {
191
        return new static(function () {
192
            foreach ($this as $values) {
193
                if (is_array($values) || $values instanceof Enumerable) {
194
                    foreach ($values as $value) {
195
                        yield $value;
196
                    }
197
                }
198
            }
199
        });
200
    }
201
202
    /**
203
     * Determine if an item exists in the enumerable.
204
     *
205
     * @param mixed $key
206
     * @param mixed $operator
207
     * @param mixed $value
208
     * @return bool
209
     */
210
    public function contains($key, $operator = null, $value = null)
211
    {
212
        if (func_num_args() === 1 && $this->useAsCallable($key)) {
213
            $placeholder = new stdClass;
214
215
            return $this->first($key, $placeholder) !== $placeholder;
216
        }
217
218
        if (func_num_args() === 1) {
219
            $needle = $key;
220
221
            foreach ($this as $value) {
222
                if ($value == $needle) {
223
                    return true;
224
                }
225
            }
226
227
            return false;
228
        }
229
230
        return $this->contains($this->operatorForWhere(...func_get_args()));
0 ignored issues
show
Bug introduced by
func_get_args() is expanded, but the parameter $key of MuCTS\Collections\LazyCo...ion::operatorForWhere() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

230
        return $this->contains($this->operatorForWhere(/** @scrutinizer ignore-type */ ...func_get_args()));
Loading history...
231
    }
232
233
    /**
234
     * Cross join the given iterables, returning all possible permutations.
235
     *
236
     * @param array ...$arrays
237
     * @return static
238
     */
239
    public function crossJoin(...$arrays)
240
    {
241
        return $this->passthru('crossJoin', func_get_args());
242
    }
243
244
    /**
245
     * Count the number of items in the collection by a field or using a callback.
246
     *
247
     * @param callable|string $countBy
248
     * @return static
249
     */
250
    public function countBy($countBy = null)
251
    {
252
        $countBy = is_null($countBy)
253
            ? $this->identity()
254
            : $this->valueRetriever($countBy);
255
256
        return new static(function () use ($countBy) {
257
            $counts = [];
258
259
            foreach ($this as $key => $value) {
260
                $group = $countBy($value, $key);
261
262
                if (empty($counts[$group])) {
263
                    $counts[$group] = 0;
264
                }
265
266
                $counts[$group]++;
267
            }
268
269
            yield from $counts;
270
        });
271
    }
272
273
    /**
274
     * Get the items that are not present in the given items.
275
     *
276
     * @param mixed $items
277
     * @return static
278
     */
279
    public function diff($items)
280
    {
281
        return $this->passthru('diff', func_get_args());
282
    }
283
284
    /**
285
     * Get the items that are not present in the given items, using the callback.
286
     *
287
     * @param mixed $items
288
     * @param callable $callback
289
     * @return static
290
     */
291
    public function diffUsing($items, callable $callback)
292
    {
293
        return $this->passthru('diffUsing', func_get_args());
294
    }
295
296
    /**
297
     * Get the items whose keys and values are not present in the given items.
298
     *
299
     * @param mixed $items
300
     * @return static
301
     */
302
    public function diffAssoc($items)
303
    {
304
        return $this->passthru('diffAssoc', func_get_args());
305
    }
306
307
    /**
308
     * Get the items whose keys and values are not present in the given items, using the callback.
309
     *
310
     * @param mixed $items
311
     * @param callable $callback
312
     * @return static
313
     */
314
    public function diffAssocUsing($items, callable $callback)
315
    {
316
        return $this->passthru('diffAssocUsing', func_get_args());
317
    }
318
319
    /**
320
     * Get the items whose keys are not present in the given items.
321
     *
322
     * @param mixed $items
323
     * @return static
324
     */
325
    public function diffKeys($items)
326
    {
327
        return $this->passthru('diffKeys', func_get_args());
328
    }
329
330
    /**
331
     * Get the items whose keys are not present in the given items, using the callback.
332
     *
333
     * @param mixed $items
334
     * @param callable $callback
335
     * @return static
336
     */
337
    public function diffKeysUsing($items, callable $callback)
338
    {
339
        return $this->passthru('diffKeysUsing', func_get_args());
340
    }
341
342
    /**
343
     * Retrieve duplicate items.
344
     *
345
     * @param callable|null $callback
346
     * @param bool $strict
347
     * @return static
348
     */
349
    public function duplicates($callback = null, $strict = false)
350
    {
351
        return $this->passthru('duplicates', func_get_args());
352
    }
353
354
    /**
355
     * Retrieve duplicate items using strict comparison.
356
     *
357
     * @param callable|null $callback
358
     * @return static
359
     */
360
    public function duplicatesStrict($callback = null)
361
    {
362
        return $this->passthru('duplicatesStrict', func_get_args());
363
    }
364
365
    /**
366
     * Get all items except for those with the specified keys.
367
     *
368
     * @param mixed $keys
369
     * @return static
370
     */
371
    public function except($keys)
372
    {
373
        return $this->passthru('except', func_get_args());
374
    }
375
376
    /**
377
     * Run a filter over each of the items.
378
     *
379
     * @param callable|null $callback
380
     * @return static
381
     */
382
    public function filter(callable $callback = null)
383
    {
384
        if (is_null($callback)) {
385
            $callback = function ($value) {
386
                return (bool)$value;
387
            };
388
        }
389
390
        return new static(function () use ($callback) {
391
            foreach ($this as $key => $value) {
392
                if ($callback($value, $key)) {
393
                    yield $key => $value;
394
                }
395
            }
396
        });
397
    }
398
399
    /**
400
     * Get the first item from the enumerable passing the given truth test.
401
     *
402
     * @param callable|null $callback
403
     * @param mixed $default
404
     * @return mixed
405
     */
406
    public function first(callable $callback = null, $default = null)
407
    {
408
        $iterator = $this->getIterator();
409
410
        if (is_null($callback)) {
411
            if (!$iterator->valid()) {
412
                return value($default);
413
            }
414
415
            return $iterator->current();
416
        }
417
418
        foreach ($iterator as $key => $value) {
419
            if ($callback($value, $key)) {
420
                return $value;
421
            }
422
        }
423
424
        return value($default);
425
    }
426
427
    /**
428
     * Get a flattened list of the items in the collection.
429
     *
430
     * @param int $depth
431
     * @return static
432
     */
433
    public function flatten($depth = INF)
434
    {
435
        $instance = new static(function () use ($depth) {
436
            foreach ($this as $item) {
437
                if (!is_array($item) && !$item instanceof Enumerable) {
438
                    yield $item;
439
                } elseif ($depth === 1) {
440
                    yield from $item;
441
                } else {
442
                    yield from (new static($item))->flatten($depth - 1);
0 ignored issues
show
Bug introduced by
$depth - 1 of type double is incompatible with the type integer expected by parameter $depth of MuCTS\Collections\LazyCollection::flatten(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

442
                    yield from (new static($item))->flatten(/** @scrutinizer ignore-type */ $depth - 1);
Loading history...
443
                }
444
            }
445
        });
446
447
        return $instance->values();
448
    }
449
450
    /**
451
     * Flip the items in the collection.
452
     *
453
     * @return static
454
     */
455
    public function flip()
456
    {
457
        return new static(function () {
458
            foreach ($this as $key => $value) {
459
                yield $value => $key;
460
            }
461
        });
462
    }
463
464
    /**
465
     * Get an item by key.
466
     *
467
     * @param mixed $key
468
     * @param mixed $default
469
     * @return mixed
470
     */
471
    public function get($key, $default = null)
472
    {
473
        if (is_null($key)) {
474
            return null;
475
        }
476
477
        foreach ($this as $outerKey => $outerValue) {
478
            if ($outerKey == $key) {
479
                return $outerValue;
480
            }
481
        }
482
483
        return value($default);
484
    }
485
486
    /**
487
     * Group an associative array by a field or using a callback.
488
     *
489
     * @param array|callable|string $groupBy
490
     * @param bool $preserveKeys
491
     * @return static
492
     */
493
    public function groupBy($groupBy, $preserveKeys = false)
494
    {
495
        return $this->passthru('groupBy', func_get_args());
496
    }
497
498
    /**
499
     * Key an associative array by a field or using a callback.
500
     *
501
     * @param callable|string $keyBy
502
     * @return static
503
     */
504
    public function keyBy($keyBy)
505
    {
506
        return new static(function () use ($keyBy) {
507
            $keyBy = $this->valueRetriever($keyBy);
508
509
            foreach ($this as $key => $item) {
510
                $resolvedKey = $keyBy($item, $key);
511
512
                if (is_object($resolvedKey)) {
513
                    $resolvedKey = (string)$resolvedKey;
514
                }
515
516
                yield $resolvedKey => $item;
517
            }
518
        });
519
    }
520
521
    /**
522
     * Determine if an item exists in the collection by key.
523
     *
524
     * @param mixed $key
525
     * @return bool
526
     */
527
    public function has($key)
528
    {
529
        $keys = array_flip(is_array($key) ? $key : func_get_args());
530
        $count = count($keys);
531
532
        foreach ($this as $key => $value) {
0 ignored issues
show
introduced by
$key is overwriting one of the parameters of this function.
Loading history...
533
            if (array_key_exists($key, $keys) && --$count == 0) {
534
                return true;
535
            }
536
        }
537
538
        return false;
539
    }
540
541
    /**
542
     * Concatenate values of a given key as a string.
543
     *
544
     * @param string $value
545
     * @param string|null $glue
546
     * @return string
547
     */
548
    public function implode($value, $glue = null)
549
    {
550
        return $this->collect()->implode(...func_get_args());
0 ignored issues
show
Bug introduced by
func_get_args() is expanded, but the parameter $value of MuCTS\Collections\Collection::implode() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

550
        return $this->collect()->implode(/** @scrutinizer ignore-type */ ...func_get_args());
Loading history...
551
    }
552
553
    /**
554
     * Intersect the collection with the given items.
555
     *
556
     * @param mixed $items
557
     * @return static
558
     */
559
    public function intersect($items)
560
    {
561
        return $this->passthru('intersect', func_get_args());
562
    }
563
564
    /**
565
     * Intersect the collection with the given items by key.
566
     *
567
     * @param mixed $items
568
     * @return static
569
     */
570
    public function intersectByKeys($items)
571
    {
572
        return $this->passthru('intersectByKeys', func_get_args());
573
    }
574
575
    /**
576
     * Determine if the items is empty or not.
577
     *
578
     * @return bool
579
     */
580
    public function isEmpty()
581
    {
582
        return !$this->getIterator()->valid();
583
    }
584
585
    /**
586
     * Join all items from the collection using a string. The final items can use a separate glue string.
587
     *
588
     * @param string $glue
589
     * @param string $finalGlue
590
     * @return string
591
     */
592
    public function join($glue, $finalGlue = '')
593
    {
594
        return $this->collect()->join(...func_get_args());
0 ignored issues
show
Bug introduced by
func_get_args() is expanded, but the parameter $glue of MuCTS\Collections\Collection::join() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

594
        return $this->collect()->join(/** @scrutinizer ignore-type */ ...func_get_args());
Loading history...
595
    }
596
597
    /**
598
     * Get the keys of the collection items.
599
     *
600
     * @return static
601
     */
602
    public function keys()
603
    {
604
        return new static(function () {
605
            foreach ($this as $key => $value) {
606
                yield $key;
607
            }
608
        });
609
    }
610
611
    /**
612
     * Get the last item from the collection.
613
     *
614
     * @param callable|null $callback
615
     * @param mixed $default
616
     * @return mixed
617
     */
618
    public function last(callable $callback = null, $default = null)
619
    {
620
        $needle = $placeholder = new stdClass;
621
622
        foreach ($this as $key => $value) {
623
            if (is_null($callback) || $callback($value, $key)) {
624
                $needle = $value;
625
            }
626
        }
627
628
        return $needle === $placeholder ? value($default) : $needle;
629
    }
630
631
    /**
632
     * Get the values of a given key.
633
     *
634
     * @param string|array $value
635
     * @param string|null $key
636
     * @return static
637
     */
638
    public function pluck($value, $key = null)
639
    {
640
        return new static(function () use ($value, $key) {
641
            [$value, $key] = $this->explodePluckParameters($value, $key);
642
643
            foreach ($this as $item) {
644
                $itemValue = data_get($item, $value);
645
646
                if (is_null($key)) {
647
                    yield $itemValue;
648
                } else {
649
                    $itemKey = data_get($item, $key);
650
651
                    if (is_object($itemKey) && method_exists($itemKey, '__toString')) {
652
                        $itemKey = (string)$itemKey;
653
                    }
654
655
                    yield $itemKey => $itemValue;
656
                }
657
            }
658
        });
659
    }
660
661
    /**
662
     * Run a map over each of the items.
663
     *
664
     * @param callable $callback
665
     * @return static
666
     */
667
    public function map(callable $callback)
668
    {
669
        return new static(function () use ($callback) {
670
            foreach ($this as $key => $value) {
671
                yield $key => $callback($value, $key);
672
            }
673
        });
674
    }
675
676
    /**
677
     * Run a dictionary map over the items.
678
     *
679
     * The callback should return an associative array with a single key/value pair.
680
     *
681
     * @param callable $callback
682
     * @return static
683
     */
684
    public function mapToDictionary(callable $callback)
685
    {
686
        return $this->passthru('mapToDictionary', func_get_args());
687
    }
688
689
    /**
690
     * Run an associative map over each of the items.
691
     *
692
     * The callback should return an associative array with a single key/value pair.
693
     *
694
     * @param callable $callback
695
     * @return static
696
     */
697
    public function mapWithKeys(callable $callback)
698
    {
699
        return new static(function () use ($callback) {
700
            foreach ($this as $key => $value) {
701
                yield from $callback($value, $key);
702
            }
703
        });
704
    }
705
706
    /**
707
     * Merge the collection with the given items.
708
     *
709
     * @param mixed $items
710
     * @return static
711
     */
712
    public function merge($items)
713
    {
714
        return $this->passthru('merge', func_get_args());
715
    }
716
717
    /**
718
     * Recursively merge the collection with the given items.
719
     *
720
     * @param mixed $items
721
     * @return static
722
     */
723
    public function mergeRecursive($items)
724
    {
725
        return $this->passthru('mergeRecursive', func_get_args());
726
    }
727
728
    /**
729
     * Create a collection by using this collection for keys and another for its values.
730
     *
731
     * @param mixed $values
732
     * @return static
733
     */
734
    public function combine($values)
735
    {
736
        return new static(function () use ($values) {
737
            $values = $this->makeIterator($values);
738
739
            $errorMessage = 'Both parameters should have an equal number of elements';
740
741
            foreach ($this as $key) {
742
                if (!$values->valid()) {
743
                    trigger_error($errorMessage, E_USER_WARNING);
744
745
                    break;
746
                }
747
748
                yield $key => $values->current();
749
750
                $values->next();
751
            }
752
753
            if ($values->valid()) {
754
                trigger_error($errorMessage, E_USER_WARNING);
755
            }
756
        });
757
    }
758
759
    /**
760
     * Union the collection with the given items.
761
     *
762
     * @param mixed $items
763
     * @return static
764
     */
765
    public function union($items)
766
    {
767
        return $this->passthru('union', func_get_args());
768
    }
769
770
    /**
771
     * Create a new collection consisting of every n-th element.
772
     *
773
     * @param int $step
774
     * @param int $offset
775
     * @return static
776
     */
777
    public function nth($step, $offset = 0)
778
    {
779
        return new static(function () use ($step, $offset) {
780
            $position = 0;
781
782
            foreach ($this as $item) {
783
                if ($position % $step === $offset) {
784
                    yield $item;
785
                }
786
787
                $position++;
788
            }
789
        });
790
    }
791
792
    /**
793
     * Get the items with the specified keys.
794
     *
795
     * @param mixed $keys
796
     * @return static
797
     */
798
    public function only($keys)
799
    {
800
        if ($keys instanceof Enumerable) {
801
            $keys = $keys->all();
802
        } elseif (!is_null($keys)) {
803
            $keys = is_array($keys) ? $keys : func_get_args();
804
        }
805
806
        return new static(function () use ($keys) {
807
            if (is_null($keys)) {
808
                yield from $this;
809
            } else {
810
                $keys = array_flip($keys);
811
812
                foreach ($this as $key => $value) {
813
                    if (array_key_exists($key, $keys)) {
814
                        yield $key => $value;
815
816
                        unset($keys[$key]);
817
818
                        if (empty($keys)) {
819
                            break;
820
                        }
821
                    }
822
                }
823
            }
824
        });
825
    }
826
827
    /**
828
     * Push all of the given items onto the collection.
829
     *
830
     * @param iterable $source
831
     * @return static
832
     */
833
    public function concat($source)
834
    {
835
        return (new static(function () use ($source) {
836
            yield from $this;
837
            yield from $source;
838
        }))->values();
839
    }
840
841
    /**
842
     * Get one or a specified number of items randomly from the collection.
843
     *
844
     * @param int|null $number
845
     * @return static|mixed
846
     *
847
     * @throws \InvalidArgumentException
848
     */
849
    public function random($number = null)
850
    {
851
        $result = $this->collect()->random(...func_get_args());
0 ignored issues
show
Bug introduced by
func_get_args() is expanded, but the parameter $number of MuCTS\Collections\Collection::random() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

851
        $result = $this->collect()->random(/** @scrutinizer ignore-type */ ...func_get_args());
Loading history...
852
853
        return is_null($number) ? $result : new static($result);
854
    }
855
856
    /**
857
     * Reduce the collection to a single value.
858
     *
859
     * @param callable $callback
860
     * @param mixed $initial
861
     * @return mixed
862
     */
863
    public function reduce(callable $callback, $initial = null)
864
    {
865
        $result = $initial;
866
867
        foreach ($this as $value) {
868
            $result = $callback($result, $value);
869
        }
870
871
        return $result;
872
    }
873
874
    /**
875
     * Replace the collection items with the given items.
876
     *
877
     * @param mixed $items
878
     * @return static
879
     */
880
    public function replace($items)
881
    {
882
        return new static(function () use ($items) {
883
            $items = $this->getArrayableItems($items);
884
885
            foreach ($this as $key => $value) {
886
                if (array_key_exists($key, $items)) {
887
                    yield $key => $items[$key];
888
889
                    unset($items[$key]);
890
                } else {
891
                    yield $key => $value;
892
                }
893
            }
894
895
            foreach ($items as $key => $value) {
896
                yield $key => $value;
897
            }
898
        });
899
    }
900
901
    /**
902
     * Recursively replace the collection items with the given items.
903
     *
904
     * @param mixed $items
905
     * @return static
906
     */
907
    public function replaceRecursive($items)
908
    {
909
        return $this->passthru('replaceRecursive', func_get_args());
910
    }
911
912
    /**
913
     * Reverse items order.
914
     *
915
     * @return static
916
     */
917
    public function reverse()
918
    {
919
        return $this->passthru('reverse', func_get_args());
920
    }
921
922
    /**
923
     * Search the collection for a given value and return the corresponding key if successful.
924
     *
925
     * @param mixed $value
926
     * @param bool $strict
927
     * @return mixed
928
     */
929
    public function search($value, $strict = false)
930
    {
931
        $predicate = $this->useAsCallable($value)
932
            ? $value
933
            : function ($item) use ($value, $strict) {
934
                return $strict ? $item === $value : $item == $value;
935
            };
936
937
        foreach ($this as $key => $item) {
938
            if ($predicate($item, $key)) {
939
                return $key;
940
            }
941
        }
942
943
        return false;
944
    }
945
946
    /**
947
     * Shuffle the items in the collection.
948
     *
949
     * @param int|null $seed
950
     * @return static
951
     */
952
    public function shuffle($seed = null)
953
    {
954
        return $this->passthru('shuffle', func_get_args());
955
    }
956
957
    /**
958
     * Skip the first {$count} items.
959
     *
960
     * @param int $count
961
     * @return static
962
     */
963
    public function skip($count)
964
    {
965
        return new static(function () use ($count) {
966
            $iterator = $this->getIterator();
967
968
            while ($iterator->valid() && $count--) {
969
                $iterator->next();
970
            }
971
972
            while ($iterator->valid()) {
973
                yield $iterator->key() => $iterator->current();
974
975
                $iterator->next();
976
            }
977
        });
978
    }
979
980
    /**
981
     * Skip items in the collection until the given condition is met.
982
     *
983
     * @param mixed $value
984
     * @return static
985
     */
986
    public function skipUntil($value)
987
    {
988
        $callback = $this->useAsCallable($value) ? $value : $this->equality($value);
989
990
        return $this->skipWhile($this->negate($callback));
991
    }
992
993
    /**
994
     * Skip items in the collection while the given condition is met.
995
     *
996
     * @param mixed $value
997
     * @return static
998
     */
999
    public function skipWhile($value)
1000
    {
1001
        $callback = $this->useAsCallable($value) ? $value : $this->equality($value);
1002
1003
        return new static(function () use ($callback) {
1004
            $iterator = $this->getIterator();
1005
1006
            while ($iterator->valid() && $callback($iterator->current(), $iterator->key())) {
1007
                $iterator->next();
1008
            }
1009
1010
            while ($iterator->valid()) {
1011
                yield $iterator->key() => $iterator->current();
1012
1013
                $iterator->next();
1014
            }
1015
        });
1016
    }
1017
1018
    /**
1019
     * Get a slice of items from the enumerable.
1020
     *
1021
     * @param int $offset
1022
     * @param int|null $length
1023
     * @return static
1024
     */
1025
    public function slice($offset, $length = null)
1026
    {
1027
        if ($offset < 0 || $length < 0) {
1028
            return $this->passthru('slice', func_get_args());
1029
        }
1030
1031
        $instance = $this->skip($offset);
1032
1033
        return is_null($length) ? $instance : $instance->take($length);
1034
    }
1035
1036
    /**
1037
     * Split a collection into a certain number of groups.
1038
     *
1039
     * @param int $numberOfGroups
1040
     * @return static
1041
     */
1042
    public function split($numberOfGroups)
1043
    {
1044
        return $this->passthru('split', func_get_args());
1045
    }
1046
1047
    /**
1048
     * Chunk the collection into chunks of the given size.
1049
     *
1050
     * @param int $size
1051
     * @return static
1052
     */
1053
    public function chunk($size)
1054
    {
1055
        if ($size <= 0) {
1056
            return static::empty();
1057
        }
1058
1059
        return new static(function () use ($size) {
1060
            $iterator = $this->getIterator();
1061
1062
            while ($iterator->valid()) {
1063
                $chunk = [];
1064
1065
                while (true) {
1066
                    $chunk[$iterator->key()] = $iterator->current();
1067
1068
                    if (count($chunk) < $size) {
1069
                        $iterator->next();
1070
1071
                        if (!$iterator->valid()) {
1072
                            break;
1073
                        }
1074
                    } else {
1075
                        break;
1076
                    }
1077
                }
1078
1079
                yield new static($chunk);
1080
1081
                $iterator->next();
1082
            }
1083
        });
1084
    }
1085
1086
    /**
1087
     * Sort through each item with a callback.
1088
     *
1089
     * @param callable|null|int $callback
1090
     * @return static
1091
     */
1092
    public function sort($callback = null)
1093
    {
1094
        return $this->passthru('sort', func_get_args());
1095
    }
1096
1097
    /**
1098
     * Sort items in descending order.
1099
     *
1100
     * @param int $options
1101
     * @return static
1102
     */
1103
    public function sortDesc($options = SORT_REGULAR)
1104
    {
1105
        return $this->passthru('sortDesc', func_get_args());
1106
    }
1107
1108
    /**
1109
     * Sort the collection using the given callback.
1110
     *
1111
     * @param callable|string $callback
1112
     * @param int $options
1113
     * @param bool $descending
1114
     * @return static
1115
     */
1116
    public function sortBy($callback, $options = SORT_REGULAR, $descending = false)
1117
    {
1118
        return $this->passthru('sortBy', func_get_args());
1119
    }
1120
1121
    /**
1122
     * Sort the collection in descending order using the given callback.
1123
     *
1124
     * @param callable|string $callback
1125
     * @param int $options
1126
     * @return static
1127
     */
1128
    public function sortByDesc($callback, $options = SORT_REGULAR)
1129
    {
1130
        return $this->passthru('sortByDesc', func_get_args());
1131
    }
1132
1133
    /**
1134
     * Sort the collection keys.
1135
     *
1136
     * @param int $options
1137
     * @param bool $descending
1138
     * @return static
1139
     */
1140
    public function sortKeys($options = SORT_REGULAR, $descending = false)
1141
    {
1142
        return $this->passthru('sortKeys', func_get_args());
1143
    }
1144
1145
    /**
1146
     * Sort the collection keys in descending order.
1147
     *
1148
     * @param int $options
1149
     * @return static
1150
     */
1151
    public function sortKeysDesc($options = SORT_REGULAR)
1152
    {
1153
        return $this->passthru('sortKeysDesc', func_get_args());
1154
    }
1155
1156
    /**
1157
     * Take the first or last {$limit} items.
1158
     *
1159
     * @param int $limit
1160
     * @return static
1161
     */
1162
    public function take($limit)
1163
    {
1164
        if ($limit < 0) {
1165
            return $this->passthru('take', func_get_args());
1166
        }
1167
1168
        return new static(function () use ($limit) {
1169
            $iterator = $this->getIterator();
1170
1171
            while ($limit--) {
1172
                if (!$iterator->valid()) {
1173
                    break;
1174
                }
1175
1176
                yield $iterator->key() => $iterator->current();
1177
1178
                if ($limit) {
1179
                    $iterator->next();
1180
                }
1181
            }
1182
        });
1183
    }
1184
1185
    /**
1186
     * Take items in the collection until the given condition is met.
1187
     *
1188
     * @param mixed $value
1189
     * @return static
1190
     */
1191
    public function takeUntil($value)
1192
    {
1193
        $callback = $this->useAsCallable($value) ? $value : $this->equality($value);
1194
1195
        return new static(function () use ($callback) {
1196
            foreach ($this as $key => $item) {
1197
                if ($callback($item, $key)) {
1198
                    break;
1199
                }
1200
1201
                yield $key => $item;
1202
            }
1203
        });
1204
    }
1205
1206
    /**
1207
     * Take items in the collection while the given condition is met.
1208
     *
1209
     * @param mixed $value
1210
     * @return static
1211
     */
1212
    public function takeWhile($value)
1213
    {
1214
        $callback = $this->useAsCallable($value) ? $value : $this->equality($value);
1215
1216
        return $this->takeUntil(function ($item, $key) use ($callback) {
1217
            return !$callback($item, $key);
1218
        });
1219
    }
1220
1221
    /**
1222
     * Pass each item in the collection to the given callback, lazily.
1223
     *
1224
     * @param callable $callback
1225
     * @return static
1226
     */
1227
    public function tapEach(callable $callback)
1228
    {
1229
        return new static(function () use ($callback) {
1230
            foreach ($this as $key => $value) {
1231
                $callback($value, $key);
1232
1233
                yield $key => $value;
1234
            }
1235
        });
1236
    }
1237
1238
    /**
1239
     * Reset the keys on the underlying array.
1240
     *
1241
     * @return static
1242
     */
1243
    public function values()
1244
    {
1245
        return new static(function () {
1246
            foreach ($this as $item) {
1247
                yield $item;
1248
            }
1249
        });
1250
    }
1251
1252
    /**
1253
     * Zip the collection together with one or more arrays.
1254
     *
1255
     * e.g. new LazyCollection([1, 2, 3])->zip([4, 5, 6]);
1256
     *      => [[1, 4], [2, 5], [3, 6]]
1257
     *
1258
     * @param mixed ...$items
1259
     * @return static
1260
     */
1261
    public function zip($items)
1262
    {
1263
        $iterables = func_get_args();
1264
1265
        return new static(function () use ($iterables) {
1266
            $iterators = Collection::make($iterables)->map(function ($iterable) {
1267
                return $this->makeIterator($iterable);
1268
            })->prepend($this->getIterator());
1269
1270
            while ($iterators->contains->valid()) {
0 ignored issues
show
Bug introduced by
The method valid() does not exist on MuCTS\Collections\HigherOrderCollectionProxy. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1270
            while ($iterators->contains->/** @scrutinizer ignore-call */ valid()) {
Loading history...
1271
                yield new static($iterators->map->current());
0 ignored issues
show
Bug introduced by
The method current() does not exist on MuCTS\Collections\HigherOrderCollectionProxy. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1271
                yield new static($iterators->map->/** @scrutinizer ignore-call */ current());
Loading history...
1272
1273
                $iterators->each->next();
0 ignored issues
show
Bug introduced by
The method next() does not exist on MuCTS\Collections\HigherOrderCollectionProxy. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1273
                $iterators->each->/** @scrutinizer ignore-call */ 
1274
                                  next();
Loading history...
1274
            }
1275
        });
1276
    }
1277
1278
    /**
1279
     * Pad collection to the specified length with a value.
1280
     *
1281
     * @param int $size
1282
     * @param mixed $value
1283
     * @return static
1284
     */
1285
    public function pad($size, $value)
1286
    {
1287
        if ($size < 0) {
1288
            return $this->passthru('pad', func_get_args());
1289
        }
1290
1291
        return new static(function () use ($size, $value) {
1292
            $yielded = 0;
1293
1294
            foreach ($this as $index => $item) {
1295
                yield $index => $item;
1296
1297
                $yielded++;
1298
            }
1299
1300
            while ($yielded++ < $size) {
1301
                yield $value;
1302
            }
1303
        });
1304
    }
1305
1306
    /**
1307
     * Get the values iterator.
1308
     *
1309
     * @return \Traversable
1310
     */
1311
    public function getIterator()
1312
    {
1313
        return $this->makeIterator($this->source);
1314
    }
1315
1316
    /**
1317
     * Count the number of items in the collection.
1318
     *
1319
     * @return int
1320
     */
1321
    public function count()
1322
    {
1323
        if (is_array($this->source)) {
1324
            return count($this->source);
1325
        }
1326
1327
        return iterator_count($this->getIterator());
1328
    }
1329
1330
    /**
1331
     * Make an iterator from the given source.
1332
     *
1333
     * @param mixed $source
1334
     * @return \Traversable
1335
     */
1336
    protected function makeIterator($source)
1337
    {
1338
        if ($source instanceof IteratorAggregate) {
1339
            return $source->getIterator();
1340
        }
1341
1342
        if (is_array($source)) {
1343
            return new ArrayIterator($source);
1344
        }
1345
1346
        return $source();
1347
    }
1348
1349
    /**
1350
     * Explode the "value" and "key" arguments passed to "pluck".
1351
     *
1352
     * @param string|array $value
1353
     * @param string|array|null $key
1354
     * @return array
1355
     */
1356
    protected function explodePluckParameters($value, $key)
1357
    {
1358
        $value = is_string($value) ? explode('.', $value) : $value;
1359
1360
        $key = is_null($key) || is_array($key) ? $key : explode('.', $key);
1361
1362
        return [$value, $key];
1363
    }
1364
1365
    /**
1366
     * Pass this lazy collection through a method on the collection class.
1367
     *
1368
     * @param string $method
1369
     * @param array $params
1370
     * @return static
1371
     */
1372
    protected function passthru($method, array $params)
1373
    {
1374
        return new static(function () use ($method, $params) {
1375
            yield from $this->collect()->$method(...$params);
1376
        });
1377
    }
1378
}
1379