GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Failed Conditions
Push — master ( 1fefb9...e4e1fd )
by Dušan
02:42
created

src/collection_functions.php (8 issues)

Upgrade to new PHP Analysis Engine

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

1
<?php
2
3
namespace DusanKasan\Knapsack;
4
5
use ArrayIterator;
6
use DusanKasan\Knapsack\Exceptions\ItemNotFound;
7
use DusanKasan\Knapsack\Exceptions\NoMoreItems;
8
use Traversable;
9
10
/**
11
 * Converts $collection to array. If $collection is not array or Traversable, an array [$collection] will be returned.
12
 * If there are multiple items with the same key, only the last will be preserved.
13
 *
14
 * @param array|Traversable $collection
15
 * @return array
16
 */
17
function toArray($collection)
18
{
19 58
    $arr = [];
20 58
    foreach ($collection as $key => $value) {
21 58
        $arr[$key] = $value;
22 58
    }
23
24 58
    return $arr;
25
}
26
27
/**
28
 * Returns a lazy collection of distinct items in $collection.
29
 *
30
 * @param array|Traversable $collection
31
 * @return Collection
32
 */
33
function distinct($collection)
34
{
35
    $generatorFactory = function () use ($collection) {
36 1
        $distinctValues = [];
37
38 1
        foreach ($collection as $key => $value) {
39 1
            if (!in_array($value, $distinctValues)) {
40 1
                $distinctValues[] = $value;
41 1
                yield $key => $value;
42 1
            }
43 1
        }
44 1
    };
45
46 1
    return new Collection($generatorFactory);
47
}
48
49
/**
50
 * Returns number of items in $collection.
51
 *
52
 * @param array|Traversable $collection
53
 * @return int
54
 */
55
function size($collection)
56
{
57 8
    $result = 0;
58 8
    foreach ($collection as $value) {
59 8
        $result++;
60 8
    }
61
62 8
    return $result;
63
}
64
65
/**
66
 * Returns a non-lazy collection with items from $collection in reversed order.
67
 *
68
 * @param array|Traversable $collection
69
 * @return Collection
70
 */
71
function reverse($collection)
72
{
73
    $generatorFactory = function () use ($collection) {
74 4
        $array = [];
75 4
        foreach ($collection as $key => $value) {
76 3
            $array[] = [$key, $value];
77 4
        }
78
79 5
        return map(
80 4
            indexBy(
81 4
                array_reverse($array),
82
                function($item) {
83 3
                    return $item[0];
84
                }
85 4
            ),
86
            function($item) {
87 3
                return $item[1];
88
            }
89 4
        );
90 4
    };
91
92 4
    return new Collection($generatorFactory);
93
}
94
95
/**
96
 * Returns a lazy collection of values from $collection (i.e. the keys are reset).
97
 *
98
 * @param array|Traversable $collection
99
 * @return Collection
100
 */
101
function values($collection)
102
{
103
    $generatorFactory = function () use ($collection) {
104 29
        foreach ($collection as $value) {
105 26
            yield $value;
106 23
        }
107 29
    };
108
109 29
    return new Collection($generatorFactory);
110
}
111
112
/**
113
 * Returns a lazy collection of keys from $collection.
114
 *
115
 * @param array|Traversable $collection
116
 * @return Collection
117
 */
118
function keys($collection)
119
{
120
    $generatorFactory = function () use ($collection) {
121 1
        foreach ($collection as $key => $value) {
122 1
            yield $key;
123 1
        }
124 1
    };
125
126 1
    return new Collection($generatorFactory);
127
}
128
129
/**
130
 * Returns a lazy collection of items from $collection repeated infinitely.
131
 *
132
 * @param array|Traversable $collection
133
 * @return Collection
134
 */
135
function cycle($collection)
136
{
137
    $generatorFactory = function () use ($collection) {
138 1
        while (true) {
139 1
            foreach ($collection as $key => $value) {
140 1
                yield $key => $value;
141 1
            }
142 1
        }
143 1
    };
144
145 1
    return new Collection($generatorFactory);
146
}
147
148
/**
149
 * Returns a non-lazy collection of shuffled items from $collection.
150
 *
151
 * @param array|Traversable $collection
152
 * @return Collection
153
 */
154
function shuffle($collection)
155
{
156 1
    $buffer = [];
157 1
    foreach ($collection as $key => $value) {
158 1
        $buffer[] = [$key, $value];
159 1
    }
160
161 1
    \shuffle($buffer);
162
163 1
    return dereferenceKeyValue($buffer);
164
}
165
166
/**
167
 * Returns true if $collection does not contain any items.
168
 *
169
 * @param array|Traversable $collection
170
 * @return bool
171
 */
172
function isEmpty($collection)
173
{
174 2
    foreach ($collection as $value) {
175 1
        return false;
176 1
    }
177
178 1
    return true;
179
}
180
181
/**
182
 * Returns true if $collection does contain any items.
183
 *
184
 * @param array|Traversable $collection
185
 * @return bool
186
 */
187
function isNotEmpty($collection)
188
{
189 2
    return !isEmpty($collection);
190
}
191
192
/**
193
 * Returns a collection where keys are distinct values from $collection and values are number of occurrences of each
194
 * value.
195
 *
196
 * @param array|Traversable $collection
197
 * @return Collection
198
 */
199
function frequencies($collection)
200
{
201 1
    return countBy($collection, '\DusanKasan\Knapsack\identity');
202
}
203
204
/**
205
 * Returns the first item of $collection or throws ItemNotFound if #collection is empty.
206
 *
207
 * @param array|Traversable $collection
208
 * @return mixed
209
 */
210
function first($collection)
211
{
212 14
    return getNth($collection, 0);
213
}
214
215
/**
216
 * Returns the last item of $collection or throws ItemNotFound if #collection is empty.
217
 *
218
 * @param array|Traversable $collection
219
 * @return mixed
220
 */
221
function last($collection)
222
{
223 2
    return first(reverse($collection));
224
}
225
226
/**
227
 * Returns a lazy collection of items of $collection where value of each item is set to the return value of calling
228
 * $function on its value and key.
229
 *
230
 * @param array|Traversable $collection
231
 * @param callable $function ($value, $key)
232
 * @return Collection
233
 */
234 View Code Duplication
function map($collection, callable $function)
235
{
236
    $generatorFactory = function () use ($collection, $function) {
237 11
        foreach ($collection as $key => $value) {
238 10
            yield $key => $function($value, $key);
239 10
        }
240 11
    };
241
242 11
    return new Collection($generatorFactory);
243
}
244
245
/**
246
 * Returns a lazy collection of items from $collection for which $function returns true.
247
 *
248
 * @param array|Traversable $collection
249
 * @param callable $function ($value, $key)
250
 * @return Collection
251
 */
