Completed
Push — master ( 54ed6a...79e18b )
by Ivan
02:00
created

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