Completed
Pull Request — master (#76)
by Luke
02:26
created

Collection   D

Complexity

Total Complexity 127

Size/Duplication

Total Lines 1127
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
dl 0
loc 1127
rs 4.4102
c 0
b 0
f 0
wmc 127
lcom 1
cbo 4

74 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 3
B __invoke() 0 20 5
A setData() 0 9 2
A getData() 0 4 1
A count() 0 7 2
A current() 0 4 1
A key() 0 4 1
A next() 0 9 2
A rewind() 0 6 1
A valid() 0 4 1
A sort() 0 10 2
A sortkeys() 0 10 2
A has() 0 4 1
A set() 0 6 1
A delete() 0 4 1
A indexOf() 0 9 3
A keys() 0 4 1
A values() 0 4 1
A pad() 0 4 1
A map() 0 9 2
A each() 0 10 3
A filter() 0 11 3
A exclude() 0 11 3
A first() 0 14 4
A last() 0 8 2
A reverse() 0 4 1
A unique() 0 4 1
A factory() 0 4 1
B isNumeric() 0 13 5
A hasOffset() 0 9 2
A getOffsetKey() 0 9 3
A getOffset() 0 4 1
A getOffsetPair() 0 6 1
A pairs() 0 10 1
A duplicates() 0 11 1
B frequency() 0 23 6
A add() 0 7 2
A get() 0 8 2
A retrieve() 0 7 2
A prepend() 0 7 1
A append() 0 7 1
A chunk() 0 17 3
A combine() 0 16 2
A diff() 0 9 1
A diffKeys() 0 9 1
A nth() 0 6 1
A except() 0 4 1
A flip() 0 4 1
A intersect() 0 9 1
A intersectKeys() 0 9 1
A isEmpty() 0 7 2
A only() 0 4 1
A pipe() 0 4 1
A random() 0 4 1
A indicesOf() 0 8 1
A shuffle() 0 4 1
A slice() 0 4 1
B split() 0 34 5
A union() 0 9 1
B zip() 0 27 2
A fold() 0 9 2
A foldl() 0 4 1
A all() 0 9 2
A none() 0 9 2
A increment() 0 6 1
A decrement() 0 6 1
A sum() 0 4 1
A average() 0 4 1
A mode() 0 8 2
A median() 0 17 2
A max() 0 4 1
A min() 0 4 1
A counts() 0 4 1
A unserialize() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Collection often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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

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

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\Immutable;
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\Immutable
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,
58
        IsContainer,
59
        IsSerializable;
60
61
    /**
62
     * @var array The collection of data this object represents
63
     */
64
    private $data = [];
65
66
    /**
67
     * @var bool True unless we have advanced past the end of the data array
68
     */
69
    protected $isValid = true;
70
71
    /**
72
     * AbstractCollection constructor.
73
     *
74
     * @param mixed $data The data to wrap
75
     */
76
    public function __construct($data = null)
77
    {
78
        if (is_null($data)) {
79
            $data = [];
80
        }
81
        if (!is_traversable($data)) {
82
            throw new InvalidArgumentException(sprintf(
83
                'Invalid input for %s. Expecting traversable data, got "%s".',
84
                __METHOD__,
85
                typeof($data)
86
            ));
87
        }
88
        $this->setData($data);
89
    }
90
91
    public function __invoke()
92
    {
93
        $args = collect(func_get_args());
94
        if ($args->hasOffset(0)) {
95
            if ($args->hasOffset(1)) {
96
                // two args only...
97
                return $this->set($args->getOffset(0), $args->getOffset(1));
98
            }
99
            // one arg only...
100
            $arg1 = $args->getOffset(0);
101
            if (is_scalar($arg1)) {
102
                return $this->get($arg1);
103
            }
104
            if (is_traversable($arg1)) {
105
                return $this->union($arg1);
0 ignored issues
show
Bug introduced by
It seems like $arg1 defined by $args->getOffset(0) on line 100 can also be of type null or object; however, Noz\Immutable\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...
106
            }
107
            // @todo Should probably throw ane invalid arg exception here...
108
        }
109
        return $this->toArray();
110
    }
111
112
    /**
113
     * Set underlying data array.
114
     *
115
     * Sets the collection data. This method should NEVER be called anywhere other than in __construct().
116
     *
117
     * @param array|Traversable $data The data to wrap
118
     */
119
    private function setData($data)
120
    {
121
        $arr = [];
122
        foreach ($data as $index => $value) {
123
            $arr[$index] = $value;
124
        }
125
        $this->data = $arr;
126
        $this->rewind();
127
    }
128
129
    /**
130
     * Get copy of underlying data array.
131
     *
132
     * Returns a copy of this collection's underlying data array. It returns a copy because collections are supposed to
133
     * be immutable. Nothing outside of the constructor should ever have direct access to the actual underlying array.
134
     *
135
     * @return array
136
     */
137
    protected function getData()
138
    {
139
        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...
140
    }
141
142
    /**
143
     * @inheritDoc
144
     */
145
    public function count(callable $callback = null)
146
    {
147
        if (!is_null($callback)) {
148
            return $this->filter($callback)->count();
149
        }
150
        return count($this->getData());
151
    }
152
153
    /**
154
     * Return the current element.
155
     *
156
     * Returns the current element in the collection. The internal array pointer
157
     * of the data array wrapped by the collection should not be advanced by this
158
     * method. No side effects. Return current element only.
159
     *
160
     * @return mixed
161
     */
162
    public function current()
163
    {
164
        return current($this->data);
165
    }
166
167
    /**
168
     * Return the current key.
169
     *
170
     * Returns the current key in the collection. No side effects.
171
     *
172
     * @return mixed
173
     */
174
    public function key()
175
    {
176
        return key($this->data);
177
    }
178
179
    /**
180
     * Advance the internal pointer forward.
181
     *
182
     * Although this method will return the current value after advancing the
183
     * pointer, you should not expect it to. The interface does not require it
184
     * to return any value at all.
185
     *
186
     * @return mixed
187
     */
188
    public function next()
189
    {
190
        $next = next($this->data);
191
        $key  = key($this->data);
192
        if (isset($key)) {
193
            return $next;
194
        }
195
        $this->isValid = false;
196
    }
197
198
    /**
199
     * Rewind the internal pointer.
200
     *
201
     * Return the internal pointer to the first element in the collection. Again,
202
     * this method is not required to return anything by its interface, so you
203
     * should not count on a return value.
204
     *
205
     * @return mixed
206
     */
207
    public function rewind()
208
    {
209
        $this->isValid = !empty($this->data);
210
211
        return reset($this->data);
212
    }
213
214
    /**
215
     * Is internal pointer in a valid position?
216
     *
217
     * If the internal pointer is advanced beyond the end of the collection, this method will return false.
218
     *
219
     * @return bool True if internal pointer isn't past the end
220
     */
221
    public function valid()
222
    {
223
        return $this->isValid;
224
    }
225
226
    /**
227
     * @inheritDoc
228
     */
229
    public function sort($alg = null)
230
    {
231
        if (is_null($alg)) {
232
            $alg = 'strnatcasecmp';
233
        }
234
        $data = $this->getData();
235
        uasort($data, $alg);
236
237
        return collect($data);
238
    }
239
240
    /**
241
     * @inheritDoc
242
     */
243
    public function sortkeys($alg = null)
244
    {
245
        if (is_null($alg)) {
246
            $alg = 'strnatcasecmp';
247
        }
248
        $data = $this->getData();
249
        uksort($data, $alg);
250
251
        return collect($data);
252
    }
253
254
    /**
255
     * Does this collection have a value at given index?
256
     *
257
     * @param mixed $index The index to check
258
     *
259
     * @return bool
260
     */
261
    public function has($index)
262
    {
263
        return array_key_exists($index, $this->getData());
264
    }
265
266
    /**
267
     * Set value at given index.
268
     *
269
     * This method simulates setting a value in this collection, but because collections are immutable, it actually
270
     * returns a copy of this collection with the value in the new collection set to specified value.
271
     *
272
     * @param mixed $index The index to set a value at
273
     * @param mixed $val   The value to set $index to
274
     *
275
     * @return CollectionInterface
276
     */
277
    public function set($index, $val)
278
    {
279
        $copy = $this->getData();
280
        $copy[$index] = $val;
281
        return collect($copy);
282
    }
283
284
    /**
285
     * Unset (delete) value at the given index.
286
     *
287
     * Get copy of collection with given index removed.
288
     *
289
     * @param mixed $index The index to unset
290
     *
291
     * @return CollectionInterface
292
     */
293
    public function delete($index)
294
    {
295
        return $this->except([$index]);
296
    }
297
298
    /**
299
     * Get index of a value.
300
     *
301
     * Given a value, this method will return the index of the first occurrence of that value.
302
     *
303
     * @param mixed $value Value to get the index of
304
     *
305
     * @return int|null|string
306
     */
307
    public function indexOf($value)
308
    {
309
        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...
310
            if (is_null($carry) && $val == $value) {
311
                return $key;
312
            }
313
            return $carry;
314
        });