252 View Code Duplication
function filter($collection, callable $function)
253
{
254
    $generatorFactory = function () use ($collection, $function) {
255 6
        foreach ($collection as $key => $value) {
256 6
            if ($function($value, $key)) {
257 5
                yield $key => $value;
258 5
            }
259 6
        }
260 6
    };
261
262 6
    return new Collection($generatorFactory);
263
}
264
265
/**
266
 * Returns a lazy collection with items from all $collections passed as argument appended together
267
 *
268
 * @param array|Traversable ...$collections
269
 * @return Collection
270
 */
271
function concat(...$collections)
272
{
273
    $generatorFactory = function () use ($collections) {
274 3
        foreach ($collections as $collection) {
275 3
            foreach ($collection as $key => $value) {
276 3
                yield $key => $value;
277 3
            }
278 3
        }
279 3
    };
280
281 3
    return new Collection($generatorFactory);
282
}
283
284
/**
285
 * Reduces the collection to single value by iterating over the collection and calling $reduction while
286
 * passing $startValue and current key/item as parameters. The output of $function is used as $startValue in
287
 * next iteration. The output of $function on last element is the return value of this function.
288
 *
289
 * @param array|Traversable $collection
290
 * @param callable $function ($value, $key)
291
 * @param mixed $startValue
292
 * @return mixed
293
 */
294
function reduce($collection, callable $function, $startValue)
295
{
296 3
    $tmp = duplicate($startValue);
297
298 3
    foreach ($collection as $key => $value) {
299 3
        $tmp = $function($tmp, $value, $key);
300 3
    }
301
302 3
    return $tmp;
303
}
304
305
/**
306
 * Flattens multiple levels of nesting in collection. If $levelsToFlatten is not specified, flattens all levels of
307
 * nesting.
308
 *
309
 * @param array|Traversable $collection
310
 * @param int $levelsToFlatten -1 to flatten everything
311
 * @return Collection
312
 */
313
function flatten($collection, $levelsToFlatten = -1)
314
{
315
    $generatorFactory = function () use ($collection, $levelsToFlatten) {
316 2
        $flattenNextLevel = $levelsToFlatten < 0 || $levelsToFlatten > 0;
317 2
        $childLevelsToFlatten = $levelsToFlatten > 0 ? $levelsToFlatten - 1 : $levelsToFlatten;
318
319 2
        foreach ($collection as $key => $value) {
320 2
            if ($flattenNextLevel && (is_array($value) || $value instanceof Traversable)) {
321 2
                foreach (flatten($value, $childLevelsToFlatten) as $childKey => $childValue) {
322 2
                    yield $childKey => $childValue;
323 2
                }
324 2
            } else {
325 2
                yield $key => $value;
326
            }
327 2
        }
328 2
    };
329
330 2
    return new Collection($generatorFactory);
331
}
332
333
/**
334
 * Returns a non-lazy collection sorted using $collection($item1, $item2, $key1, $key2 ). $collection should
335
 * return true if first item is larger than the second and false otherwise.
336
 *
337
 * @param array|Traversable $collection
338
 * @param callable $function ($value1, $value2, $key1, $key2)
339
 * @return Collection
340
 */
341
function sort($collection, callable $function)
342
{
343 1
    $array = iterator_to_array(
344 1
        values(
345 1
            map(
346 1
                $collection,
347
                function ($value, $key) {
348 1
                    return [$key, $value];
349
                }
350 1
            )
351 1
        )
352 1
    );
353
354 1
    uasort(
355 1
        $array,
356
        function ($a, $b) use ($function) {
357 1
            return $function($a[1], $b[1], $a[0], $b[0]);
358
        }
359 1
    );
360
361 1
    return dereferenceKeyValue($array);
362
}
363
364
/**
365
 * Returns a lazy collection that is a part of $collection starting from $from position and ending in $to position.
366
 * If $to is not provided, the returned collection is contains all items from $from until end of $collection. All items
367
 * before $from are iterated over, but not included in result.
368
 *
369
 * @param array|Traversable $collection
370
 * @param int $from
371
 * @param int $to -1 to slice until end
372
 * @return Collection
373
 */
374
function slice($collection, $from, $to = -1)
375
{
376
    $generatorFactory = function () use ($collection, $from, $to) {
377 16
        $index = 0;
378 16
        foreach ($collection as $key => $value) {
379 16
            if ($index >= $from && ($index < $to || $to == -1)) {
380 15
                yield $key => $value;
381 16
            } elseif ($index >= $to && $to >= 0) {
382 11
                break;
383
            }
384
385 16
            $index++;
386 16
        }
387 16
    };
388
389 16
    return new Collection($generatorFactory);
390
}
391
392
/**
393
 * Returns a non-lazy collection of items grouped by the result of $function.
394
 *
395
 * @param array|Traversable $collection
396
 * @param callable $function ($value, $key)
397
 * @return Collection
398
 */
399
function groupBy($collection, callable $function)
400
{
401 3
    $result = [];
402
403 3
    foreach ($collection as $key => $value) {
404 3
        $newKey = $function($value, $key);
405
406 3
        $group = isset($result[$newKey]) ? $result[$newKey] : new Collection([]);
407 3
        $result[$newKey] = $group->append($value);
408 3
    }
409
410 3
    return Collection::from($result);
411
}
412
413
/**
414
 * Executes $function for each item in $collection
415
 *
416
 * @param array|Traversable $collection
417
 * @param callable $function ($value, $key)
418
 * @return Collection
419
 */
420 View Code Duplication
function each($collection, callable $function)
421
{
422
    $generatorFactory = function () use ($collection, $function) {
423 1
        foreach ($collection as $key => $value) {
424 1
            $function($value, $key);
425
426 1
            yield $key => $value;
427 1
        }
428 1
    };
429
430 1
    return new Collection($generatorFactory);
431
}
432
433
/**
434
 * Returns an item with $key key from $collection. If that key is not present, throws ItemNotFound.
435
 *
436
 * @param array|Traversable $collection
437
 * @param mixed $key
438
 * @return mixed
439
 */
440
function get($collection, $key)
441
{
442 20
    foreach ($collection as $valueKey => $value) {
443 17
        if ($key === $valueKey) {
444 17
            return $value;
445
        }
446 15
    }
447
448 8
    throw new ItemNotFound;
449
}
450
451
/**
452
 * Returns an item with $key key from $collection. If that key is not present, returns $default.
453
 *
454
 * @param array|Traversable $collection
455
 * @param mixed $key
456
 * @param mixed $default value returned if key is not found
457
 * @return mixed
458
 */
