Completed
Push — master ( 79e18b...694a43 )
by Ivan
02:23
created

Collection::offsetGet()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3.1406

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 3
cts 4
cp 0.75
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 4
nc 2
nop 1
crap 3.1406
1
<?php
2
3
namespace vakata\collection;
4
5
class Collection implements \Iterator, \ArrayAccess, \Serializable, \Countable
6
{
7
    protected $array = null;
8
    protected $stack = [];
9
    protected $iterator = null;
10
    protected $key = null;
11
    protected $val = null;
12
13 4
    protected static function rangeGenerator($low, $high, $step = 1)
14
    {
15 4
        $k = -1;
16 4
        for ($i = $low; $i <= $high; $i += $step) {
17 4
            yield ++$k => $i;
18
        }
19 4
    }
20
    /**
21
     * Create a collection based on a range generator
22
     * @param  int|float  $low  start value
23
     * @param  int|float  $high end value
24
     * @param  int|float  $step increment
25
     * @return Collection
26
     */
27 4
    public static function range($low, $high, $step = 1) : Collection
28
    {
29 4
        return new static(static::rangeGenerator($low, $high, $step));
30
    }
31
    /**
32
     * A static alias of the __constructor
33
     * @param  mixed  $input  Anything iterable
34
     * @return Collection
35
     */
36 51
    public static function from($input) : Collection
37
    {
38 51
        return new static($input);
39
    }
40
    /**
41
     * Create an instance
42
     * @param  mixed  $input  Anything iterable
43
     */
44 55
    public function __construct($input = [])
45
    {
46 55
        if (is_object($input)) {
47 28
            if ($input instanceof \Iterator) {
48 5
                $this->array = $input;
49 5
                $this->iterator = $input;
50 23
            } else if ($input instanceof self) {
51
                $this->array = $input->toArray();
52
            } else {
53 23
                $input = get_object_vars($input);
54
            }
55
        }
56 55
        if (is_array($input)) {
57 51
            $this->array = new \ArrayObject($input);
58 51
            $this->iterator = $this->array->getIterator();
59
        }
60 55
    }
61 1
    public function __clone()
62
    {
63 1
        return new static($this->toArray());
64
    }
65 1
    public function __toString()
66
    {
67 1
        return implode(', ', $this->toArray());
68
    }
69 1
    public function serialize() {
70 1
        return serialize($this->toArray());
71
    }
72 1
    public function unserialize($array) {
73 1
        $this->array = new \ArrayObject(unserialize($array));
74 1
        $this->stack = [];
75 1
        $this->iterator = $this->array->getIterator();
76 1
    }
77
78
    /**
79
     * Applies all pending operations
80
     * @return $this
81
     */
82 45
    public function squash() : Collection
83
    {
84 45
        $this->array = new \ArrayObject(iterator_to_array($this));
85 45
        $this->stack = [];
86 45
        $this->iterator = $this->array->getIterator();
87 45
        return $this;
88
    }
89
    /**
90
     * Get an actual array from the collection
91
     * @return array
92
     */
93 44
    public function toArray() : array
94
    {
95 44
        $this->squash();
96 44
        return $this->array->getArrayCopy();
97
    }
98
    /**
99
     * Gets the first value in the collection or null if empty
100
     * @return mixed
101
     */
102 3
    public function value()
103
    {
104 3
        foreach ($this as $v) {
105 3
            return $v;
106
        }
107 1
        return null;
108
    }
109
110
    // iterator
111 51
    public function key()
112
    {
113 51
        return $this->key;
114
    }
115 54
    public function current()
116
    {
117 54
        return $this->val;
118
    }
119 54
    public function rewind()
120
    {
121 54
        return $this->iterator->rewind();
122
    }
123 53
    public function next()
124
    {
125 53
        return $this->iterator->next();
126
    }
127 54
    public function valid()
128
    {
129 54
        while ($this->iterator->valid()) {
130 54
            $this->val = $this->iterator->current();
131 54
            $this->key = $this->iterator->key();
132 54
            $con = false;
133 54
            foreach ($this->stack as $action) {
134 12
                if ($action[0] === 'filter') {
135 8
                    if (!call_user_func($action[1], $this->val, $this->key, $this)) {
136 8
                        $con = true;
137 8
                        break;
138
                    }
139
                }
140 12
                if ($action[0] === 'map') {
141 5
                    $this->val = call_user_func($action[1], $this->val, $this->key, $this);
142
                }
143 12
                if ($action[0] === 'mapKey') {
144 12
                    $this->key = call_user_func($action[1], $this->val, $this->key, $this);
145
                }
146
            }
147 54
            if ($con) {
148 8
                $this->iterator->next();
149 8
                continue;
150
            }
151 54
            return true;
152
        }
153 54
        return false;
154
    }
155
156
    // array access
157 1
    public function offsetGet($offset)
158
    {
159 1
        if (count($this->stack) || !($this->array instanceof \ArrayAccess)) {
160
            $this->squash();
161
        }
162 1
        return $this->squash()->iterator->offsetGet($offset);
163
    }
164 1
    public function offsetExists($offset)
165
    {
166 1
        if (count($this->stack) || !($this->array instanceof \ArrayAccess)) {
167
            $this->squash();
168
        }
169 1
        return $this->squash()->iterator->offsetExists($offset);
170
    }
171 1
    public function offsetUnset($offset)
172
    {
173 1
        return $this->squash()->iterator->offsetUnset($offset);
174
    }
175 1
    public function offsetSet($offset, $value)
176
    {
177 1
        return $this->squash()->iterator->offsetSet($offset, $value);
178
    }
179
    /**
180
     * Get the collection length
181
     * @return int
182
     */
183 1
    public function count()
184
    {
185 1
        if (count($this->stack) || !($this->array instanceof \Countable)) {
186
            $this->squash();
187
        }
188 1
        return $this->array->count();
189
    }
190
191
    // mutators
192
    /**
193
     * Filter values from the collection based on a predicate. The callback will receive the value, key and collection
194
     * @param  callable $iterator the predicate
195
     * @return $this
196
     */
197 8
    public function filter(callable $iterator) : Collection
198
    {
199 8
        $this->stack[] = [ 'filter', $iterator ];
200 8
        return $this;
201
    }
202
    /**
203
     * Pass all values of the collection through a mutator callable, which will receive the value, key and collection
204
     * @param  callable $iterator the mutator
205
     * @return $this
206
     */
207 5
    public function map(callable $iterator) : Collection
208
    {
209 5
        $this->stack[] = [ 'map', $iterator ];
210 5
        return $this;
211
    }
212
    /**
213
     * Pass all values of the collection through a key mutator callable, which will receive the value, key and collection
214
     * @param  callable $iterator the mutator
215
     * @return $this
216
     */
217 1
    public function mapKey(callable $iterator) : Collection
218
    {
219 1
        $this->stack[] = [ 'mapKey', $iterator ];
220 1
        return $this;
221
    }
222
    /**
223
     * Clone the current collection and return it.
224
     * @return Collection
225
     */
226
    public function clone() : Collection
227
    {
228 1
        return clone $this;
229
    }
230
    /**
231
     * Remove all falsy values from the collection (uses filter internally).
232
     * @return $this
233
     */
234 1
    public function compact() : Collection
235
    {
236
        return $this->filter(function ($v) {
237 1
            return !!$v;
238 1
        });
239
    }
240
    /**
241
     * Exclude all listed values from the collection (uses filter internally).
242
     * @param  iterable $values the values to exclude
243
     * @return $this
244
     */
245 3
    public function difference($values) : Collection
246
    {
247 3
        if (!is_array($values)) {
248 1
            $values = iterator_to_array($values);
249
        }
250 3
        $keys = array_keys($values);
251 3
        $isAssoc = $keys !== array_keys($keys);
252
        return $this->filter(function ($v, $k) use ($values, $isAssoc) {
253 3
            return $isAssoc ? 
254 1
                ($index = array_search($v, $values)) === false || $index !== $k :
255 3
                !in_array($v, $values, true);
256 3
        });
257
    }
258
    /**
259
     * Append more values to the collection
260
     * @param  iterable $source the values to add
261
     * @return Collection
262
     */
263 2
    public function extend($source) : Collection
264
    {
265 2
        if (!is_array($source)) {
266 1
            $source = iterator_to_array($source);
267
        }
268 2
        return new static(array_merge($this->toArray(), $source));
269
    }
270
    /**
271
     * Append more values to the collection
272
     * @param  iterable $source the values to add
273
     * @return Collection
274
     */
275 1
    public function merge($source) : Collection
276
    {
277 1
        return $this->extend($source);
278
    }
279
    /**
280
     * Perform a shallow flatten of the collection
281
     * @return Collection
282
     */
283 1
    public function flatten() : Collection
284
    {
285 1
        $rslt = [];
286 1
        $temp = $this->toArray();
287 1
        foreach ($temp as $v) {
288 1
            $rslt = array_merge($rslt, is_array($v) ? $v : [$v]);
289
        }
290 1
        return new static($rslt);
291
    }
292
    /**
293
     * Group by a key (if a callable is used - return the value to group by)
294
     * @param  string|callable $iterator the key to group by
295
     * @return Collection
296
     */
297 1
    public function groupBy($iterator) : Collection
298
    {
299 1
        $rslt = [];
300 1
        $temp = $this->toArray();
301 1
        foreach ($temp as $k => $v) {
302 1
            $rslt[is_string($iterator) ? (is_object($v) ? $v->{$iterator} : $v[$iterator]) : call_user_func($iterator, $v, $k)][] = $v;
303
        }
304 1
        return new static($rslt);
305
    }
306
    /**
307
     * Get the first X items from the collection
308
     * @param  int $count the number of items to include (defaults to 1)
309
     * @return Collection
310
     */
311 1
    public function first(int $count = 1) : Collection
312
    {
313 1
        $i = 0;
314 1
        $new = [];
315 1
        foreach ($this as $k => $v) {
316 1
            if (++$i > $count) {
317 1
                break;
318
            }
319 1
            $new[$k] = $v;
320
        }
321 1
        return new static($new);
322
    }
323
    /**
324
     * Get the first X items from the collection
325
     * @param  int $count the number of items to include (defaults to 1)
326
     * @return Collection
327
     */
328 1
    public function head(int $count = 1) : Collection
329
    {
330 1
        return $this->first($count);
331
    }
332
    /**
333
     * Get the last X items from the collection
334
     * @param  int $count the number of items to include (defaults to 1)
335
     * @return Collection
336
     */
337 2
    public function last(int $count = 1) : Collection
338
    {
339 2
        $new = $this->toArray();
340 2
        return new static(array_slice($new, $count * -1));
341
    }
342
    /**
343
     * Get the first X items from the collection
344
     * @param  int $count the number of items to include (defaults to 1)
345
     * @return Collection
346
     */
347 1
    public function tail(int $count = 1) : Collection
348
    {
349 1
        return $this->last($count);
350
    }
351
    /**
352
     * Get all but the last X items from the collection
353
     * @param  int $count the number of items to exclude (defaults to 1)
354
     * @return Collection
355
     */
356 1
    public function initial(int $count = 1) : Collection
357
    {
358 1
        $new = $this->toArray();
359 1
        return new static(array_slice($new, 0, $count * -1));
360
    }
361
    /**
362
     * Get all but the first X items from the collection
363
     * @param  int $count the number of items to exclude (defaults to 1)
364
     * @return Collection
365
     */
366 1
    public function rest(int $count = 1) : Collection
367
    {
368 1
        $new = $this->toArray();
369 1
        return new static(array_slice($new, $count));
370
    }
371
    /**
372
     * Execute a callable for each item in the collection (does not modify the collection)
373
     * @param  callable $iterator the callable to execute
374
     * @return $this
375
     */
376 1
    public function each(callable $iterator) : Collection
377
    {
378 1
        foreach ($this as $k => $v) {
379 1
            call_user_func($iterator, $v, $k, $this);
380
        }
381 1
        return $this;
382
    }
383
    /**
384
     * Execute a callable for each item in the collection (does not modify the collection)
385
     * @param  callable $iterator the callable to execute
386
     * @return $this
387
     */
388 1
    public function invoke(callable $iterator) : Collection
389
    {
390 1
        return $this->each($iterator);
391
    }
392
    /**
393
     * Get all the collection keys
394
     * @return $this
395
     */
396
    public function keys() : Collection
397
    {
398
        return $this->map(function ($v, $k) { return $k; })->values();
399
    }
400
    /**
401
     * Pluck a value from each object (uses map internally)
402
     * @param  string|int $key the key to extract
403
     * @return $this
404
     */
405 2
    public function pluck($key) : Collection
406
    {
407
        return $this->map(function ($v) use ($key) {
408 2
            return is_object($v) ?
409 1
                (isset($v->{$key}) ? $v->{$key} : null) :
410 2
                (isset($v[$key]) ? $v[$key] : null);
411 2
        });
412
    }
413
    /**
414
     * Intersect the collection with another iterable (uses filter internally)
415
     * @param  interable $values the data to intersect with
416
     * @return $this
417
     */
418 1
    public function intersection($values) : Collection
419
    {
420 1
        if (!is_array($values)) {
421 1
            $values = iterator_to_array($values);
422
        }
423 1
        $keys = array_keys($values);
424 1
        $isAssoc = $keys !== array_keys($keys);
425
        return $this->filter(function ($v, $k) use ($values, $isAssoc) {
426 1
            return $isAssoc ? 
427 1
                array_search($v, $values) === $k :
428 1
                in_array($v, $values, true);
429 1
        });
430
    }
431
    /**
432
     * Reject values on a given predicate (opposite of filter)
433
     * @param  callable $iterator the predicate
434
     * @return $this
435
     */
436 1
    public function reject(callable $iterator) : Collection
437
    {
438
        return $this->filter(function ($v, $k, $array) use ($iterator) {
439 1
            return !call_user_func($iterator, $v, $k, $array);
440 1
        });
441
    }
442
    /**
443
     * Shuffle the values in the collection
444
     * @return Collection
445
     */
446 1
    public function shuffle() : Collection
447
    {
448 1
        $temp = $this->toArray();
449 1
        $keys = array_keys($temp);
450 1
        shuffle($keys);
451 1
        $rslt = [];
452 1
        foreach ($keys as $key) {
453 1
            $rslt[$key] = $temp[$key];
454
        }
455 1
        return new static($rslt);
456
    }
457
    /**
458
     * Sort the collection using a standard sorting function
459
     * @param  callable $iterator the sort function (must return -1, 0 or 1)
460
     * @return Collection
461
     */
462 1
    public function sortBy(callable $iterator) : Collection
463
    {
464 1
        $this->squash();
465 1
        $this->array->uasort($iterator);
466 1
        return $this;
467
    }
468
    /**
469
     * Inspect the whole collection (as an array) mid-chain
470
     * @param  callable $iterator the callable to execute
471
     * @return $this
472
     */
473 1
    public function tap(callable $iterator) : Collection
474
    {
475 1
        call_user_func($iterator, $this->toArray());
476 1
        return $this;
477
    }
478
    /**
479
     * Modify the whole collection (as an array) mid-chain
480
     * @param  callable $iterator the callable to execute
481
     * @return Collection
482
     */
483 1
    public function thru(callable $iterator) : Collection
484
    {
485 1
        $temp = $this->toArray();
486 1
        $rslt = call_user_func($iterator, $temp);
487 1
        return new static($rslt);
488
    }
489
    /**
490
     * Leave only unique items in the collection
491
     * @return Collection
492
     */
493 3
    public function unique() : Collection
494
    {
495 3
        $temp = $this->toArray();
496 3
        $rslt = [];
497 3
        foreach ($temp as $k => $v) {
498 3
            if (!in_array($v, $rslt, true)) {
499 3
                $rslt[$k] = $v;
500
            }
501
        }
502 3
        return new static($rslt);
503
    }
504
    /**
505
     * Get only the values of the collection
506
     * @return Collection
507
     */
508 3
    public function values() : Collection
509
    {
510 3
        return new static(array_values($this->toArray()));
511
    }
512
    /**
513
     * Filter items from the collection using key => value pairs
514
     * @param  array   $properties the key => value to check for in each item
515
     * @param  boolean $strict     should the comparison be strict
516
     * @return $this
517
     */
518
    public function where(array $properties, $strict = true) : Collection
519
    {
520 1
        return $this->filter(function ($v) use ($properties, $strict) {
521 1
            foreach ($properties as $key => $value) {
522 1
                $vv = is_object($v) ? (isset($v->{$key}) ? $v->{$key} : null) : (isset($v[$key]) ? $v[$key] : null);
523 1
                if (!$vv || ($strict && $vv !== $value) || (!$strict && $vv != $value)) {
524 1
                    return false;
525
                }
526
            }
527 1
            return true;
528 1
        });
529
    }
530
    /**
531
     * Exclude all listed values from the collection (uses filter internally).
532
     * @param  iterable $values the values to exclude
533
     * @return $this
534
     */
535 2
    public function without($values) : Collection
536
    {
537 2
        return $this->difference($values);
538
    }
539
    /**
540
     * Combine all the values from the collection with a key
541
     * @param  iterable $keys the keys to use
542
     * @return Collection
543
     */
544 2
    public function zip($keys) : Collection
545
    {
546 2
        if (!is_array($keys)) {
547 1
            $keys = iterator_to_array($keys);
548
        }
549 2
        return new static(array_combine($keys, $this->toArray()));
550
    }
551
    /**
552
     * Reverse the collection order
553
     * @return Collection
554
     */
555 1
    public function reverse() : Collection
556
    {
557 1
        return new static(array_reverse($this->toArray()));
558
    }
559
560
    // accessors
561
    /**
562
     * Do all of the items in the collection match a given criteria
563
     * @param  callable $iterator the criteria - should return true / false
564
     * @return bool
565
     */
566 1
    public function all(callable $iterator) : bool
567
    {
568 1
        foreach ($this as $k => $v) {
569 1
            if (!call_user_func($iterator, $v, $k, $this)) {
570 1
                return false;
571
            }
572
        }
573 1
        return true;
574
    }
575
    /**
576
     * Do any of the items in the collection match a given criteria
577
     * @param  callable $iterator the criteria - should return true / false
578
     * @return bool
579
     */
580 1
    public function any(callable $iterator) : bool
581
    {
582 1
        foreach ($this as $k => $v) {
583 1
            if (call_user_func($iterator, $v, $k, $this)) {
584 1
                return true;
585
            }
586
        }
587 1
        return false;
588
    }
589
    /**
590
     * Does the collection contain a given value
591
     * @param  mixed $needle the value to check for
592
     * @return bool
593
     */
594 1
    public function contains($needle) : bool
595
    {
596 1
        foreach ($this as $k => $v) {
597 1
            if ($v === $needle) {
598 1
                return true;
599
            }
600
        }
601 1
        return false;
602
    }
603
    /**
604
     * Get the first element matching a given criteria (or null)
605
     * @param  callable $iterator the filter criteria
606
     * @return mixed
607
     */
608 1
    public function find(callable $iterator)
609
    {
610 1
        foreach ($this as $k => $v) {
611 1
            if (call_user_func($iterator, $v, $k, $this)) {
612 1
                return $v;
613
            }
614
        }
615 1
        return null;
616
    }
617
    /**
618
     * Get all the elements matching a given criteria (with the option to limit the number of results)
619
     * @param  callable $iterator the search criteria
620
     * @param  int|null $limit    optional limit to the number of results (default to null - no limit)
621
     * @return Collection
622
     */
623 1
    public function findAll(callable $iterator, int $limit = null) : Collection
624
    {
625 1
        $res = [];
626 1
        foreach ($this as $k => $v) {
627 1
            if (call_user_func($iterator, $v, $k, $this)) {
628 1
                $res[] = $v;
629
            }
630 1
            if ((int)$limit > 0 && count($res) >= $limit) {
631 1
                break;
632
            }
633
        }
634 1
        return new static($res);
635
    }
636
    /**
637
     * Get the key corresponding to a value (or false)
638
     * @param  mixed  $needle the value to search for
639
     * @return mixed
640
     */
641 1
    public function indexOf($needle)
642
    {
643 1
        return array_search($needle, $this->toArray(), true);
644
    }
645
    /**
646
     * Get the last key corresponding to a value (or false)
647
     * @param  mixed  $needle the value to search for
648
     * @return mixed
649
     */
650 1
    public function lastIndexOf($needle)
651
    {
652 1
        $res = null;
653 1
        foreach ($this as $k => $v) {
654 1
            if ($v === $needle) {
655 1
                $res = $k;
656
            }
657
        }
658 1
        return $res;
659
    }
660
    /**
661
     * Get the number of elements in the collection
662
     * @return int
663
     */
664 1
    public function size() : int
665
    {
666 1
        return $this->count();
667
    }
668
    /**
669
     * Get the minimal item in the collection
670
     * @return mixed
671
     */
672 1
    public function min()
673
    {
674 1
        $min = null;
675 1
        $first = false;
676 1
        foreach ($this as $v) {
677 1
            if (!$first || $v < $min) {
678 1
                $min = $v;
679 1
                $first = true;
680
            }
681
        }
682 1
        return $min;
683
    }
684
    /**
685
     * Get the maximum item in the collection
686
     * @return mixed
687
     */
688 1
    public function max()
689
    {
690 1
        $max = null;
691 1
        $first = false;
692 1
        foreach ($this as $v) {
693 1
            if (!$first || $v > $max) {
694 1
                $max = $v;
695 1
                $first = true;
696
            }
697
        }
698 1
        return $max;
699
    }
700
    /**
701
     * Does the collection contain a given key
702
     * @param  string|int  $key the key to check
703
     * @return bool
704
     */
705 1
    public function has($key) : bool
706
    {
707 1
        return $this->offsetExists($key);
708
    }
709
    /**
710
     * Reduce the collection to a single value
711
     * @param  callable $iterator the reducer
712
     * @param  mixed    $initial  the initial value
713
     * @return mixed the final value
714
     */
715 1
    public function reduce(callable $iterator, $initial = null)
716
    {
717 1
        return array_reduce($this->toArray(), $iterator, $initial);
718
    }
719
    /**
720
     * Reduce the collection to a single value, starting from the last element
721
     * @param  callable $iterator the reducer
722
     * @param  mixed    $initial  the initial value
723
     * @return mixed the final value
724
     */
725 1
    public function reduceRight(callable $iterator, $initial = null)
726
    {
727 1
        return array_reduce(array_reverse($this->toArray()), $iterator, $initial);
728
    }
729
}
730