Completed
Push — master ( 91c709...1f6826 )
by Antonio Carlos
06:01
created

LazyCollection::sortDesc()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace IlluminateAgnostic\Arr\Support;
4
5
use ArrayIterator;
6
use Closure;
7
use IlluminateAgnostic\Arr\Support\Traits\EnumeratesValues;
8
use IlluminateAgnostic\Arr\Support\Traits\Macroable;
9
use IteratorAggregate;
10
use stdClass;
11
12
class LazyCollection implements Enumerable
13
{
14
    use EnumeratesValues, Macroable;
15
16
    /**
17
     * The source from which to generate items.
18
     *
19
     * @var callable|static
20
     */
21
    public $source;
22
23
    /**
24
     * Create a new lazy collection instance.
25
     *
26
     * @param  mixed  $source
27
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

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

Please refer to the PHP core documentation on constructors.

Loading history...
28
     */
29 236
    public function __construct($source = null)
30
    {
31 236
        if ($source instanceof Closure || $source instanceof self) {
32 165
            $this->source = $source;
33 236
        } elseif (is_null($source)) {
34 26
            $this->source = static::empty();
35
        } else {
36 236
            $this->source = $this->getArrayableItems($source);
37
        }
38 236
    }
39
40
    /**
41
     * Create a new instance with no items.
42
     *
43
     * @return static
44
     */
45 28
    public static function empty()
46
    {
47 28
        return new static([]);
48
    }
49
50
    /**
51
     * Create a new instance by invoking the callback a given amount of times.
52
     *
53
     * @param  int  $number
54
     * @param  callable|null  $callback
55
     * @return static
56
     */
57 1
    public static function times($number, callable $callback = null)
58
    {
59 1
        if ($number < 1) {
60 1
            return new static;
61
        }
62
63
        $instance = new static(function () use ($number) {
64 1
            for ($current = 1; $current <= $number; $current++) {
65 1
                yield $current;
66
            }
67 1
        });
68
69 1
        return is_null($callback) ? $instance : $instance->map($callback);
70
    }
71
72
    /**
73
     * Create an enumerable with the given range.
74
     *
75
     * @param  int  $from
76
     * @param  int  $to
77
     * @return static
78
     */
79
    public static function range($from, $to)
80
    {
81
        return new static(function () use ($from, $to) {
82
            for (; $from <= $to; $from++) {
83
                yield $from;
84
            }
85
        });
86
    }
87
88
    /**
89
     * Get all items in the enumerable.
90
     *
91
     * @return array
92
     */
93 195
    public function all()
94
    {
95 195
        if (is_array($this->source)) {
96 111
            return $this->source;
97
        }
98
99 163
        return iterator_to_array($this->getIterator());
100
    }
101
102
    /**
103
     * Eager load all items into a new lazy collection backed by an array.
104
     *
105
     * @return static
106
     */
107
    public function eager()
108
    {
109
        return new static($this->all());
110
    }
111
112
    /**
113
     * Cache values as they're enumerated.
114
     *
115
     * @return static
116
     */
117
    public function remember()
118
    {
119
        $iterator = $this->getIterator();
120
121
        $iteratorIndex = 0;
122
123
        $cache = [];
124
125
        return new static(function () use ($iterator, &$iteratorIndex, &$cache) {
126
            for ($index = 0; true; $index++) {
127
                if (array_key_exists($index, $cache)) {
128
                    yield $cache[$index][0] => $cache[$index][1];
129
130
                    continue;
131
                }
132
133
                if ($iteratorIndex < $index) {
134
                    $iterator->next();
135
136
                    $iteratorIndex++;
137
                }
138
139
                if (! $iterator->valid()) {
140
                    break;
141
                }
142
143
                $cache[$index] = [$iterator->key(), $iterator->current()];
144
145
                yield $cache[$index][0] => $cache[$index][1];
146
            }
147
        });
148
    }
149
150
    /**
151
     * Get the average value of a given key.
152
     *
153
     * @param  callable|string|null  $callback
154
     * @return mixed
155
     */
156 1
    public function avg($callback = null)
157
    {
158 1
        return $this->collect()->avg($callback);
159
    }
160
161
    /**
162
     * Get the median of a given key.
163
     *
164
     * @param  string|array|null  $key
165
     * @return mixed
166
     */
167 6
    public function median($key = null)
168
    {
169 6
        return $this->collect()->median($key);
170
    }
171
172
    /**
173
     * Get the mode of a given key.
174
     *
175
     * @param  string|array|null  $key
176
     * @return array|null
177
     */
178 4
    public function mode($key = null)
179
    {
180 4
        return $this->collect()->mode($key);
181
    }
182
183
    /**
184
     * Collapse the collection of items into a single array.
185
     *
186
     * @return static
187
     */
188 3
    public function collapse()
189
    {
190
        return new static(function () {
191 3
            foreach ($this as $values) {
192 3
                if (is_array($values) || $values instanceof Enumerable) {
193 3
                    foreach ($values as $value) {
194 3
                        yield $value;
195
                    }
196
                }
197
            }
198 3
        });
199
    }
200
201
    /**
202
     * Determine if an item exists in the enumerable.
203
     *
204
     * @param  mixed  $key
205
     * @param  mixed  $operator
206
     * @param  mixed  $value
207
     * @return bool
208
     */
209 4
    public function contains($key, $operator = null, $value = null)
210
    {
211 4
        if (func_num_args() === 1 && $this->useAsCallable($key)) {
212 4
            $placeholder = new stdClass;
213
214 4
            return $this->first($key, $placeholder) !== $placeholder;
215
        }
216
217 3
        if (func_num_args() === 1) {
218 2
            $needle = $key;
219
220 2
            foreach ($this as $value) {
221 2
                if ($value == $needle) {
222 2
                    return true;
223
                }
224
            }
225
226 2
            return false;
227
        }
228
229 3
        return $this->contains($this->operatorForWhere(...func_get_args()));
230
    }
231
232
    /**
233
     * Cross join the given iterables, returning all possible permutations.
234
     *
235
     * @param  array  ...$arrays
236
     * @return static
237
     */
238 1
    public function crossJoin(...$arrays)
239
    {
240 1
        return $this->passthru('crossJoin', func_get_args());
241
    }
242
243
    /**
244
     * Get the items that are not present in the given items.
245
     *
246
     * @param  mixed  $items
247
     * @return static
248
     */
249 3
    public function diff($items)
250
    {
251 3
        return $this->passthru('diff', func_get_args());
252
    }
253
254
    /**
255
     * Get the items that are not present in the given items, using the callback.
256
     *
257
     * @param  mixed  $items
258
     * @param  callable  $callback
259
     * @return static
260
     */
261 2
    public function diffUsing($items, callable $callback)
262
    {
263 2
        return $this->passthru('diffUsing', func_get_args());
264
    }
265
266
    /**
267
     * Get the items whose keys and values are not present in the given items.
268
     *
269
     * @param  mixed  $items
270
     * @return static
271
     */
272 2
    public function diffAssoc($items)
273
    {
274 2
        return $this->passthru('diffAssoc', func_get_args());
275
    }
276
277
    /**
278
     * Get the items whose keys and values are not present in the given items, using the callback.
279
     *
280
     * @param  mixed  $items
281
     * @param  callable  $callback
282
     * @return static
283
     */
284 1
    public function diffAssocUsing($items, callable $callback)
285
    {
286 1
        return $this->passthru('diffAssocUsing', func_get_args());
287
    }
288
289
    /**
290
     * Get the items whose keys are not present in the given items.
291
     *
292
     * @param  mixed  $items
293
     * @return static
294
     */
295 2
    public function diffKeys($items)
296
    {
297 2
        return $this->passthru('diffKeys', func_get_args());
298
    }
299
300
    /**
301
     * Get the items whose keys are not present in the given items, using the callback.
302
     *
303
     * @param  mixed  $items
304
     * @param  callable  $callback
305
     * @return static
306
     */
307 1
    public function diffKeysUsing($items, callable $callback)
308
    {
309 1
        return $this->passthru('diffKeysUsing', func_get_args());
310
    }
311
312
    /**
313
     * Retrieve duplicate items.
314
     *
315
     * @param  callable|null  $callback
316
     * @param  bool  $strict
317
     * @return static
318
     */
319 3
    public function duplicates($callback = null, $strict = false)
320
    {
321 3
        return $this->passthru('duplicates', func_get_args());
322
    }
323
324
    /**
325
     * Retrieve duplicate items using strict comparison.
326
     *
327
     * @param  callable|null  $callback
328
     * @return static
329
     */
330 1
    public function duplicatesStrict($callback = null)
331
    {
332 1
        return $this->passthru('duplicatesStrict', func_get_args());
333
    }
334
335
    /**
336
     * Get all items except for those with the specified keys.
337
     *
338
     * @param  mixed  $keys
339
     * @return static
340
     */
341 2
    public function except($keys)
342
    {
343 2
        return $this->passthru('except', func_get_args());
344
    }
345
346
    /**
347
     * Run a filter over each of the items.
348
     *
349
     * @param  callable|null  $callback
350
     * @return static
351
     */
352 26
    public function filter(callable $callback = null)
353
    {
354 26
        if (is_null($callback)) {
355
            $callback = function ($value) {
356 1
                return (bool) $value;
357 1
            };
358
        }
359
360
        return new static(function () use ($callback) {
361 26
            foreach ($this as $key => $value) {
362 26
                if ($callback($value, $key)) {
363 26
                    yield $key => $value;
364
                }
365
            }
366 26
        });
367
    }
368
369
    /**
370
     * Get the first item from the enumerable passing the given truth test.
371
     *
372
     * @param  callable|null  $callback
373
     * @param  mixed  $default
374
     * @return mixed
375
     */
376 10
    public function first(callable $callback = null, $default = null)
377
    {
378 10
        $iterator = $this->getIterator();
379
380 10
        if (is_null($callback)) {
381 3
            if (! $iterator->valid()) {
382 1
                return value($default);
383
            }
384
385 2
            return $iterator->current();
386
        }
387
388 7
        foreach ($iterator as $key => $value) {
389 7
            if ($callback($value, $key)) {
390 6
                return $value;
391
            }
392
        }
393
394 6
        return value($default);
395
    }
396
397
    /**
398
     * Get a flattened list of the items in the collection.
399
     *
400
     * @param  int  $depth
401
     * @return static
402
     */
403 3
    public function flatten($depth = INF)
404
    {
405
        $instance = new static(function () use ($depth) {
406 3
            foreach ($this as $item) {
407 3
                if (! is_array($item) && ! $item instanceof Enumerable) {
408 3
                    yield $item;
409 3
                } elseif ($depth === 1) {
410 2
                    yield from $item;
411
                } else {
412 3
                    yield from (new static($item))->flatten($depth - 1);
413
                }
414
            }
415 3
        });
416
417 3
        return $instance->values();
418
    }
419
420
    /**
421
     * Flip the items in the collection.
422
     *
423
     * @return static
424
     */
425 1
    public function flip()
426
    {
427
        return new static(function () {
428 1
            foreach ($this as $key => $value) {
429 1
                yield $value => $key;
430
            }
431 1
        });
432
    }
433
434
    /**
435
     * Get an item by key.
436
     *
437
     * @param  mixed  $key
438
     * @param  mixed  $default
439
     * @return mixed
440
     */
441 7
    public function get($key, $default = null)
442
    {
443 7
        if (is_null($key)) {
444 1
            return;
445
        }
446
447 6
        foreach ($this as $outerKey => $outerValue) {
448 6
            if ($outerKey == $key) {
449 6
                return $outerValue;
450
            }
451
        }
452
453
        return value($default);
454
    }
455
456
    /**
457
     * Group an associative array by a field or using a callback.
458
     *
459
     * @param  array|callable|string  $groupBy
460
     * @param  bool  $preserveKeys
461
     * @return static
462
     */
463 10
    public function groupBy($groupBy, $preserveKeys = false)
464
    {
465 10
        return $this->passthru('groupBy', func_get_args());
466
    }
467
468
    /**
469
     * Key an associative array by a field or using a callback.
470
     *
471
     * @param  callable|string  $keyBy
472
     * @return static
473
     */
474 4
    public function keyBy($keyBy)
475
    {
476
        return new static(function () use ($keyBy) {
477 4
            $keyBy = $this->valueRetriever($keyBy);
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $keyBy, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
478
479 4
            foreach ($this as $key => $item) {
480 4
                $resolvedKey = $keyBy($item, $key);
481
482 4
                if (is_object($resolvedKey)) {
483 1
                    $resolvedKey = (string) $resolvedKey;
484
                }
485
486 4
                yield $resolvedKey => $item;
487
            }
488 4
        });
489
    }
490
491
    /**
492
     * Determine if an item exists in the collection by key.
493
     *
494
     * @param  mixed  $key
495
     * @return bool
496
     */
497 2
    public function has($key)
498
    {
499 2
        $keys = array_flip(is_array($key) ? $key : func_get_args());
500 2
        $count = count($keys);
501
502 2
        foreach ($this as $key => $value) {
503 2
            if (array_key_exists($key, $keys) && --$count == 0) {
504 2
                return true;
505
            }
506
        }
507
508 2
        return false;
509
    }
510
511
    /**
512
     * Concatenate values of a given key as a string.
513
     *
514
     * @param  string  $value
515
     * @param  string|null  $glue
516
     * @return string
517
     */
518 1
    public function implode($value, $glue = null)
519
    {
520 1
        return $this->collect()->implode(...func_get_args());
521
    }
522
523
    /**
524
     * Intersect the collection with the given items.
525
     *
526
     * @param  mixed  $items
527
     * @return static
528
     */
529 2
    public function intersect($items)
530
    {
531 2
        return $this->passthru('intersect', func_get_args());
532
    }
533
534
    /**
535
     * Intersect the collection with the given items by key.
536
     *
537
     * @param  mixed  $items
538
     * @return static
539
     */
540 2
    public function intersectByKeys($items)
541
    {
542 2
        return $this->passthru('intersectByKeys', func_get_args());
543
    }
544
545
    /**
546
     * Determine if the items is empty or not.
547
     *
548
     * @return bool
549
     */
550 12
    public function isEmpty()
551
    {
552 12
        return ! $this->getIterator()->valid();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Traversable as the method valid() does only exist in the following implementations of said interface: APCUIterator, AppendIterator, ArrayIterator, CachingIterator, CallbackFilterIterator, Carbon\CarbonPeriod, DirectoryIterator, EmptyIterator, FilesystemIterator, FilterIterator, Generator, GlobIterator, HttpMessage, HttpRequestPool, Imagick, ImagickPixelIterator, InfiniteIterator, IteratorIterator, LimitIterator, MongoCommandCursor, MongoCursor, MongoGridFSCursor, MultipleIterator, NoRewindIterator, PHPUnit\Framework\TestSuiteIterator, PHPUnit\Runner\Filter\ExcludeGroupFilterIterator, PHPUnit\Runner\Filter\GroupFilterIterator, PHPUnit\Runner\Filter\IncludeGroupFilterIterator, PHPUnit\Runner\Filter\NameFilterIterator, PHPUnit\TextUI\Configura...stantCollectionIterator, PHPUnit\TextUI\Configura...ctoryCollectionIterator, PHPUnit\TextUI\Configura...nsionCollectionIterator, PHPUnit\TextUI\Configura...\FileCollectionIterator, PHPUnit\TextUI\Configura...ctoryCollectionIterator, PHPUnit\TextUI\Configura...rFileCollectionIterator, PHPUnit\TextUI\Configura...GroupCollectionIterator, PHPUnit\TextUI\Configura...ttingCollectionIterator, PHPUnit\TextUI\Configura...ctoryCollectionIterator, PHPUnit\TextUI\Configura...tFileCollectionIterator, PHPUnit\TextUI\Configura...SuiteCollectionIterator, PHPUnit\TextUI\Configura...iableCollectionIterator, PHP_Token_Stream, ParentIterator, Phar, PharData, PharIo\Manifest\AuthorCollectionIterator, PharIo\Manifest\AuthorElementCollection, PharIo\Manifest\BundledComponentCollectionIterator, PharIo\Manifest\ComponentElementCollection, PharIo\Manifest\ElementCollection, PharIo\Manifest\ExtElementCollection, PharIo\Manifest\RequirementCollectionIterator, RecursiveArrayIterator, RecursiveCachingIterator, RecursiveCallbackFilterIterator, RecursiveDirectoryIterator, RecursiveFilterIterator, RecursiveIteratorIterator, RecursiveRegexIterator, RecursiveTreeIterator, RegexIterator, SQLiteResult, SebastianBergmann\CodeCoverage\Node\Iterator, SebastianBergmann\FileIterator\Iterator, SebastianBergmann\Type\TestFixture\Iterator, SimpleXMLIterator, SplDoublyLinkedList, SplFileObject, SplFixedArray, SplHeap, SplMaxHeap, SplMinHeap, SplObjectStorage, SplPriorityQueue, SplQueue, SplStack, SplTempFileObject, TestIterator, TestIterator2, TheSeer\Tokenizer\TokenCollection.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
553
    }
554
555
    /**
556
     * Join all items from the collection using a string. The final items can use a separate glue string.
557
     *
558
     * @param  string  $glue
559
     * @param  string  $finalGlue
560
     * @return string
561
     */
562 1
    public function join($glue, $finalGlue = '')
563
    {
564 1
        return $this->collect()->join(...func_get_args());
565
    }
566
567
    /**
568
     * Get the keys of the collection items.
569
     *
570
     * @return static
571
     */
572 3
    public function keys()
573
    {
574
        return new static(function () {
575 3
            foreach ($this as $key => $value) {
576 3
                yield $key;
577
            }
578 3
        });
579
    }
580
581
    /**
582
     * Get the last item from the collection.
583
     *
584
     * @param  callable|null  $callback
585
     * @param  mixed  $default
586
     * @return mixed
587
     */
588 4
    public function last(callable $callback = null, $default = null)
589
    {
590 4
        $needle = $placeholder = new stdClass;
591
592 4
        foreach ($this as $key => $value) {
593 3
            if (is_null($callback) || $callback($value, $key)) {
594 2
                $needle = $value;
595
            }
596
        }
597
598 4
        return $needle === $placeholder ? value($default) : $needle;
599
    }
600
601
    /**
602
     * Get the values of a given key.
603
     *
604
     * @param  string|array  $value
605
     * @param  string|null  $key
606
     * @return static
607
     */
608 4
    public function pluck($value, $key = null)
609
    {
610
        return new static(function () use ($value, $key) {
611 4
            [$value, $key] = $this->explodePluckParameters($value, $key);
612
613 4
            foreach ($this as $item) {
614 4
                $itemValue = data_get($item, $value);
615
616 4
                if (is_null($key)) {
617 4
                    yield $itemValue;
618
                } else {
619 2
                    $itemKey = data_get($item, $key);
620
621 2
                    if (is_object($itemKey) && method_exists($itemKey, '__toString')) {
622
                        $itemKey = (string) $itemKey;
623
                    }
624
625 2
                    yield $itemKey => $itemValue;
626
                }
627
            }
628 4
        });
629
    }
630
631
    /**
632
     * Run a map over each of the items.
633
     *
634
     * @param  callable  $callback
635
     * @return static
636
     */
637 68
    public function map(callable $callback)
638
    {
639
        return new static(function () use ($callback) {
640 68
            foreach ($this as $key => $value) {
641 65
                yield $key => $callback($value, $key);
642
            }
643 68
        });
644
    }
645
646
    /**
647
     * Run a dictionary map over the items.
648
     *
649
     * The callback should return an associative array with a single key/value pair.
650
     *
651
     * @param  callable  $callback
652
     * @return static
653
     */
654 4
    public function mapToDictionary(callable $callback)
655
    {
656 4
        return $this->passthru('mapToDictionary', func_get_args());
657
    }
658
659
    /**
660
     * Run an associative map over each of the items.
661
     *
662
     * The callback should return an associative array with a single key/value pair.
663
     *
664
     * @param  callable  $callback
665
     * @return static
666
     */
667 5
    public function mapWithKeys(callable $callback)
668
    {
669
        return new static(function () use ($callback) {
670 5
            foreach ($this as $key => $value) {
671 5
                yield from $callback($value, $key);
672
            }
673 5
        });
674
    }
675
676
    /**
677
     * Merge the collection with the given items.
678
     *
679
     * @param  mixed  $items
680
     * @return static
681
     */
682 3
    public function merge($items)
683
    {
684 3
        return $this->passthru('merge', func_get_args());
685
    }
686
687
    /**
688
     * Recursively merge the collection with the given items.
689
     *
690
     * @param  mixed  $items
691
     * @return static
692
     */
693 3
    public function mergeRecursive($items)
694
    {
695 3
        return $this->passthru('mergeRecursive', func_get_args());
696
    }
697
698
    /**
699
     * Create a collection by using this collection for keys and another for its values.
700
     *
701
     * @param  mixed  $values
702
     * @return static
703
     */
704 2
    public function combine($values)
705
    {
706
        return new static(function () use ($values) {
707 2
            $values = $this->makeIterator($values);
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $values, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
708
709 2
            $errorMessage = 'Both parameters should have an equal number of elements';
710
711 2
            foreach ($this as $key) {
712 2
                if (! $values->valid()) {
713
                    trigger_error($errorMessage, E_USER_WARNING);
714
715
                    break;
716
                }
717
718 2
                yield $key => $values->current();
719
720 2
                $values->next();
721
            }
722
723 2
            if ($values->valid()) {
724
                trigger_error($errorMessage, E_USER_WARNING);
725
            }
726 2
        });
727
    }
728
729
    /**
730
     * Union the collection with the given items.
731
     *
732
     * @param  mixed  $items
733
     * @return static
734
     */
735 3
    public function union($items)
736
    {
737 3
        return $this->passthru('union', func_get_args());
738
    }
739
740
    /**
741
     * Create a new collection consisting of every n-th element.
742
     *
743
     * @param  int  $step
744
     * @param  int  $offset
745
     * @return static
746
     */
747 1
    public function nth($step, $offset = 0)
748
    {
749
        return new static(function () use ($step, $offset) {
750 1
            $position = 0;
751
752 1
            foreach ($this as $item) {
753 1
                if ($position % $step === $offset) {
754 1
                    yield $item;
755
                }
756
757 1
                $position++;
758
            }
759 1
        });
760
    }
761
762
    /**
763
     * Get the items with the specified keys.
764
     *
765
     * @param  mixed  $keys
766
     * @return static
767
     */
768 1
    public function only($keys)
769
    {
770 1
        if ($keys instanceof Enumerable) {
771 1
            $keys = $keys->all();
772 1
        } elseif (! is_null($keys)) {
773 1
            $keys = is_array($keys) ? $keys : func_get_args();
774
        }
775
776
        return new static(function () use ($keys) {
777 1
            if (is_null($keys)) {
778 1
                yield from $this;
779
            } else {
780 1
                $keys = array_flip($keys);
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $keys, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
781
782 1
                foreach ($this as $key => $value) {
783 1
                    if (array_key_exists($key, $keys)) {
784 1
                        yield $key => $value;
785
786 1
                        unset($keys[$key]);
787
788 1
                        if (empty($keys)) {
789 1
                            break;
790
                        }
791
                    }
792
                }
793
            }
794 1
        });
795
    }
796
797
    /**
798
     * Push all of the given items onto the collection.
799
     *
800
     * @param  iterable  $source
801
     * @return static
802
     */
803 15
    public function concat($source)
804
    {
805
        return (new static(function () use ($source) {
806 15
            yield from $this;
807 15
            yield from $source;
808 15
        }))->values();
809
    }
810
811
    /**
812
     * Get one or a specified number of items randomly from the collection.
813
     *
814
     * @param  int|null  $number
815
     * @return static|mixed
816
     *
817
     * @throws \InvalidArgumentException
818
     */
819 3
    public function random($number = null)
820
    {
821 3
        $result = $this->collect()->random(...func_get_args());
822
823 2
        return is_null($number) ? $result : new static($result);
824
    }
825
826
    /**
827
     * Reduce the collection to a single value.
828
     *
829
     * @param  callable  $callback
830
     * @param  mixed  $initial
831
     * @return mixed
832
     */
833 7
    public function reduce(callable $callback, $initial = null)
834
    {
835 7
        $result = $initial;
836
837 7
        foreach ($this as $value) {
838 6
            $result = $callback($result, $value);
839
        }
840
841 7
        return $result;
842
    }
843
844
    /**
845
     * Replace the collection items with the given items.
846
     *
847
     * @param  mixed  $items
848
     * @return static
849
     */
850 3
    public function replace($items)
851
    {
852
        return new static(function () use ($items) {
853 3
            $items = $this->getArrayableItems($items);
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $items, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

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