459
function getOrDefault($collection, $key, $default)
460
{
461
    try {
462 2
        return get($collection, $key);
463 2
    } catch (ItemNotFound $e) {
464 2
        return $default;
465
    }
466
}
467
468
/**
469
 * Returns the first item from $collection for which $function returns true. If item like that is not present, returns
470
 * $default.
471
 *
472
 * @param array|Traversable $collection
473
 * @param callable $function ($value, $key)
474
 * @param mixed $default
475
 * @return mixed
476
 */
477
function find($collection, callable $function, $default = null)
478
{
479 1
    foreach ($collection as $key => $value) {
480 1
        if ($function($value, $key)) {
481 1
            return $value;
482
        }
483 1
    }
484
485 1
    return $default;
486
}
487
488
/**
489
 * Returns a lazy collection by changing keys of $collection for each item to the result of $function for
490
 * that item.
491
 *
492
 * @param array|Traversable $collection
493
 * @param callable $function ($value, $key)
494
 * @return Collection
495
 */
496 View Code Duplication
function indexBy($collection, callable $function)
497
{
498
    $generatorFactory = function () use ($collection, $function) {
499 5
        foreach ($collection as $key => $value) {
500 4
            yield $function($value, $key) => $value;
501 4
        }
502 5
    };
503
504 5
    return new Collection($generatorFactory);
505
}
506
507
/**
508
 * Returns a non-lazy collection of items whose keys are the return values of $function and values are the number of
509
 * items in this collection for which the $function returned this value.
510
 *
511
 * @param array|Traversable $collection
512
 * @param callable $function ($value, $key)
513
 * @return Collection
514
 */
515
function countBy($collection, callable $function)
516
{
517 2
    return map(
518 2
        groupBy($collection, $function),
519
        '\DusanKasan\Knapsack\size'
520 2
    );
521
}
522
523
/**
524
 * Returns true if $function returns true for every item in $collection
525
 *
526
 * @param array|Traversable $collection
527
 * @param callable $function ($value, $key)
528
 * @return bool
529
 */
530
function every($collection, callable $function)
531
{
532 1
    foreach ($collection as $key => $value) {
533 1
        if (!$function($value, $key)) {
534 1
            return false;
535
        }
536 1
    }
537
538 1
    return true;
539
}
540
541
/**
542
 * Returns true if $function returns true for at least one item in $collection.
543
 *
544
 * @param array|Traversable $collection
545
 * @param callable $function ($value, $key)
546
 * @return bool
547
 */
548
function some($collection, callable $function)
549
{
550 1
    foreach ($collection as $key => $value) {
551 1
        if ($function($value, $key)) {
552 1
            return true;
553
        }
554 1
    }
555
556 1
    return false;
557
}
558
559
/**
560
 * Returns true if $needle is found in $collection values.
561
 *
562
 * @param $collection
563
 * @param mixed $needle
564
 * @return bool
565
 */
566
function contains($collection, $needle)
567
{
568 1
    foreach ($collection as $key => $value) {
569 1
        if ($value === $needle) {
570 1
            return true;
571
        }
572 1
    }
573
574 1
    return false;
575
}
576
577
/**
578
 * Reduce that walks from right to the left.
579
 *
580
 * @param array|Traversable $collection
581
 * @param callable $function
582
 * @param mixed $startValue
583
 * @return mixed
584
 */
585
function reduceRight($collection, callable $function, $startValue)
586
{
587 1
    return reduce(reverse($collection), $function, $startValue);
588
}
589
590
/**
591
 * Returns a lazy collection of first $numberOfItems items of $collection.
592
 *
593
 * @param array|Traversable $collection
594
 * @param int $numberOfItems
595
 * @return Collection
596
 */
597
function take($collection, $numberOfItems)
598
{
599 11
    return slice($collection, 0, $numberOfItems);
600
}
601
602
/**
603
 * Returns a lazy collection of all but first $numberOfItems items of $collection.
604
 *
605
 * @param array|Traversable $collection
606
 * @param int $numberOfItems
607
 * @return Collection
608
 */
609
function drop($collection, $numberOfItems)
610
{
611 6
    return slice($collection, $numberOfItems);
612
}
613
614
/**
615
 * Returns a lazy collection of values, where first value is $value and all subsequent values are computed by applying
616
 * $function to the last value in the collection. By default this produces an infinite collection. However you can
617
 * end the collection by throwing a NoMoreItems exception.
618
 *
619
 * @param mixed $value
620
 * @param callable $function ($value, $key)
621
 * @return Collection
622
 */
623
function iterate($value, callable $function)
624
{
625 4
    $duplicated = duplicate($value);
626
    $generatorFactory = function () use ($duplicated, $function) {
627 4
        $value = $duplicated;
628
629 4
        yield $value;
630
631 4
        while (true) {
632
            try {
633 4
                $value = $function($value);
634 4
                yield $value;
635 4
            } catch (NoMoreItems $e) {
636 2
                break;
637
            }
638 4
        }
639 4
    };
640
641 4
    return new Collection($generatorFactory);
642
}
643
644
/**
645
 * Returns a lazy collection of items from $collection for which $function returned true.
646
 *
647
 * @param array|Traversable $collection
648
 * @param callable $function ($value, $key)
649
 * @return Collection
650
 */
651
function reject($collection, callable $function)
652
{
653 2
    return filter(
654 2
        $collection,
655
        function($value, $key) use ($function) {
656 2
            return !$function($value, $key);
657
        }
658 2
    );
659
}
660
661
/**
662
 * Returns a lazy collection of items in $collection without the last $numberOfItems items.
663
 *
664
 * @param array|Traversable $collection
665
 * @param int $numberOfItems
666
 * @return Collection
667
 */
668
function dropLast($collection, $numberOfItems = 1)
669
{
670
    $generatorFactory = function () use ($collection, $numberOfItems) {
671 1
        $buffer = [];
672
673 1
        foreach ($collection as $key => $value) {
674 1
            $buffer[] = [$key, $value];
675
676 1
            if (count($buffer) > $numberOfItems) {
677 1
                $val = array_shift($buffer);
678 1
                yield $val[0] => $val[1];
679 1
            }
680 1
        }
681 1
    };
682
683 1
    return new Collection($generatorFactory);
684
}
685
686
/**
687
 * Returns a lazy collection of items from $collection separated by $separator.
688
 *
689
 * @param array|Traversable $collection
690
 * @param mixed $separator
691
 * @return Collection
692
 */
693
function interpose($collection, $separator)
694
{
695
    $generatorFactory = function () use ($collection, $separator) {
696 1
        foreach (take($collection, 1) as $key => $value) {
697 1
            yield $key => $value;
698 1
        }
699
700 1
        foreach (drop($collection, 1) as $key => $value) {
701 1
            yield $separator;
702 1
            yield $key => $value;
703 1
        }
704 1
    };
705
706 1
    return new Collection($generatorFactory);
707
}
708
709
/**
710
 * Returns a lazy collection of first item from first collection, first item from second, second from first and
711
 * so on. Accepts any number of collections.
712
 *
713
 * @param array|Traversable ...$collections
714
 * @return Collection
715
 */
