Completed
Push — master ( 59a712...6828bb )
by Ivan
01:54
created

Collection::last()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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