315
    }
316
317
    /**
318
     * Get this collection's keys as a collection.
319
     *
320
     * @return CollectionInterface Containing this collection's keys
321
     */
322
    public function keys()
323
    {
324
        return collect(array_keys($this->getData()));
325
    }
326
327
    /**
328
     * Get this collection's values as a collection.
329
     *
330
     * This method returns this collection's values but completely re-indexed (numerically).
331
     *
332
     * @return CollectionInterface Containing this collection's values
333
     */
334
    public function values()
335
    {
336
        return collect(array_values($this->getData()));
337
    }
338
339
    /**
340
     * Pad collection to a certain size.
341
     *
342
     * Returns a new collection, padded to the given size, with the given value.
343
     *
344
     * @param int   $size The number of items that should be in the collection
345
     * @param mixed $with The value to pad the collection with
346
     *
347
     * @return CollectionInterface A new collection padded to specified length
348
     */
349
    public function pad($size, $with = null)
350
    {
351
        return collect(array_pad($this->getData(), $size, $with));
352
    }
353
354
    /**
355
     * Apply a callback to each item in collection.
356
     *
357
     * Applies a callback to each item in collection and returns a new collection
358
     * containing each iteration's return value.
359
     *
360
     * @param callable $callback The callback to apply
361
     *
362
     * @return CollectionInterface A new collection with callback return values
363
     */