716
function interleave(...$collections)
717
{
718
    $generatorFactory = function () use ($collections) {
719 1
        $normalizedCollection = array_map(
720
            function ($collection) {
721 1
                $c = (is_array($collection)) ? new ArrayIterator($collection) : $collection;
722 1
                $c->rewind();
723 1
                return $c;
724 1
            },
725
            $collections
726 1
        );
727
728
        do {
729 1
            $valid = false;
730 1
            foreach ($normalizedCollection as $collection) {
731 1
                if ($collection->valid()) {
732 1
                    yield $collection->key() => $collection->current();
733 1
                    $collection->next();
734 1
                    $valid = true;
735 1
                }
736 1
            }
737 1
        } while ($valid);
738 1
    };
739
740 1
    return new Collection($generatorFactory);
741
}
742
743
/**
744
 * Returns a lazy collection of items in $collection with $value added as first element. If $key is not provided
745
 * it will be next integer index.
746
 *
747
 * @param array|Traversable $collection
748
 * @param mixed $value
749
 * @param mixed|null $key
750
 * @return Collection
751
 */
752 View Code Duplication
function prepend($collection, $value, $key = null)
753
{
754
    $generatorFactory = function () use ($collection, $value, $key) {
755 2
        if ($key === null) {
756 1
            yield $value;
757 1
        } else {
758 1
            yield $key => $value;
759
        }
760
761 2
        foreach ($collection as $key => $value) {
762 2
            yield $key => $value;
763 2
        }
764 2
    };
765
766 2
    return new Collection($generatorFactory);
767
}
768
769
/**
770
 * Returns a lazy collection of items in $collection with $value added as last element. If $key is not provided
771
 * it will be next integer index.
772
 *
773
 * @param array|Traversable $collection
774
 * @param mixed $value
775
 * @param mixed|null $key
776
 * @return Collection
777
 */
778 View Code Duplication
function append($collection, $value, $key = null)
779
{
780
    $generatorFactory = function () use ($collection, $value, $key) {
781 7
        foreach ($collection as $k => $v) {
782 7
            yield $k => $v;
783 7
        }
784
785 7
        if ($key === null) {
786 6
            yield $value;
787 6
        } else {
788 1
            yield $key => $value;
789
        }
790 7
    };
791
792 7
    return new Collection($generatorFactory);
793
}
794
795
/**
796
 * Returns a lazy collection by removing items from $collection until first item for which $function returns false.
797
 *
798
 * @param array|Traversable $collection
799
 * @param callable $function ($value, $key)
800
 * @return Collection
801
 */
802 View Code Duplication
function dropWhile($collection, callable $function)
803
{
804
    $generatorFactory = function () use ($collection, $function) {
805 2
        $shouldDrop = true;
806 2
        foreach ($collection as $key => $value) {
807 2
            if ($shouldDrop) {
808 2
                $shouldDrop = $function($value, $key);
809 2
            }
810
811 2
            if (!$shouldDrop) {
812 2
                yield $key => $value;
813 2
            }
814 2
        }
815 2
    };
816
817 2
    return new Collection($generatorFactory);
818
}
819
820
/**
821
 * Returns a lazy collection of items from $collection until first item for which $function returns false.
822
 *
823
 * @param array|Traversable $collection
824
 * @param callable $function ($value, $key)
825
 * @return Collection
826
 */
827 View Code Duplication
function takeWhile($collection, callable $function)
828
{
829
    $generatorFactory = function () use ($collection, $function) {
830 2
        $shouldTake = true;
831 2
        foreach ($collection as $key => $value) {
832 2
            if ($shouldTake) {
833 2
                $shouldTake = $function($value, $key);
834 2
            }
835
836 2
            if ($shouldTake) {
837 2
                yield $key => $value;
838 2
            }
839 2
        }
840 2
    };
841
842 2
    return new Collection($generatorFactory);
843
}
844
845
/**
846
 * Returns a lazy collection. A result of calling map and flatten(1)
847
 *
848
 * @param array|Traversable $collection
849
 * @param callable $function ($value, $key)
850
 * @return Collection
851
 */
852
function mapcat($collection, callable $function)
853
{
854 1
    return flatten(map($collection, $function), 1);
855
}
856
857
/**
858
 * Returns a lazy collection [take($collection, $position), drop($collection, $position)]
859
 *
860
 * @param array|Traversable $collection
861
 * @param int $position
862
 * @return Collection
863
 */
864
function splitAt($collection, $position)
865
{
866
    $generatorFactory = function () use ($collection, $position) {
867 1
        yield take($collection, $position);
868 1
        yield drop($collection, $position);
869 1
    };
870
871 1
    return new Collection($generatorFactory);
872
}
873
874
/**
875
 * Returns a lazy collection [takeWhile($collection, $function), dropWhile($collection, $function)]
876
 *
877
 * @param array|Traversable $collection
878
 * @param callable $function ($value, $key)
879
 * @return Collection
880
 */
881
function splitWith($collection, callable $function)
882
{
883
    $generatorFactory = function () use ($collection, $function) {
884 1
        yield takeWhile($collection, $function);
885 1
        yield dropWhile($collection, $function);
886 1
    };
887
888 1
    return new Collection($generatorFactory);
889
}
890
891
/**
892
 * Returns a lazy collection with items from $collection but values that are found in keys of $replacementMap
893
 * are replaced by their values.
894
 *
895
 * @param array|Traversable $collection
896
 * @param array|Traversable $replacementMap
897
 * @return Collection
898
 */
899
function replace($collection, $replacementMap)
900
{
901
    $generatorFactory = function () use ($collection, $replacementMap) {
902 1
        foreach ($collection as $key => $value) {
903 1
            $newValue = getOrDefault($replacementMap, $value, $value);
904 1
            yield $key => $newValue;
905 1
        }
906 1
    };
907
908 1
    return new Collection($generatorFactory);
909
}
910
911
/**
912
 * Returns a lazy collection of reduction steps.
913
 *
914
 * @param array|Traversable $collection
915
 * @param callable $function
916
 * @param mixed $startValue
917
 * @return Collection
918
 */
919 View Code Duplication
function reductions($collection, callable $function, $startValue)
920
{
921
    $generatorFactory = function () use ($collection, $function, $startValue) {
922 1
        $tmp = duplicate($startValue);
923
924 1
        yield $tmp;
925 1
        foreach ($collection as $key => $value) {
926 1
            $tmp = $function($tmp, $value, $key);
927 1
            yield $tmp;
928 1
        }
929 1
    };
930
931 1
    return new Collection($generatorFactory);
932
}
933
934
/**
935
 * Returns a lazy collection of every nth ($step) item in  $collection.
936
 *
937
 * @param array|Traversable $collection
938
 * @param int $step
939
 * @return Collection
940
 */
