Completed
Pull Request — master (#49)
by Luke
05:51 queued 02:32
created

Collection   D

Complexity

Total Complexity 126

Size/Duplication

Total Lines 1116
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
dl 0
loc 1116
rs 4.4102
c 0
b 0
f 0
wmc 126
lcom 1
cbo 3

73 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

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