364
    public function map(callable $callback)
365
    {
366
        $iter = 0;
367
        $transform = [];
368
        foreach ($this as $key => $val) {
369
            $transform[$key] = $callback($val, $key, $iter++);
370
        }
371
        return collect($transform);
372
    }
373
374
    /**
375
     * Iterate over each item in the collection, calling $callback on it. Return false to stop iterating.
376
     *
377
     * @param callable    $callback A callback to use
378
     *
379
     * @return $this
380
     */
381
    public function each(callable $callback)
382
    {
383
        foreach ($this as $key => $val) {
384
            if (!$callback($val, $key)) {
385
                break;
386
            }
387
        }
388
389
        return $this;
390
    }
391
392
    /**
393
     * Filter the collection.
394
     *
395
     * Using a callback function, this method will filter out unwanted values, returning
396
     * a new collection containing only the values that weren't filtered.
397
     *
398
     * @param callable $callback The callback function used to filter
399
     *
400
     * @return CollectionInterface A new collection with only values that weren't filtered
401
     */
402
    public function filter(callable $callback)
403
    {
404
        $iter = 0;
405
        $filtered = [];
406
        foreach ($this as $key => $val) {
407
            if ($callback($val, $key, $iter++)) {
408
                $filtered[$key] = $val;
409
            }
410
        }
411
        return collect($filtered);
412
    }