941
function takeNth($collection, $step)
942
{
943
    $generatorFactory = function () use ($collection, $step) {
944 1
        $index = 0;
945 1
        foreach ($collection as $key => $value) {
946 1
            if ($index % $step == 0) {
947 1
                yield $key => $value;
948 1
            }
949
950 1
            $index++;
951 1
        }
952 1
    };
953
954 1
    return new Collection($generatorFactory);
955
}
956
957
/**
958
 * Returns a lazy collection of collections of $numberOfItems items each, at $step step
959
 * apart. If $step is not supplied, defaults to $numberOfItems, i.e. the partitions
960
 * do not overlap. If a $padding collection is supplied, use its elements as
961
 * necessary to complete last partition up to $numberOfItems items. In case there are
962
 * not enough padding elements, return a partition with less than $numberOfItems items.
963
 *
964
 * @param array|Traversable $collection
965
 * @param $numberOfItems
966
 * @param int $step
967
 * @param array|Traversable $padding
968
 * @return Collection
969
 */
970
function partition($collection, $numberOfItems, $step = -1, $padding = [])
971
{
972
    $generatorFactory = function () use ($collection, $numberOfItems, $step, $padding) {
973 1
        $buffer = [];
974 1
        $itemsToSkip = 0;
975 1
        $tmpStep = $step ?: $numberOfItems;
976
977 1
        foreach ($collection as $key => $value) {
978 1
            if (count($buffer) == $numberOfItems) {
979 1
                yield dereferenceKeyValue($buffer);
980
981 1
                $buffer = array_slice($buffer, $tmpStep);
982 1
                $itemsToSkip =  $tmpStep - $numberOfItems;
983 1
            }
984
985 1
            if ($itemsToSkip <= 0) {
986 1
                $buffer[] = [$key, $value];
987 1
            } else {
988 1
                $itemsToSkip--;
989
            }
990 1
        }
991
992 1
        yield take(
993 1
            concat(dereferenceKeyValue($buffer), $padding),
994
            $numberOfItems
995 1
        );
996 1
    };
997
998 1
    return new Collection($generatorFactory);
999
}
1000
1001
/**
1002
 * Returns a lazy collection created by partitioning $collection each time $function returned a different value.
1003
 *
1004
 * @param array|Traversable $collection
1005
 * @param callable $function
1006
 * @return Collection
1007
 */
1008
function partitionBy($collection, callable $function)
1009
{
1010
    $generatorFactory = function () use ($collection, $function) {
1011 1
        $result = null;
1012 1
        $buffer = [];
1013
1014 1
        foreach ($collection as $key => $value) {
1015 1
            $newResult = $function($value, $key);
1016
1017 1
            if (!empty($buffer) && $result != $newResult) {
1018 1
                yield dereferenceKeyValue($buffer);
1019 1
                $buffer = [];
1020 1
            }
1021
1022 1
            $result = $newResult;
1023 1
            $buffer[] = [$key, $value];
1024 1
        }
1025
1026 1
        if (!empty($buffer)) {
1027 1
            yield dereferenceKeyValue($buffer);
1028 1
        }
1029 1
    };
1030
1031 1
    return new Collection($generatorFactory);
1032
}
1033
1034
/**
1035
 * Returns nth ($position) item from $collection. If $position is greater than $collection size, throws ItemNotFound.
1036
 *
1037
 * @param array|Traversable $collection
1038
 * @param int $position
1039
 * @return mixed
1040
 */
1041
function getNth($collection, $position)
1042
{
1043 15
    return get(values($collection), $position);
1044
}
1045
1046
/**
1047
 * Returns a lazy collection by picking a $key key from each sub-collection of $collection if possible. Ignores
1048
 * non-collection items.
1049
 *
1050
 * @param array|Traversable $collection
1051
 * @param mixed $key
1052
 * @return Collection
1053
 */
1054
function pluck($collection, $key)
1055
{
1056
    $generatorFactory = function () use ($collection, $key) {
1057
1058 2
        return map(
1059 2
            filter(
1060 2
                $collection,
1061
                function ($item) use ($key) {
1062 2
                    return isCollection($item) && has($item, $key);
1063
                }
1064 2
            ),
1065
            function($value) use ($key) {
1066 2
                return get($value, $key);
1067
            }
1068 2
        );
1069 2
    };
1070
1071 2
    return new Collection($generatorFactory);
1072
1073
}
1074
1075
/**
1076
 * Returns a lazy collection of $value repeated $times times. If $times is not provided the collection is infinite.
1077
 *
1078
 * @param mixed $value
1079
 * @param int $times
1080
 * @return Collection
1081
 */
1082
function repeat($value, $times = -1)
1083
{
1084
    $generatorFactory = function () use ($value, $times) {
1085 3
        $tmpTimes = $times;
1086
1087 3
        while ($tmpTimes != 0) {
1088 3
            yield $value;
1089
1090 3
            $tmpTimes = $tmpTimes < 0 ? -1 : $tmpTimes - 1;
1091 3
        }
1092 3
    };
1093
1094 3
    return new Collection($generatorFactory);
1095
}
1096
1097
/**
1098
 * Returns a lazy collection of numbers starting at $start, incremented by $step until $end is reached.
1099
 *
1100
 * @param int $start
1101
 * @param int|null $end
1102
 * @param int $step
1103
 * @return Collection
1104
 */
1105
function range($start = 0, $end = null, $step = 1)
1106
{
1107
    $generatorFactory = function () use ($start, $end, $step) {
1108 2
        return iterate(
1109 2
            $start,
1110
            function($value) use ($step, $end) {
1111 2
                $result = $value + $step;
1112
1113 2
                if ($end !== null && $result > $end) {
1114 1
                    throw new NoMoreItems;
1115
                }
1116
1117 2
                return $result;
1118
            }
1119 2
        );
1120 2
    };
1121
1122 2
    return new Collection($generatorFactory);
1123
}
1124
1125
//helpers
1126
/**
1127
 * Returns true if $input is array or Traversable object.
1128
 *
1129
 * @param mixed $input
1130
 * @return bool
1131
 */
1132
function isCollection($input)
1133
{
1134 14
    return is_array($input) || $input instanceof Traversable;
1135
}
1136
1137
/**
1138
 * Returns duplicated/cloned $input that has no relation to the original one. Used for making sure there are no side
1139
 * effect in functions.
1140
 *
1141
 * @param $input
1142
 * @return mixed
1143
 */
