Completed
Pull Request — master (#49)
by Luke
10:01 queued 06:50
created

Collection::split()   B

Complexity

Conditions 5
Paths 1

Size

Total Lines 34
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 20
nc 1
nop 1
dl 0
loc 34
rs 8.439
c 0
b 0
f 0
1
<?php
2
/*
3
 * Nozavroni/Collections
4
 * Just another collections library for PHP5.6+.
5
 *
6
 * @copyright Copyright (c) 2016 Luke Visinoni <[email protected]>
7
 * @author    Luke Visinoni <[email protected]>
8
 * @license   https://github.com/nozavroni/collections/blob/master/LICENSE The MIT License (MIT)
9
 */
10
namespace Noz\Collection;
11
12
use InvalidArgumentException;
13
use OutOfBoundsException;
14
15
use ArrayIterator;
16
use Countable;
17
use Iterator;
18
use Traversable;
19
20
use Noz\Contracts\Arrayable;
21
use Noz\Contracts\Invokable;
22
use Noz\Contracts\CollectionInterface;
23
24
use Noz\Traits\IsArrayable;
25
use Noz\Traits\IsContainer;
26
use Noz\Traits\IsSerializable;
27
28
use function
29
    Noz\is_traversable,
30
    Noz\typeof,
31
    Noz\collect,
32
    Noz\is_arrayable,
33
    Noz\to_array;
34
35
/**
36
 * Class Collection.
37
 *
38
 * This is the abstract class that all other collection classes are based on.
39
 * Although it's possible to use a completely custom Collection class by simply
40
 * implementing the "Collectable" interface, extending this class gives you a
41
 * whole slew of convenient methods for free.
42
 *
43
 * @package Noz\Collection
44
 *
45
 * @author Luke Visinoni <[email protected]>
46
 * @copyright Copyright (c) 2016 Luke Visinoni <[email protected]>
47
 *
48
 * @todo Implement Serializable, other Interfaces
49
 */
50
class Collection implements
51
    CollectionInterface,
52
    Arrayable,
53
    Invokable,
54
    Countable,
55
    Iterator
56
{
57
    use IsArrayable, IsContainer, IsSerializable;
58
59
    /**
60
     * @var array The collection of data this object represents
61
     */
62
    private $data = [];
63
64
    /**
65
     * @var bool True unless we have advanced past the end of the data array
66
     */
67
    protected $isValid = true;
68
69
    /**
70
     * AbstractCollection constructor.
71
     *
72
     * @param mixed $data The data to wrap
73
     */
74
    public function __construct($data = null)
75
    {
76
        if (is_null($data)) {
77
            $data = [];
78
        }
79
        if (!is_traversable($data)) {
80
            throw new InvalidArgumentException(sprintf(
81
                'Invalid input for %s. Expecting traversable data, got "%s".',
82
                __METHOD__,
83
                typeof($data)
84
            ));
85
        }
86
        $this->setData($data);
87
    }
88
89
    public function __invoke()
90
    {
91
        $args = collect(func_get_args());
92
        if ($args->hasOffset(0)) {
93
            if ($args->hasOffset(1)) {
94
                // two args only...
95
                return $this->set($args->getOffset(0), $args->getOffset(1));
96
            }
97
            // one arg only...
98
            $arg1 = $args->getOffset(0);
99
            if (is_scalar($arg1)) {
100
                return $this->get($arg1);
101
            }
102
            if (is_traversable($arg1)) {
103
                return $this->union($arg1);
0 ignored issues
show
Bug introduced by
It seems like $arg1 defined by $args->getOffset(0) on line 98 can also be of type null or object; however, Noz\Collection\Collection::union() does only seem to accept array|object<Noz\Contracts\CollectionInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
104
            }
105
            // @todo Should probably throw ane invalid arg exception here...
106
        }
107
        return $this->toArray();
108
    }
109
110
    /**
111
     * Set underlying data array.
112
     *
113
     * Sets the collection data. This method should NEVER be called anywhere other than in __construct().
114
     *
115
     * @param array|Traversable $data The data to wrap
116
     */
117
    private function setData($data)
118
    {
119
        $arr = [];
120
        foreach ($data as $index => $value) {
121
            $arr[$index] = $value;
122
        }
123
        $this->data = $arr;
124
        $this->rewind();
125
    }
126
127
    /**
128
     * Get copy of underlying data array.
129
     *
130
     * Returns a copy of this collection's underlying data array. It returns a copy because collections are supposed to
131
     * be immutable. Nothing outside of the constructor should ever have direct access to the actual underlying array.
132
     *
133
     * @return array
134
     */
135
    protected function getData()
136
    {
137
        return $data = $this->data;
0 ignored issues
show
Unused Code introduced by
$data is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
138
    }
139
140
    /**
141
     * @inheritDoc
142
     */
143
    public function count(callable $callback = null)
144
    {
145
        if (!is_null($callback)) {
146
            return $this->filter($callback)->count();
147
        }
148
        return count($this->getData());
149
    }
150
151
    /**
152
     * Return the current element.
153
     *
154
     * Returns the current element in the collection. The internal array pointer
155
     * of the data array wrapped by the collection should not be advanced by this
156
     * method. No side effects. Return current element only.
157
     *
158
     * @return mixed
159
     */
160
    public function current()
161
    {
162
        return current($this->data);
163
    }
164
165
    /**
166
     * Return the current key.
167
     *
168
     * Returns the current key in the collection. No side effects.
169
     *
170
     * @return mixed
171
     */
172
    public function key()
173
    {
174
        return key($this->data);
175
    }
176
177
    /**
178
     * Advance the internal pointer forward.
179
     *
180
     * Although this method will return the current value after advancing the
181
     * pointer, you should not expect it to. The interface does not require it
182
     * to return any value at all.
183
     *
184
     * @return mixed
185
     */
186
    public function next()
187
    {
188
        $next = next($this->data);
189
        $key  = key($this->data);
190
        if (isset($key)) {
191
            return $next;
192
        }
193
        $this->isValid = false;
194
    }
195
196
    /**
197
     * Rewind the internal pointer.
198
     *
199
     * Return the internal pointer to the first element in the collection. Again,
200
     * this method is not required to return anything by its interface, so you
201
     * should not count on a return value.
202
     *
203
     * @return mixed
204
     */
205
    public function rewind()
206
    {
207
        $this->isValid = !empty($this->data);
208
209
        return reset($this->data);
210
    }
211
212
    /**
213
     * Is internal pointer in a valid position?
214
     *
215
     * If the internal pointer is advanced beyond the end of the collection, this method will return false.
216
     *
217
     * @return bool True if internal pointer isn't past the end
218
     */
219
    public function valid()
220
    {
221
        return $this->isValid;
222
    }
223
224
    /**
225
     * @inheritDoc
226
     */
227
    public function sort($alg = null)
228
    {
229
        if (is_null($alg)) {
230
            $alg = 'strnatcasecmp';
231
        }
232
        $data = $this->getData();
233
        uasort($data, $alg);
234
235
        return collect($data);
236
    }
237
238
    /**
239
     * @inheritDoc
240
     */
241
    public function sortkeys($alg = null)
242
    {
243
        if (is_null($alg)) {
244
            $alg = 'strnatcasecmp';
245
        }
246
        $data = $this->getData();
247
        uksort($data, $alg);
248
249
        return collect($data);
250
    }
251
252
    /**
253
     * Does this collection have a value at given index?
254
     *
255
     * @param mixed $index The index to check
256
     *
257
     * @return bool
258
     */
259
    public function has($index)
260
    {
261
        return array_key_exists($index, $this->getData());
262
    }
263
264
    /**
265
     * Set value at given index.
266
     *
267
     * This method simulates setting a value in this collection, but because collections are immutable, it actually
268
     * returns a copy of this collection with the value in the new collection set to specified value.
269
     *
270
     * @param mixed $index The index to set a value at
271
     * @param mixed $val   The value to set $index to
272
     *
273
     * @return CollectionInterface
274
     */
275
    public function set($index, $val)
276
    {
277
        $copy = $this->getData();
278
        $copy[$index] = $val;
279
        return collect($copy);
280
    }
281
282
    /**
283
     * Unset (delete) value at the given index.
284
     *
285
     * Get copy of collection with given index removed.
286
     *
287
     * @param mixed $index The index to unset
288
     *
289
     * @return CollectionInterface
290
     */
291
    public function delete($index)
292
    {
293
        return $this->except([$index]);
294
    }
295
296
    /**
297
     * Get index of a value.
298
     *
299
     * Given a value, this method will return the index of the first occurrence of that value.
300
     *
301
     * @param mixed $value Value to get the index of
302
     *
303
     * @return int|null|string
304
     */
305
    public function indexOf($value)
306
    {
307
        return $this->fold(function($carry, $val, $key, $iter) use ($value) {
0 ignored issues
show
Unused Code introduced by
The parameter $iter 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...
308
            if (is_null($carry) && $val == $value) {
309
                return $key;
310
            }
311
            return $carry;
312
        });
313
    }
314
315
    /**
316
     * Get this collection's keys as a collection.
317
     *
318
     * @return CollectionInterface Containing this collection's keys
319
     */
320
    public function keys()
321
    {
322
        return collect(array_keys($this->getData()));
323
    }
324
325
    /**
326
     * Get this collection's values as a collection.
327
     *
328
     * This method returns this collection's values but completely re-indexed (numerically).
329
     *
330
     * @return CollectionInterface Containing this collection's values
331
     */
332
    public function values()
333
    {
334
        return collect(array_values($this->getData()));
335
    }
336
337
    /**
338
     * Pad collection to a certain size.
339
     *
340
     * Returns a new collection, padded to the given size, with the given value.
341
     *
342
     * @param int   $size The number of items that should be in the collection
343
     * @param mixed $with The value to pad the collection with
344
     *
345
     * @return CollectionInterface A new collection padded to specified length
346
     */
347
    public function pad($size, $with = null)
348
    {
349
        return collect(array_pad($this->getData(), $size, $with));
350
    }
351
352
    /**
353
     * Apply a callback to each item in collection.
354
     *
355
     * Applies a callback to each item in collection and returns a new collection
356
     * containing each iteration's return value.
357
     *
358
     * @param callable $callback The callback to apply
359
     *
360
     * @return CollectionInterface A new collection with callback return values
361
     */
362
    public function map(callable $callback)
363
    {
364
        $iter = 0;
365
        $transform = [];
366
        foreach ($this as $key => $val) {
367
            $transform[$key] = $callback($val, $key, $iter++);
368
        }
369
        return collect($transform);
370
    }
371
372
    /**
373
     * Iterate over each item in the collection, calling $callback on it. Return false to stop iterating.
374
     *
375
     * @param callable    $callback A callback to use
376
     *
377
     * @return $this
378
     */
379
    public function each(callable $callback)
380
    {
381
        foreach ($this as $key => $val) {
382
            if (!$callback($val, $key)) {
383
                break;
384
            }
385
        }
386
387
        return $this;
388
    }
389
390
    /**
391
     * Filter the collection.
392
     *
393
     * Using a callback function, this method will filter out unwanted values, returning
394
     * a new collection containing only the values that weren't filtered.
395
     *
396
     * @param callable $callback The callback function used to filter
397
     *
398
     * @return CollectionInterface A new collection with only values that weren't filtered
399
     */
400
    public function filter(callable $callback)
401
    {
402
        $iter = 0;
403
        $filtered = [];
404
        foreach ($this as $key => $val) {
405
            if ($callback($val, $key, $iter++)) {
406
                $filtered[$key] = $val;
407
            }
408
        }
409
        return collect($filtered);
410
    }
411
412
    /**
413
     * Filter the collection.
414
     *
415
     * Using a callback function, this method will filter out unwanted values, returning
416
     * a new collection containing only the values that weren't filtered.
417
     *
418
     * @param callable $callback The callback function used to filter
419
     *
420
     * @return CollectionInterface A new collection with only values that weren't filtered
421
     */
422
    public function exclude(callable $callback)
423
    {
424
        $iter = 0;
425
        $filtered = [];
426
        foreach ($this as $key => $val) {
427
            if (!$callback($val, $key, $iter++)) {
428
                $filtered[$key] = $val;
429
            }
430
        }
431
        return collect($filtered);
432
    }
433
434
    /**
435
     * Return the first item that meets given criteria.
436
     *
437
     * Using a callback function, this method will return the first item in the collection
438
     * that causes the callback function to return true.
439
     *
440
     * @param callable|null $callback The callback function
441
     * @param mixed|null    $default  The default return value
442
     *
443
     * @return mixed
444
     */
445
    public function first(callable $callback = null, $default = null)
446
    {
447
        if (is_null($callback)) {
448
            return $this->getOffset(0);
449
        }
450
451
        foreach ($this as $index => $value) {
452
            if ($callback($value, $index)) {
453
                return $value;
454
            }
455
        }
456
457
        return $default;
458
    }
459
460
    /**
461
     * Return the last item that meets given criteria.
462
     *
463
     * Using a callback function, this method will return the last item in the collection
464
     * that causes the callback function to return true.
465
     *
466
     * @param callable|null $callback The callback function
467
     * @param mixed|null    $default  The default return value
468
     *
469
     * @return mixed
470
     */
471
    public function last(callable $callback = null, $default = null)
472
    {
473
        $reverse = $this->reverse();
474
        if (is_null($callback)) {
475
            return $reverse->getOffset(0);
476
        }
477
        return $reverse->first($callback);
478
    }
479
480
    /**
481
     * Returns collection in reverse order.
482
     *
483
     * @return CollectionInterface This collection in reverse order.
484
     */
485
    public function reverse()
486
    {
487
        return collect(array_reverse($this->getData(), true));
488
    }
489
490
    /**
491
     * Get unique items.
492
     *
493
     * Returns a collection of all the unique items in this collection.
494
     *
495
     * @return CollectionInterface This collection with duplicate items removed
496
     */
497
    public function unique()
498
    {
499
        return collect(array_unique($this->getData()));
500
    }
501
502
    /**
503
     * Collection factory method.
504
     *
505
     * This method will analyze input data and determine the most appropriate Collection
506
     * class to use. It will then instantiate said Collection class with the given
507
     * data and return it.
508
     *
509
     * @param mixed $data The data to wrap
510
     *
511
     * @return CollectionInterface A collection containing $data
512
     */
513
    public static function factory($data = null)
514
    {
515
        return new Collection($data);
516
    }
517
518
    /**
519
     * Determine if structure contains all numeric values.
520
     *
521
     * @return bool
522
     */
523
    public function isNumeric()
524
    {
525
        $data = $this->getData();
526
        if (!is_traversable($data) || empty($data)) {
527
            return false;
528
        }
529
        foreach ($data as $val) {
530
            if (!is_numeric($val)) {
531
                return false;
532
            }
533
        }
534
        return true;
535
    }
536
537
    /**
538
     * @inheritdoc
539
     */
540
    public function hasOffset($offset)
541
    {
542
        try {
543
            $this->getOffsetKey($offset);
544
            return true;
545
        } catch (OutOfBoundsException $e) {
546
            return false;
547
        }
548
    }
549
550
    /**
551
     * @inheritdoc
552
     */
553
    public function getOffsetKey($offset)
554
    {
555
        if (!is_null($key = $this->fold(function($carry, $val, $key, $iter) use ($offset) {
556
            return ($iter === $offset) ? $key : $carry;
557
        }))) {
558
            return $key;
559
        }
560
        throw new OutOfBoundsException("Offset does not exist: $offset");
561
    }
562
563
    /**
564
     * @inheritdoc
565
     */
566
    public function getOffset($offset)
567
    {
568
        return $this->retrieve($this->getOffsetKey($offset));
569
    }
570
571
    /**
572
     * @param int $offset The numerical offset
573
     *
574
     * @throws OutOfBoundsException if no pair at position
575
     *
576
     * @return array
577
     */
578
    public function getOffsetPair($offset)
579
    {
580
        $pairs = $this->pairs();
581
582
        return $pairs[$this->getOffsetKey($offset)];
583
    }
584
585
    /**
586
     * Get each key/value as an array pair.
587
     *
588
     * Returns a collection of arrays where each item in the collection is [key,value]
589
     *
590
     * @return CollectionInterface
591
     */
592
    public function pairs()
593
    {
594
        return collect(array_map(
595
            function ($key, $val) {
596
                return [$key, $val];
597
            },
598
            array_keys($this->getData()),
599
            array_values($this->getData())
600
        ));
601
    }
602
603
    /**
604
     * Get duplicate values.
605
     *
606
     * Returns a collection of arrays where the key is the duplicate value
607
     * and the value is an array of keys from the original collection.
608
     *
609
     * @return CollectionInterface A new collection with duplicate values.
610
     */
611
    public function duplicates()
612
    {
613
        $dups = [];
614
        $this->walk(function ($val, $key) use (&$dups) {
0 ignored issues
show
Bug introduced by
The method walk() does not seem to exist on object<Noz\Collection\Collection>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
615
            $dups[$val][] = $key;
616
        });
617
618
        return collect($dups)->filter(function ($val) {
619
            return count($val) > 1;
620
        });
621
    }
622
623
    // END Iterator methods
624
625
    /**
626
     * Counts how many times each value occurs in a collection.
627
     *
628
     * Returns a new collection with values as keys and how many times that
629
     * value appears in the collection. Works best with scalar values but will
630
     * attempt to work on collections of objects as well.
631
     *
632
     * @return CollectionInterface
633
     *
634
     * @todo Right now, collections of arrays or objects are supported via the
635
     * __toString() or spl_object_hash()
636
     * @todo NumericCollection::counts() does the same thing...
637
     */
638
    public function frequency()
639
    {
640
        $frequency = [];
641
        foreach ($this as $key => $val) {
642
            if (!is_scalar($val)) {
643
                if (!is_object($val)) {
644
                    $val = new ArrayIterator($val);
645
                }
646
647
                if (method_exists($val, '__toString')) {
648
                    $val = (string) $val;
649
                } else {
650
                    $val = spl_object_hash($val);
651
                }
652
            }
653
            if (!isset($frequency[$val])) {
654
                $frequency[$val] = 0;
655
            }
656
            $frequency[$val]++;
657
        }
658
659
        return collect($frequency);
660
    }
661
662
    /**
663
     * @inheritDoc
664
     */
665
    public function add($index, $value)
666
    {
667
        if (!$this->has($index)) {
668
            return $this->set($index, $value);
669
        }
670
        return collect($this);
671
    }
672
673
    /**
674
     * @inheritdoc
675
     * @todo Maybe read would be a better name for this?
676
     */
677
    public function get($index, $default = null)
678
    {
679
        try {
680
            return $this->retrieve($index);
681
        } catch (OutOfBoundsException $e) {
682
            return $default;
683
        }
684
    }
685
686
    /**
687
     * @inheritdoc
688
     * @todo Maybe read would be a better name for this?
689
     */
690
    public function retrieve($index)
691
    {
692
        if (!$this->has($index)) {
693
            throw new OutOfBoundsException(__CLASS__ . ' could not retrieve value at index ' . $index);
694
        }
695
        return $this->getData()[$index];
696
    }
697
698
    /**
699
     * @inheritDoc
700
     */
701
    public function prepend($item)
702
    {
703
        $data = $this->getData();
704
        array_unshift($data, $item);
705
706
        return collect($data);
707
    }
708
709
    /**
710
     * @inheritDoc
711
     */
712
    public function append($item)
713
    {
714
        $data = $this->getData();
715
        array_push($data, $item);
716
717
        return collect($data);
718
    }
719
720
    /**
721
     * @inheritDoc
722
     */
723
    public function chunk($size)
724
    {
725
        return collect($this->fold(function($chunks, $val, $key, $iter) use ($size) {
726
            if (is_null($chunks)) {
727
                $chunks = [];
728
            }
729
            if ($iter % $size == 0) {
730
                // start new chunk
731
                array_push($chunks, []);
732
            }
733
            $chunk = array_pop($chunks);
734
            array_push($chunk, $val);
735
            array_push($chunks, $chunk);
736
737
            return $chunks;
738
        }));
739
    }
740
741
    public function combine($values)
742
    {
743
        if (!is_traversable($values)) {
744
            throw new InvalidArgumentException(sprintf(
745
                'Expecting traversable data for %s but got %s.',
746
                __METHOD__,
747
                typeof($values)
748
            ));
749
        }
750
        return collect(
751
            array_combine(
752
                $this->keys()->toArray(),
753
                collect($values)->values()->toArray()
0 ignored issues
show
Bug introduced by
It seems like $values defined by parameter $values on line 741 can also be of type object<Noz\Contracts\CollectionInterface>; however, Noz\collect() does only seem to accept array|object<Iterator>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
754
            )
755
        );
756
    }
757
758
    /**
759
     * @inheritDoc
760
     */
761
    public function diff($data)
762
    {
763
        return collect(
764
            array_diff(
765
                $this->toArray(),
766
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 761 can also be of type object<Noz\Contracts\CollectionInterface>; however, Noz\collect() does only seem to accept array|object<Iterator>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
767
            )
768
        );
769
    }
770
771
    /**
772
     * @inheritDoc
773
     */
774
    public function diffKeys($data)
775
    {
776
        return collect(
777
            array_diff_key(
778
                $this->getData(),
779
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 774 can also be of type object<Noz\Contracts\CollectionInterface>; however, Noz\collect() does only seem to accept array|object<Iterator>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
780
            )
781
        );
782
    }
783
784
    /**
785
     * @inheritDoc
786
     */
787
    public function nth($nth, $offset = null)
788
    {
789
        return $this->slice($offset)->filter(function($val, $key, $iter) use ($nth) {
790
            return $iter % $nth == 0;
791
        });
792
    }
793
794
    /**
795
     * @inheritDoc
796
     */
797
    public function except($indexes)
798
    {
799
        return $this->diffKeys(collect($indexes)->flip());
0 ignored issues
show
Bug introduced by
It seems like $indexes defined by parameter $indexes on line 797 can also be of type object<Noz\Contracts\CollectionInterface>; however, Noz\collect() does only seem to accept array|object<Iterator>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
800
    }
801
802
    /**
803
     * @inheritDoc
804
     */
805
    public function flip()
806
    {
807
        return collect(array_flip($this->getData()));
808
    }
809
810
    /**
811
     * @inheritDoc
812
     */
813
    public function intersect($data)
814
    {
815
        return collect(
816
            array_intersect(
817
                $this->toArray(),
818
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 813 can also be of type object<Noz\Contracts\CollectionInterface>; however, Noz\collect() does only seem to accept array|object<Iterator>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
819
            )
820
        );
821
    }
822
823
    /**
824
     * @inheritDoc
825
     */
826
    public function intersectKeys($data)
827
    {
828
        return collect(
829
            array_intersect_key(
830
                $this->toArray(),
831
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 826 can also be of type object<Noz\Contracts\CollectionInterface>; however, Noz\collect() does only seem to accept array|object<Iterator>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
832
            )
833
        );
834
    }
835
836
    /**
837
     * @inheritDoc
838
     */
839
    public function isEmpty(callable $callback = null)
840
    {
841
        if (!is_null($callback)) {
842
            return $this->all($callback);
843
        }
844
        return empty($this->getData());
845
    }
846
847
    /**
848
     * @inheritDoc
849
     */
850
    public function only($indices)
851
    {
852
        return $this->intersectKeys(collect($indices)->flip()->toArray());
0 ignored issues
show
Bug introduced by
It seems like $indices defined by parameter $indices on line 850 can also be of type object<Noz\Contracts\CollectionInterface>; however, Noz\collect() does only seem to accept array|object<Iterator>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
853
    }
854
855
    /**
856
     * @inheritDoc
857
     */
858
    public function pipe(callable $callback)
859
    {
860
        return $callback($this);
861
    }
862
863
    /**
864
     * @inheritDoc
865
     */
866
    public function random($num)
867
    {
868
        return $this->shuffle()->slice(0, $num);
869
    }
870
871
    /**
872
     * @inheritDoc
873
     */
874
    public function indicesOf($value)
875
    {
876
        return $this->filter(function($val) use ($value) {
877
            return $val == $value;
878
        })->map(function($val, $key) {
879
            return $key;
880
        });
881
    }
882
883
    /**
884
     * @inheritDoc
885
     */
886
    public function shuffle()
887
    {
888
        return collect(shuffle($this->getData()));
0 ignored issues
show
Bug introduced by
$this->getData() cannot be passed to shuffle() as the parameter $array expects a reference.
Loading history...
Documentation introduced by
shuffle($this->getData()) is of type boolean, but the function expects a array|object<Iterator>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
889
    }
890
891
    /**
892
     * @inheritDoc
893
     */
894
    public function slice($offset, $length = null)
895
    {
896
        return collect(array_slice($this->getData(), $offset, $length, true));
897
    }
898
899
    /**
900
     * @inheritDoc
901
     */
902
    public function split($num)
903
    {
904
        $count = $this->count();
905
        $size = (int)($count / $num);
906
        $mod = $count % $num;
907
        return collect($this->fold(function($chunks, $val, $key, $iter) use ($num, $size, $mod) {
0 ignored issues
show
Unused Code introduced by
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...
Unused Code introduced by
The parameter $iter 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...
908
            $chunk_count = count($chunks);
909
            if ($chunk_count <= $mod) {
910
                $size++;
911
            }
912
            if (is_null($chunks)) {
913
                // create initial chunks array...
914
                $chunks = [[]];
915
            }
916
917
            // grab the most recent chunk
918
            $chunk = array_pop($chunks);
919
920
            if (count($chunk) < $size) {
921
                // if chunk is less than the expected size, add value to it
922
                array_push($chunk, $val);
923
                array_push($chunks, $chunk);
924
            } else {
925
                // otherwise, add the chunk back and add the value to a new chunk
926
                array_push($chunks, $chunk);
927
                if ($chunk_count <= $num) {
928
                    $chunk = [$val];
929
                }
930
                array_push($chunks, $chunk);
931
            }
932
933
            return $chunks;
934
        }));
935
    }
936
937
    /**
938
     * @inheritDoc
939
     */
940
    public function union($data)
941
    {
942
        return collect(
943
            array_merge(
944
                $this->getData(),
945
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 940 can also be of type object<Noz\Contracts\CollectionInterface>; however, Noz\collect() does only seem to accept array|object<Iterator>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
946
            )
947
        );
948
    }
949
950
    /**
951
     * @inheritDoc
952
     */
953
    public function zip(...$data)
954
    {
955
        /** @var CollectionInterface $args The function arguments TODO: Change this to SequenceInterface when you have that interface. */
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 137 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
956
        $args = collect(func_get_args());
957
        $args->map(function($val) {
958
            if (is_arrayable($val)) {
959
                return to_array($val);
960
            } else {
961
                // @todo throw exception?
962
                return [];
963
            }
964
        });
965
        $args = $args->prepend($this->getData())
966
                     ->prepend(null);
967
968
        return collect(
969
//            array_map(
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
970
//                null,
971
//                $this->getData(),
972
//                ...$data
973
//            )
974
            call_user_func_array(
975
                'array_map',
976
                $args->toArray()
977
            )
978
        );
979
    }
980
981
    /**
982
     * @inheritDoc
983
     */
984
    public function fold(callable $callback, $initial = null)
985
    {
986
        $iter = 0;
987
        $carry = $initial;
988
        foreach ($this as $key => $val) {
989
            $carry = $callback($carry, $val, $key, $iter++);
990
        }
991
        return $carry;
992
    }
993
994
    /**
995
     * @inheritDoc
996
     */
997
    public function foldl(callable $callback, $initial = null)
998
    {
999
        return $this->reverse()->fold($callback, $initial);
1000
    }
1001
1002
    /**
1003
     * @inheritDoc
1004
     */
1005
    public function all(callable $callback = null)
1006
    {
1007
        if (is_null($callback)) {
1008
            $callback = function($val) {
1009
                return (bool) $val;
1010
            };
1011
        }
1012
        return $this->filter($callback)->isEmpty();
1013
    }
1014
1015
    /**
1016
     * @inheritDoc
1017
     */
1018
    public function none(callable $callback = null)
1019
    {
1020
        if (is_null($callback)) {
1021
            $callback = function($val) {
1022
                return (bool) $val;
1023
            };
1024
        }
1025
        return $this->filter($callback)->isEmpty();
1026
    }
1027
1028
    // BEGIN Numeric Collection Methods
1029
    // These methods only really work on numeric data.
1030
1031
    /**
1032
     * Increment an item.
1033
     *
1034
     * Increment the item specified by $key by one value. Intended for integers
1035
     * but also works (using this term loosely) for letters. Any other data type
1036
     * it may modify is unintended behavior at best.
1037
     *
1038
     * This method modifies its internal data array rather than returning a new
1039
     * collection.
1040
     *
1041
     * @param mixed $index    The key of the item you want to increment.
1042
     * @param int   $interval The interval that $key should be incremented by
1043
     *
1044
     * @return CollectionInterface
1045
     */
1046
    public function increment($index, $interval = 1)
1047
    {
1048
        $val = $this->retrieve($index);
1049
        $val += $interval;
1050
        return $this->set($index, $val);
1051
    }
1052
1053
    /**
1054
     * Decrement an item.
1055
     *
1056
     * Frcrement the item specified by $key by one value. Intended for integers.
1057
     * Does not work for letters and if it does anything to anything else, it's
1058
     * unintended at best.
1059
     *
1060
     * This method modifies its internal data array rather than returning a new
1061
     * collection.
1062
     *
1063
     * @param mixed $index      The key of the item you want to decrement.
1064
     * @param int   $interval The interval that $key should be decremented by
1065
     *
1066
     * @return CollectionInterface
1067
     */
1068
    public function decrement($index, $interval = 1)
1069
    {
1070
        $val = $this->retrieve($index);
1071
        $val -= $interval;
1072
        return $this->set($index, $val);
1073
    }
1074
1075
    /**
1076
     * Get the sum.
1077
     *
1078
     * @return int|float The sum of all values in collection
1079
     */
1080
    public function sum()
1081
    {
1082
        return array_sum($this->toArray());
1083
    }
1084
1085
    /**
1086
     * Get the average.
1087
     *
1088
     * @return float|int The average value from the collection
1089
     */
1090
    public function average()
1091
    {
1092
        return $this->sum() / $this->count();
1093
    }
1094
1095
    /**
1096
     * Get the mode.
1097
     *
1098
     * @return float|int The mode
1099
     */
1100
    public function mode()
1101
    {
1102
        $counts = $this->counts()->toArray();
1103
        arsort($counts);
1104
        $mode = key($counts);
1105
1106
        return (strpos($mode, '.')) ? floatval($mode) : intval($mode);
1107
    }
1108
1109
    /**
1110
     * Get the median value.
1111
     *
1112
     * @return float|int The median value
1113
     */
1114
    public function median()
1115
    {
1116
        $count = $this->count();
1117
        $data  = $this->toArray();
1118
        natcasesort($data);
1119
        $middle = $count / 2;
1120
        $values = array_values($data);
1121
        if ($count % 2 == 0) {
1122
            // even number, use middle
1123
            $low  = $values[$middle - 1];
1124
            $high = $values[$middle];
1125
1126
            return ($low + $high) / 2;
1127
        }
1128
        // odd number return median
1129
        return $values[$middle];
1130
    }
1131
1132
    /**
1133
     * Get the maximum value.
1134
     *
1135
     * @return mixed The maximum
1136
     */
1137
    public function max()
1138
    {
1139
        return max($this->getData());
1140
    }
1141
1142
    /**
1143
     * Get the minimum value.
1144
     *
1145
     * @return mixed The minimum
1146
     */
1147
    public function min()
1148
    {
1149
        return min($this->getData());
1150
    }
1151
1152
    /**
1153
     * Get the number of times each item occurs in the collection.
1154
1155
     * This method will return a NumericCollection where keys are the
1156
     * values and values are the number of times that value occurs in
1157
     * the original collection.
1158
1159
     * @return CollectionInterface
1160
     */
1161
    public function counts()
1162
    {
1163
        return collect(array_count_values($this->toArray()));
1164
    }
1165
}
1166