413
414
    /**
415
     * Filter the collection.
416
     *
417
     * Using a callback function, this method will filter out unwanted values, returning
418
     * a new collection containing only the values that weren't filtered.
419
     *
420
     * @param callable $callback The callback function used to filter
421
     *
422
     * @return CollectionInterface A new collection with only values that weren't filtered
423
     */
424
    public function exclude(callable $callback)
425
    {
426
        $iter = 0;
427
        $filtered = [];
428
        foreach ($this as $key => $val) {
429
            if (!$callback($val, $key, $iter++)) {
430
                $filtered[$key] = $val;
431
            }
432
        }
433
        return collect($filtered);
434
    }
435
436
    /**
437
     * Return the first item that meets given criteria.
438
     *
439
     * Using a callback function, this method will return the first item in the collection
440
     * that causes the callback function to return true.
441
     *
442
     * @param callable|null $callback The callback function
443
     * @param mixed|null    $default  The default return value
444
     *
445
     * @return mixed
446
     */
447
    public function first(callable $callback = null, $default = null)
448
    {
449
        if (is_null($callback)) {
450
            return $this->getOffset(0);
451
        }
452
453
        foreach ($this as $index => $value) {
454
            if ($callback($value, $index)) {
455
                return $value;
456
            }
457
        }
458
459
        return $default;
460
    }
461
462
    /**
463
     * Return the last item that meets given criteria.
464
     *
465
     * Using a callback function, this method will return the last item in the collection
466
     * that causes the callback function to return true.
467
     *
468
     * @param callable|null $callback The callback function
469
     * @param mixed|null    $default  The default return value
470
     *
471
     * @return mixed
472
     */
473
    public function last(callable $callback = null, $default = null)
474
    {
475
        $reverse = $this->reverse();
476
        if (is_null($callback)) {
477
            return $reverse->getOffset(0);
478
        }
479
        return $reverse->first($callback);
480
    }
481
482
    /**
483
     * Returns collection in reverse order.
484
     *
485
     * @return CollectionInterface This collection in reverse order.
486
     */
487
    public function reverse()
488
    {
489
        return collect(array_reverse($this->getData(), true));
490
    }
491
492
    /**
493
     * Get unique items.
494
     *
495
     * Returns a collection of all the unique items in this collection.
496
     *
497
     * @return CollectionInterface This collection with duplicate items removed
498
     */
499
    public function unique()
500
    {
501
        return collect(array_unique($this->getData()));
502
    }
503
504
    /**
505
     * Collection factory method.
506
     *
507
     * This method will analyze input data and determine the most appropriate Collection
508
     * class to use. It will then instantiate said Collection class with the given
509
     * data and return it.
510
     *
511
     * @param mixed $data The data to wrap
512
     *
513
     * @return CollectionInterface A collection containing $data
514
     */
515
    public static function factory($data = null)
516
    {
517
        return new Collection($data);
518
    }
519
520
    /**
521
     * Determine if structure contains all numeric values.
522
     *
523
     * @return bool
524
     */
525
    public function isNumeric()
526
    {
527
        $data = $this->getData();
528
        if (!is_traversable($data) || empty($data)) {
529
            return false;
530
        }
531
        foreach ($data as $val) {
532
            if (!is_numeric($val)) {
533
                return false;
534
            }
535
        }
536
        return true;
537
    }
538
539
    /**
540
     * @inheritdoc
541
     */
542
    public function hasOffset($offset)
543
    {
544
        try {
545
            $this->getOffsetKey($offset);
546
            return true;
547
        } catch (OutOfBoundsException $e) {
548
            return false;
549
        }
550
    }
551
552
    /**
553
     * @inheritdoc
554
     */
555
    public function getOffsetKey($offset)
556
    {
557
        if (!is_null($key = $this->fold(function($carry, $val, $key, $iter) use ($offset) {
558
            return ($iter === $offset) ? $key : $carry;
559
        }))) {
560
            return $key;
561
        }
562
        throw new OutOfBoundsException("Offset does not exist: $offset");
563
    }