1144
function duplicate($input)
1145
{
1146 8
    if (is_array($input)) {
1147
        return toArray(
1148
            map(
1149
                $input,
1150
                function($i) {
1151
                    return duplicate($i);
1152
                }
1153
            )
1154
        );
1155 8
    } elseif (is_object($input)) {
1156 2
        return clone $input;
1157
    } else {
1158 8
        return $input;
1159
    }
1160
}
1161
1162
/**
1163
 * Transforms [[$key, $value], [$key2, $value2]] into [$key => $value, $key2 => $value2]. Used as a helper
1164
 *
1165
 * @param array|Traversable $collection
1166
 * @return Collection
1167
 */
1168
function dereferenceKeyValue($collection)
1169
{
1170
    $generatorFactory = function () use ($collection) {
1171 4
        foreach ($collection as $value) {
1172 4
            yield $value[0] => $value[1];
1173 4
        }
1174 4
    };
1175
1176 4
    return new Collection($generatorFactory);
1177
}
1178
1179
/**
1180
 * Realizes collection - turns lazy collection into non-lazy one by iterating over it and storing the key/values.
1181
 *
1182
 * @param array|Traversable $collection
1183
 * @return Collection
1184
 */
1185
function realize($collection)
1186
{
1187 2
    return new Collection(toArray($collection));
1188
}
1189
1190
/**
1191
 * Returns the second item of $collection or throws ItemNotFound if $collection is empty or has 1 item.
1192
 *
1193
 * @param array|Traversable $collection
1194
 * @return mixed
1195
 */
1196
function second($collection)
1197
{
1198 2
    return first(drop($collection, 1));
1199
}
1200
1201
1202
/**
1203
 * Combines $keys and $values into a lazy collection. The resulting collection has length equal to the size of smaller
1204
 * argument If $strict is true, the size of both collections must be equal, otherwise ItemNotFound is thrown. When
1205
 * strict, the collection is realized immediately.
1206
 *
1207
 * @param array|Traversable $keys
1208
 * @param array|Traversable $values
1209
 * @param bool $strict
1210
 * @return Collection
1211
 */
1212
function combine($keys, $values, $strict = false)
1213
{
1214
    $generatorFactory = function () use ($keys, $values, $strict) {
1215 1
        $keys = is_array($keys) ? new ArrayIterator($keys) : $keys;
0 ignored issues
show
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...
1216 1
        $values = is_array($values) ? new ArrayIterator($values) : $values;
0 ignored issues
show
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...
1217 1
        $values->rewind();
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Traversable as the method rewind() does only exist in the following implementations of said interface: AppendIterator, ArrayIterator, CachingIterator, CallbackFilterIterator, DirectoryIterator, DoctrineTest\InstantiatorTestAsset\PharAsset, DusanKasan\Knapsack\Collection, EmptyIterator, File_Iterator, FilesystemIterator, FilterIterator, Generator, GlobIterator, HttpMessage, HttpRequestPool, Imagick, ImagickPixelIterator, InfiniteIterator, Issue523, IteratorIterator, LimitIterator, MongoCommandCursor, MongoCursor, MongoGridFSCursor, MultipleIterator, NoRewindIterator, PDepend\Input\Iterator, PDepend\Metrics\AnalyzerIterator, PDepend\Source\AST\ASTArtifactList, PDepend\Source\AST\ASTCl...erfaceReferenceIterator, PHPUnit_Runner_Filter_GroupFilterIterator, PHPUnit_Runner_Filter_Group_Exclude, PHPUnit_Runner_Filter_Group_Include, PHPUnit_Runner_Filter_Test, PHPUnit_Util_TestSuiteIterator, PHP_CodeCoverage_Report_Node_Iterator, PHP_Token_Stream, ParentIterator, Phar, PharData, RecursiveArrayIterator, RecursiveCachingIterator, RecursiveCallbackFilterIterator, RecursiveDirectoryIterator, RecursiveFilterIterator, RecursiveIteratorIterator, RecursiveRegexIterator, RecursiveTreeIterator, RegexIterator, SQLiteResult, SimpleXMLIterator, SplDoublyLinkedList, SplFileObject, SplFixedArray, SplHeap, SplMaxHeap, SplMinHeap, SplObjectStorage, SplPriorityQueue, SplQueue, SplStack, SplTempFileObject, Symfony\Component\Finder...or\CustomFilterIterator, Symfony\Component\Finder...DateRangeFilterIterator, Symfony\Component\Finder...epthRangeFilterIterator, Symfony\Component\Finder...DirectoryFilterIterator, Symfony\Component\Finder...\FileTypeFilterIterator, Symfony\Component\Finder...lecontentFilterIterator, Symfony\Component\Finder...\FilenameFilterIterator, Symfony\Component\Finder\Iterator\FilterIterator, Symfony\Component\Finder...tiplePcreFilterIterator, Symfony\Component\Finder...ator\PathFilterIterator, Symfony\Component\Finder...ursiveDirectoryIterator, Symfony\Component\Finder...SizeRangeFilterIterator, Symfony\Component\Finder...rator\InnerNameIterator, Symfony\Component\Finder...rator\InnerSizeIterator, Symfony\Component\Finder...rator\InnerTypeIterator, Symfony\Component\Finder\Tests\Iterator\Iterator, Symfony\Component\Finder...or\MockFileListIterator, Symfony\Component\Finder...tiplePcreFilterIterator, TestIterator, TestIterator2.

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...
1218
1219 1
        foreach ($keys as $key) {
1220 1
            if (!$values->valid()) {
0 ignored issues
show
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: AppendIterator, ArrayIterator, CachingIterator, CallbackFilterIterator, DirectoryIterator, DoctrineTest\InstantiatorTestAsset\PharAsset, DusanKasan\Knapsack\Collection, EmptyIterator, File_Iterator, FilesystemIterator, FilterIterator, Generator, GlobIterator, HttpMessage, HttpRequestPool, Imagick, ImagickPixelIterator, InfiniteIterator, Issue523, IteratorIterator, LimitIterator, MongoCommandCursor, MongoCursor, MongoGridFSCursor, MultipleIterator, NoRewindIterator, PDepend\Input\Iterator, PDepend\Metrics\AnalyzerIterator, PDepend\Source\AST\ASTArtifactList, PDepend\Source\AST\ASTCl...erfaceReferenceIterator, PHPUnit_Runner_Filter_GroupFilterIterator, PHPUnit_Runner_Filter_Group_Exclude, PHPUnit_Runner_Filter_Group_Include, PHPUnit_Runner_Filter_Test, PHPUnit_Util_TestSuiteIterator, PHP_CodeCoverage_Report_Node_Iterator, PHP_Token_Stream, ParentIterator, Phar, PharData, RecursiveArrayIterator, RecursiveCachingIterator, RecursiveCallbackFilterIterator, RecursiveDirectoryIterator, RecursiveFilterIterator, RecursiveIteratorIterator, RecursiveRegexIterator, RecursiveTreeIterator, RegexIterator, SQLiteResult, SimpleXMLIterator, SplDoublyLinkedList, SplFileObject, SplFixedArray, SplHeap, SplMaxHeap, SplMinHeap, SplObjectStorage, SplPriorityQueue, SplQueue, SplStack, SplTempFileObject, Symfony\Component\Finder...or\CustomFilterIterator, Symfony\Component\Finder...DateRangeFilterIterator, Symfony\Component\Finder...epthRangeFilterIterator, Symfony\Component\Finder...DirectoryFilterIterator, Symfony\Component\Finder...\FileTypeFilterIterator, Symfony\Component\Finder...lecontentFilterIterator, Symfony\Component\Finder...\FilenameFilterIterator, Symfony\Component\Finder\Iterator\FilterIterator, Symfony\Component\Finder...tiplePcreFilterIterator, Symfony\Component\Finder...ator\PathFilterIterator, Symfony\Component\Finder...ursiveDirectoryIterator, Symfony\Component\Finder...SizeRangeFilterIterator, Symfony\Component\Finder...rator\InnerNameIterator, Symfony\Component\Finder...rator\InnerSizeIterator, Symfony\Component\Finder...rator\InnerTypeIterator, Symfony\Component\Finder\Tests\Iterator\Iterator, Symfony\Component\Finder...or\MockFileListIterator, Symfony\Component\Finder...tiplePcreFilterIterator, TestIterator, TestIterator2.

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...
1221 1
                break;
1222
            }
1223
1224 1
            yield $key => $values->current();
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Traversable as the method current() does only exist in the following implementations of said interface: AppendIterator, ArrayIterator, CachingIterator, CallbackFilterIterator, DirectoryIterator, DoctrineTest\InstantiatorTestAsset\PharAsset, DusanKasan\Knapsack\Collection, EmptyIterator, File_Iterator, FilesystemIterator, FilterIterator, Generator, GlobIterator, HttpMessage, HttpRequestPool, Imagick, ImagickPixelIterator, InfiniteIterator, Issue523, IteratorIterator, LimitIterator, MongoCommandCursor, MongoCursor, MongoGridFSCursor, MultipleIterator, NoRewindIterator, PDepend\Input\Iterator, PDepend\Metrics\AnalyzerIterator, PDepend\Source\AST\ASTArtifactList, PDepend\Source\AST\ASTCl...erfaceReferenceIterator, PHPUnit_Runner_Filter_GroupFilterIterator, PHPUnit_Runner_Filter_Group_Exclude, PHPUnit_Runner_Filter_Group_Include, PHPUnit_Runner_Filter_Test, PHPUnit_Util_TestSuiteIterator, PHP_CodeCoverage_Report_Node_Iterator, PHP_Token_Stream, ParentIterator, Phar, PharData, RecursiveArrayIterator, RecursiveCachingIterator, RecursiveCallbackFilterIterator, RecursiveDirectoryIterator, RecursiveFilterIterator, RecursiveIteratorIterator, RecursiveRegexIterator, RecursiveTreeIterator, RegexIterator, SQLiteResult, SimpleXMLIterator, SplDoublyLinkedList, SplFileObject, SplFixedArray, SplHeap, SplMaxHeap, SplMinHeap, SplObjectStorage, SplPriorityQueue, SplQueue, SplStack, SplTempFileObject, Symfony\Component\Finder...or\CustomFilterIterator, Symfony\Component\Finder...DateRangeFilterIterator, Symfony\Component\Finder...epthRangeFilterIterator, Symfony\Component\Finder...DirectoryFilterIterator, Symfony\Component\Finder...\FileTypeFilterIterator, Symfony\Component\Finder...lecontentFilterIterator, Symfony\Component\Finder...\FilenameFilterIterator, Symfony\Component\Finder\Iterator\FilterIterator, Symfony\Component\Finder...tiplePcreFilterIterator, Symfony\Component\Finder...ator\PathFilterIterator, Symfony\Component\Finder...ursiveDirectoryIterator, Symfony\Component\Finder...SizeRangeFilterIterator, Symfony\Component\Finder...rator\InnerNameIterator, Symfony\Component\Finder...rator\InnerSizeIterator, Symfony\Component\Finder...rator\InnerTypeIterator, Symfony\Component\Finder\Tests\Iterator\Iterator, Symfony\Component\Finder...or\MockFileListIterator, Symfony\Component\Finder...tiplePcreFilterIterator, TestIterator, TestIterator2.

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...
1225 1
            $values->next();
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Traversable as the method next() does only exist in the following implementations of said interface: AppendIterator, ArrayIterator, CachingIterator, CallbackFilterIterator, DirectoryIterator, DoctrineTest\InstantiatorTestAsset\PharAsset, DusanKasan\Knapsack\Collection, EmptyIterator, File_Iterator, FilesystemIterator, FilterIterator, Generator, GlobIterator, HttpMessage, HttpRequestPool, Imagick, ImagickPixelIterator, InfiniteIterator, Issue523, IteratorIterator, LimitIterator, MongoCommandCursor, MongoCursor, MongoGridFSCursor, MultipleIterator, NoRewindIterator, PDepend\Input\Iterator, PDepend\Metrics\AnalyzerIterator, PDepend\Source\AST\ASTArtifactList, PDepend\Source\AST\ASTCl...erfaceReferenceIterator, PHPUnit_Runner_Filter_GroupFilterIterator, PHPUnit_Runner_Filter_Group_Exclude, PHPUnit_Runner_Filter_Group_Include, PHPUnit_Runner_Filter_Test, PHPUnit_Util_TestSuiteIterator, PHP_CodeCoverage_Report_Node_Iterator, PHP_Token_Stream, ParentIterator, Phar, PharData, RecursiveArrayIterator, RecursiveCachingIterator, RecursiveCallbackFilterIterator, RecursiveDirectoryIterator, RecursiveFilterIterator, RecursiveIteratorIterator, RecursiveRegexIterator, RecursiveTreeIterator, RegexIterator, SQLiteResult, SimpleXMLIterator, SplDoublyLinkedList, SplFileObject, SplFixedArray, SplHeap, SplMaxHeap, SplMinHeap, SplObjectStorage, SplPriorityQueue, SplQueue, SplStack, SplTempFileObject, Symfony\Component\Finder...or\CustomFilterIterator, Symfony\Component\Finder...DateRangeFilterIterator, Symfony\Component\Finder...epthRangeFilterIterator, Symfony\Component\Finder...DirectoryFilterIterator, Symfony\Component\Finder...\FileTypeFilterIterator, Symfony\Component\Finder...lecontentFilterIterator, Symfony\Component\Finder...\FilenameFilterIterator, Symfony\Component\Finder\Iterator\FilterIterator, Symfony\Component\Finder...tiplePcreFilterIterator, Symfony\Component\Finder...ator\PathFilterIterator, Symfony\Component\Finder...ursiveDirectoryIterator, Symfony\Component\Finder...SizeRangeFilterIterator, Symfony\Component\Finder...rator\InnerNameIterator, Symfony\Component\Finder...rator\InnerSizeIterator, Symfony\Component\Finder...rator\InnerTypeIterator, Symfony\Component\Finder\Tests\Iterator\Iterator, Symfony\Component\Finder...or\MockFileListIterator, Symfony\Component\Finder...tiplePcreFilterIterator, TestIterator, TestIterator2.

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...
1226 1
        }
1227
1228 1
        if ($strict && ($keys->valid() || $values->valid())) {
0 ignored issues
show
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: AppendIterator, ArrayIterator, CachingIterator, CallbackFilterIterator, DirectoryIterator, DoctrineTest\InstantiatorTestAsset\PharAsset, DusanKasan\Knapsack\Collection, EmptyIterator, File_Iterator, FilesystemIterator, FilterIterator, Generator, GlobIterator, HttpMessage, HttpRequestPool, Imagick, ImagickPixelIterator, InfiniteIterator, Issue523, IteratorIterator, LimitIterator, MongoCommandCursor, MongoCursor, MongoGridFSCursor, MultipleIterator, NoRewindIterator, PDepend\Input\Iterator, PDepend\Metrics\AnalyzerIterator, PDepend\Source\AST\ASTArtifactList, PDepend\Source\AST\ASTCl...erfaceReferenceIterator, PHPUnit_Runner_Filter_GroupFilterIterator, PHPUnit_Runner_Filter_Group_Exclude, PHPUnit_Runner_Filter_Group_Include, PHPUnit_Runner_Filter_Test, PHPUnit_Util_TestSuiteIterator, PHP_CodeCoverage_Report_Node_Iterator, PHP_Token_Stream, ParentIterator, Phar, PharData, RecursiveArrayIterator, RecursiveCachingIterator, RecursiveCallbackFilterIterator, RecursiveDirectoryIterator, RecursiveFilterIterator, RecursiveIteratorIterator, RecursiveRegexIterator, RecursiveTreeIterator, RegexIterator, SQLiteResult, SimpleXMLIterator, SplDoublyLinkedList, SplFileObject, SplFixedArray, SplHeap, SplMaxHeap, SplMinHeap, SplObjectStorage, SplPriorityQueue, SplQueue, SplStack, SplTempFileObject, Symfony\Component\Finder...or\CustomFilterIterator, Symfony\Component\Finder...DateRangeFilterIterator, Symfony\Component\Finder...epthRangeFilterIterator, Symfony\Component\Finder...DirectoryFilterIterator, Symfony\Component\Finder...\FileTypeFilterIterator, Symfony\Component\Finder...lecontentFilterIterator, Symfony\Component\Finder...\FilenameFilterIterator, Symfony\Component\Finder\Iterator\FilterIterator, Symfony\Component\Finder...tiplePcreFilterIterator, Symfony\Component\Finder...ator\PathFilterIterator, Symfony\Component\Finder...ursiveDirectoryIterator, Symfony\Component\Finder...SizeRangeFilterIterator, Symfony\Component\Finder...rator\InnerNameIterator, Symfony\Component\Finder...rator\InnerSizeIterator, Symfony\Component\Finder...rator\InnerTypeIterator, Symfony\Component\Finder\Tests\Iterator\Iterator, Symfony\Component\Finder...or\MockFileListIterator, Symfony\Component\Finder...tiplePcreFilterIterator, TestIterator, TestIterator2.

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...
1229 1
            throw new ItemNotFound;
1230
        }
1231 1
    };
1232
1233 1
    $collection = new Collection($generatorFactory);
1234
1235 1
    if ($strict) {
1236 1
        $collection = realize($collection);
1237
    }
1238
1239 1
    return $collection;
1240
}
1241
1242
/**
1243
 * Returns a lazy collection without the items associated to any of the keys from $keys.
1244
 *
1245
 * @param array|Traversable $collection
1246
 * @param array|Traversable $keys
1247
 * @return Collection
1248
 */