564
565
    /**
566
     * @inheritdoc
567
     */
568
    public function getOffset($offset)
569
    {
570
        return $this->retrieve($this->getOffsetKey($offset));
571
    }
572
573
    /**
574
     * @param int $offset The numerical offset
575
     *
576
     * @throws OutOfBoundsException if no pair at position
577
     *
578
     * @return array
579
     */
580
    public function getOffsetPair($offset)
581
    {
582
        $pairs = $this->pairs();
583
584
        return $pairs[$this->getOffsetKey($offset)];
585
    }
586
587
    /**
588
     * Get each key/value as an array pair.
589
     *
590
     * Returns a collection of arrays where each item in the collection is [key,value]
591
     *
592
     * @return CollectionInterface
593
     */
594
    public function pairs()
595
    {
596
        return collect(array_map(
597
            function ($key, $val) {
598
                return [$key, $val];
599
            },
600
            array_keys($this->getData()),
601
            array_values($this->getData())
602
        ));
603
    }
604
605
    /**
606
     * Get duplicate values.
607
     *
608
     * Returns a collection of arrays where the key is the duplicate value
609
     * and the value is an array of keys from the original collection.
610
     *
611
     * @return CollectionInterface A new collection with duplicate values.
612
     */
613
    public function duplicates()
614
    {
615
        $dups = [];
616
        $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\Immutable\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...
617
            $dups[$val][] = $key;
618
        });
619
620
        return collect($dups)->filter(function ($val) {
621
            return count($val) > 1;
622
        });
623
    }
624
625
    // END Iterator methods
626
627
    /**
628
     * Counts how many times each value occurs in a collection.
629
     *
630
     * Returns a new collection with values as keys and how many times that
631
     * value appears in the collection. Works best with scalar values but will
632
     * attempt to work on collections of objects as well.
633
     *
634
     * @return CollectionInterface
635
     *
636
     * @todo Right now, collections of arrays or objects are supported via the
637
     * __toString() or spl_object_hash()
638
     * @todo NumericCollection::counts() does the same thing...
639
     */
640
    public function frequency()
641
    {
642
        $frequency = [];
643
        foreach ($this as $key => $val) {
644
            if (!is_scalar($val)) {
645
                if (!is_object($val)) {
646
                    $val = new ArrayIterator($val);
647
                }
648
649
                if (method_exists($val, '__toString')) {
650
                    $val = (string) $val;
651
                } else {
652
                    $val = spl_object_hash($val);
653
                }
654
            }
655
            if (!isset($frequency[$val])) {
656
                $frequency[$val] = 0;
657
            }
658
            $frequency[$val]++;
659
        }
660
661
        return collect($frequency);
662
    }
663
664
    /**
665
     * @inheritDoc
666
     */
667
    public function add($index, $value)
668
    {
669
        if (!$this->has($index)) {
670
            return $this->set($index, $value);
671
        }
672
        return collect($this);
673
    }
674
675
    /**
676
     * @inheritdoc
677
     * @todo Maybe read would be a better name for this?
678
     */
679
    public function get($index, $default = null)
680
    {
681
        try {
682
            return $this->retrieve($index);
683
        } catch (OutOfBoundsException $e) {
684
            return $default;
685
        }
686
    }
687
688
    /**
689
     * @inheritdoc
690
     * @todo Maybe read would be a better name for this?
691
     */
692
    public function retrieve($index)
693
    {
694
        if (!$this->has($index)) {
695
            throw new OutOfBoundsException(__CLASS__ . ' could not retrieve value at index ' . $index);
696
        }
697
        return $this->getData()[$index];
698
    }
699
700
    /**
701
     * @inheritDoc
702
     */
703
    public function prepend($item)
704
    {
705
        $data = $this->getData();
706
        array_unshift($data, $item);
707
708
        return collect($data);
709
    }
710
711
    /**
712
     * @inheritDoc
713
     */
714
    public function append($item)
715
    {
716
        $data = $this->getData();
717
        array_push($data, $item);
718
719
        return collect($data);
720
    }
721
722
    /**
723
     * @inheritDoc
724
     */
725
    public function chunk($size)
726
    {
727
        return collect($this->fold(function($chunks, $val, $key, $iter) use ($size) {
728
            if (is_null($chunks)) {
729
                $chunks = [];
730
            }
731
            if ($iter % $size == 0) {
732
                // start new chunk
733
                array_push($chunks, []);
734
            }
735
            $chunk = array_pop($chunks);
736
            array_push($chunk, $val);
737
            array_push($chunks, $chunk);
738
739
            return $chunks;
740
        }));
741
    }
742
743
    public function combine($values)
744
    {
745
        if (!is_traversable($values)) {
746
            throw new InvalidArgumentException(sprintf(
747
                'Expecting traversable data for %s but got %s.',
748
                __METHOD__,
749
                typeof($values)
750
            ));
751
        }
752
        return collect(
753
            array_combine(
754
                $this->keys()->toArray(),
755
                collect($values)->values()->toArray()
0 ignored issues
show
Bug introduced by
It seems like $values defined by parameter $values on line 743 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...
756
            )
757
        );
758
    }
759
760
    /**
761
     * @inheritDoc
762
     */
763
    public function diff($data)
764
    {
765
        return collect(
766
            array_diff(
767
                $this->toArray(),
768
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 763 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...
769
            )
770
        );
771
    }
772
773
    /**
774
     * @inheritDoc
775
     */
776
    public function diffKeys($data)
777
    {
778
        return collect(
779
            array_diff_key(
780
                $this->getData(),
781
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 776 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...
782
            )
783
        );
784
    }
785
786
    /**
787
     * @inheritDoc
788
     */
789
    public function nth($nth, $offset = null)
790
    {
791
        return $this->slice($offset)->filter(function($val, $key, $iter) use ($nth) {
792
            return $iter % $nth == 0;
793
        });
794
    }
795
796
    /**
797
     * @inheritDoc
798
     */
799
    public function except($indexes)
800
    {
801
        return $this->diffKeys(collect($indexes)->flip());
0 ignored issues
show
Bug introduced by
It seems like $indexes defined by parameter $indexes on line 799 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...
802
    }
803
804
    /**
805
     * @inheritDoc
806
     */
807
    public function flip()
808
    {
809
        return collect(array_flip($this->getData()));
810
    }
811
812
    /**
813
     * @inheritDoc
814
     */
815
    public function intersect($data)
816
    {
817
        return collect(
818
            array_intersect(
819
                $this->toArray(),
820
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 815 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...
821
            )
822
        );
823
    }
824
825
    /**
826
     * @inheritDoc
827
     */
828
    public function intersectKeys($data)
829
    {
830
        return collect(
831
            array_intersect_key(
832
                $this->toArray(),
833
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 828 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...
834
            )
835
        );
836
    }
837
838
    /**
839
     * @inheritDoc
840
     */
841
    public function isEmpty(callable $callback = null)
842
    {
843
        if (!is_null($callback)) {
844
            return $this->all($callback);
845
        }
846
        return empty($this->getData());
847
    }
848
849
    /**
850
     * @inheritDoc
851
     */
852
    public function only($indices)
853
    {
854
        return $this->intersectKeys(collect($indices)->flip()->toArray());
0 ignored issues
show
Bug introduced by
It seems like $indices defined by parameter $indices on line 852 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...
855
    }
856
857
    /**
858
     * @inheritDoc
859
     */
860
    public function pipe(callable $callback)
861
    {
862
        return $callback($this);
863
    }
864
865
    /**
866
     * @inheritDoc
867
     */
868
    public function random($num)
869
    {
870
        return $this->shuffle()->slice(0, $num);
871
    }
872
873
    /**
874
     * @inheritDoc
875
     */