1249
function except($collection, $keys)
1250
{
1251 1
    $keys = toArray(values($keys));
1252
1253 1
    return reject(
1254 1
        $collection,
1255
        function ($value, $key) use ($keys) {
1256 1
            return in_array($key, $keys);
1257
        }
1258 1
    );
1259
}
1260
1261
/**
1262
 * Returns a lazy collection of items associated to any of the keys from $keys.
1263
 *
1264
 * @param array|Traversable $collection
1265
 * @param array|Traversable $keys
1266
 * @return Collection
1267
 */
1268
function only($collection, $keys)
1269
{
1270 1
    $keys = toArray(values($keys));
1271
1272 1
    return filter(
1273 1
        $collection,
1274
        function ($value, $key) use ($keys) {
1275 1
            return in_array($key, $keys);
1276
        }
1277 1
    );
1278
}
1279
1280
/**
1281
 * Returns a lazy collection of items that are in $collection but are not in any of the other arguments. Note that the
1282
 * ...$collections are iterated non-lazily.
1283
 *
1284
 * @param array|Traversable $collection
1285
 * @param array|Traversable ...$collections
1286
 * @return Collection
1287
 */
1288
function difference($collection, ...$collections)
1289
{
1290 1
    $valuesToCompare = toArray(values(concat(...$collections)));
1291
    $generatorFactory = function () use ($collection, $valuesToCompare) {
1292 1
        foreach ($collection as $key => $value) {
1293 1
            if (!in_array($value, $valuesToCompare)) {
1294 1
                yield $key => $value;
1295 1
            }
1296 1
        }
1297 1
    };
1298
1299 1
    return new Collection($generatorFactory);
1300
}
1301
1302
/**
1303
 * Returns a lazy collection where keys and values are flipped.
1304
 *
1305
 * @param array|Traversable $collection
1306
 * @return Collection
1307
 */
1308
function flip($collection)
1309
{
1310
    $generatorFactory = function () use ($collection) {
1311 1
        foreach ($collection as $key => $value) {
1312 1
            yield $value => $key;
1313 1
        }
1314 1
    };
1315
1316 1
    return new Collection($generatorFactory);
1317
}
1318
1319
/**
1320
 * @param array|Traversable $collection
1321
 * @param mixed $key
1322
 * @return bool
1323
 */
1324
function has($collection, $key)
0 ignored issues
show
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1325
{
1326
    try {
1327 3
        get($collection, $key);
1328 3
        return true;
1329 2
    } catch (ItemNotFound $key) {
1330 2
        return false;
1331
    }
1332
}
1333