876
    public function indicesOf($value)
877
    {
878
        return $this->filter(function($val) use ($value) {
879
            return $val == $value;
880
        })->map(function($val, $key) {
881
            return $key;
882
        });
883
    }
884
885
    /**
886
     * @inheritDoc
887
     */
888
    public function shuffle()
889
    {
890
        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...
891
    }
892
893
    /**
894
     * @inheritDoc
895
     */
896
    public function slice($offset, $length = null)
897
    {
898
        return collect(array_slice($this->getData(), $offset, $length, true));
899
    }
900
901
    /**
902
     * @inheritDoc
903
     */
904
    public function split($num)
905
    {
906
        $count = $this->count();
907
        $size = (int)($count / $num);
908
        $mod = $count % $num;
909
        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...
910
            $chunk_count = count($chunks);
911
            if ($chunk_count <= $mod) {
912
                $size++;
913
            }
914
            if (is_null($chunks)) {
915
                // create initial chunks array...
916
                $chunks = [[]];
917
            }
918
919
            // grab the most recent chunk
920
            $chunk = array_pop($chunks);
921
922
            if (count($chunk) < $size) {
923
                // if chunk is less than the expected size, add value to it
924
                array_push($chunk, $val);
925
                array_push($chunks, $chunk);
926
            } else {
927
                // otherwise, add the chunk back and add the value to a new chunk
928
                array_push($chunks, $chunk);
929
                if ($chunk_count <= $num) {
930
                    $chunk = [$val];
931
                }
932
                array_push($chunks, $chunk);
933
            }
934
935
            return $chunks;
936
        }));
937
    }
938
939
    /**
940
     * @inheritDoc
941
     */
942
    public function union($data)
943
    {
944
        return collect(
945
            array_merge(
946
                $this->getData(),
947
                collect($data)->toArray()
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 942 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...
948
            )
949
        );
950
    }
951
952
    /**
953
     * @inheritDoc
954
     */
955
    public function zip(...$data)
956
    {
957
        /** @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...
958
        $args = collect(func_get_args());
959
        $args->map(function($val) {
960
            if (is_arrayable($val)) {
961
                return to_array($val);
962
            } else {
963
                // @todo throw exception?
964
                return [];
965
            }
966
        });
967
        $args = $args->prepend($this->getData())
968
                     ->prepend(null);
969
970
        return collect(
971
//            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...
972
//                null,
973
//                $this->getData(),
974
//                ...$data
975
//            )
976
            call_user_func_array(
977
                'array_map',
978
                $args->toArray()
979
            )
980
        );
981
    }
982
983
    /**
984
     * @inheritDoc
985
     */
986
    public function fold(callable $callback, $initial = null)
987
    {
988
        $iter = 0;
989
        $carry = $initial;
990
        foreach ($this as $key => $val) {
991
            $carry = $callback($carry, $val, $key, $iter++);
992
        }
993
        return $carry;
994
    }
995
996
    /**
997
     * @inheritDoc
998
     */
999
    public function foldl(callable $callback, $initial = null)
1000
    {
1001
        return $this->reverse()->fold($callback, $initial);
1002
    }
1003
1004
    /**
1005
     * @inheritDoc
1006
     */
1007
    public function all(callable $callback = null)
1008
    {
1009
        if (is_null($callback)) {
1010
            $callback = function($val) {
1011
                return (bool) $val;
1012
            };
1013
        }
1014
        return $this->filter($callback)->isEmpty();
1015
    }
1016
1017
    /**
1018
     * @inheritDoc
1019
     */
1020
    public function none(callable $callback = null)
1021
    {
1022
        if (is_null($callback)) {
1023
            $callback = function($val) {
1024
                return (bool) $val;
1025
            };
1026
        }
1027
        return $this->filter($callback)->isEmpty();
1028
    }
1029
1030
    // BEGIN Numeric Collection Methods
1031
    // These methods only really work on numeric data.
1032
1033
    /**
1034
     * Increment an item.
1035
     *
1036
     * Increment the item specified by $key by one value. Intended for integers
1037
     * but also works (using this term loosely) for letters. Any other data type
1038
     * it may modify is unintended behavior at best.
1039
     *
1040
     * This method modifies its internal data array rather than returning a new
1041
     * collection.
1042
     *
1043
     * @param mixed $index    The key of the item you want to increment.
1044
     * @param int   $interval The interval that $key should be incremented by
1045
     *
1046
     * @return CollectionInterface
1047
     */
1048
    public function increment($index, $interval = 1)
1049
    {
1050
        $val = $this->retrieve($index);
1051
        $val += $interval;
1052
        return $this->set($index, $val);
1053
    }
1054
1055
    /**
1056
     * Decrement an item.
1057
     *
1058
     * Frcrement the item specified by $key by one value. Intended for integers.
1059
     * Does not work for letters and if it does anything to anything else, it's
1060
     * unintended at best.
1061
     *
1062
     * This method modifies its internal data array rather than returning a new
1063
     * collection.
1064
     *
1065
     * @param mixed $index      The key of the item you want to decrement.
1066
     * @param int   $interval The interval that $key should be decremented by
1067
     *
1068
     * @return CollectionInterface
1069
     */
1070
    public function decrement($index, $interval = 1)
1071
    {
1072
        $val = $this->retrieve($index);
1073
        $val -= $interval;
1074
        return $this->set($index, $val);
1075
    }
1076
1077
    /**
1078
     * Get the sum.
1079
     *
1080
     * @return int|float The sum of all values in collection
1081
     */
1082
    public function sum()
1083
    {
1084
        return array_sum($this->toArray());
1085
    }
1086
1087
    /**
1088
     * Get the average.
1089
     *
1090
     * @return float|int The average value from the collection
1091
     */
1092
    public function average()
1093
    {
1094
        return $this->sum() / $this->count();
1095
    }
1096
1097
    /**
1098
     * Get the mode.
1099
     *
1100
     * @return float|int The mode
1101
     */
1102
    public function mode()
1103
    {
1104
        $counts = $this->counts()->toArray();
1105
        arsort($counts);
1106
        $mode = key($counts);
1107
1108
        return (strpos($mode, '.')) ? floatval($mode) : intval($mode);
1109
    }
1110
1111
    /**
1112
     * Get the median value.
1113
     *
1114
     * @return float|int The median value
1115
     */
1116
    public function median()
1117
    {
1118
        $count = $this->count();
1119
        $data  = $this->toArray();
1120
        natcasesort($data);
1121
        $middle = $count / 2;
1122
        $values = array_values($data);
1123
        if ($count % 2 == 0) {
1124
            // even number, use middle
1125
            $low  = $values[$middle - 1];
1126
            $high = $values[$middle];
1127
1128
            return ($low + $high) / 2;
1129
        }
1130
        // odd number return median
1131
        return $values[$middle];
1132
    }
1133
1134
    /**
1135
     * Get the maximum value.
1136
     *
1137
     * @return mixed The maximum
1138
     */
1139
    public function max()
1140
    {
1141
        return max($this->getData());
1142
    }
1143
1144
    /**
1145
     * Get the minimum value.
1146
     *
1147
     * @return mixed The minimum
1148
     */
1149
    public function min()
1150
    {
1151
        return min($this->getData());
1152
    }
1153
1154
    /**
1155
     * Get the number of times each item occurs in the collection.
1156
1157
     * This method will return a NumericCollection where keys are the
1158
     * values and values are the number of times that value occurs in
1159
     * the original collection.
1160
1161
     * @return CollectionInterface
1162
     */
1163
    public function counts()
1164
    {
1165
        return collect(array_count_values($this->toArray()));
1166
    }
1167
1168
    /**
1169
     * @param $serialized
1170
     */
1171
    public function unserialize($serialized)
1172
    {
1173
        $this->setData(unserialize($serialized));
1174
    }
1175
1176
}
1177