Test Setup Failed
Pull Request — master (#10)
by Luke
13:21
created

Collection::getColumn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
ccs 1
cts 1
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * nozavroni/collect
4
 *
5
 * This is a basic utility library for PHP5.6+ with special emphesis on Collections.
6
 *
7
 * @author Luke Visinoni <[email protected]>
8
 * @copyright (c) 2018 Luke Visinoni <[email protected]>
9
 * @license MIT (see LICENSE file)
10
 */
11
namespace Noz\Collection;
12
13
use Countable;
14
use JsonSerializable;
15
use Iterator;
16
use ArrayAccess;
17
use RuntimeException;
18
use Traversable;
19
20
use function Noz\is_traversable,
21
             Noz\to_array;
22
23
/**
24
 * Nozavroni Collection
25
 *
26
 * Basically an array wrapper with a bunch of super useful methods for working with its items and/or create new collections from its items.
27
 *
28
 * @note None of the methods in this class have a $preserveKeys param. That is by design. I don't think it's necessary.
29
 *       Instead, keys are ALWAYS preserved and if you want to NOT preserve keys, simply call Collection::values().
30
 * @todo Scour Laravel's collection methods for ideas (for instance contains($val, $key) to check key as well as value)
31
 *       So I did and the following methods look interesting (think about implementing): tap, times, transform, zip?
32
 *       I also still like the idea of a pairs() method that returns a collection of the collection's key/val pairs as
33
 *       two-item arrays [key, val].
34
 */
35
class Collection implements ArrayAccess, Iterator, Countable, JsonSerializable
36 93
{
37
    /** @var array The items for this collection */
38 93
    protected $items;
39 93
40 93
    /**
41
     * Collection constructor.
42
     *
43
     * @param array $items
44
     */
45
    public function __construct(array $items = [])
46
    {
47
        $this->items = $items;
48
        $this->rewind();
49
    }
50 30
51
    /**
52 30
     * Generate a collection from an array of items.
53 10
     * I created this method so that it's possible to extend a collection more easily.
54 10
     *
55 30
     * @param mixed $items
56
     *
57
     * @return Collection
58
     */
59
    public static function factory($items = null)
60
    {
61
        if (is_null($items)) {
62
            $items = [];
63 47
        }
64
        return new Collection(to_array($items));
65 47
    }
66
67
    /**
68
     * Get collection as an array
69
     *
70
     * @return array
71
     */
72
    public function toArray()
73
    {
74
        return $this->items;
75 47
    }
76
77 47
    /**
78
     * Determine if collection has a given key
79
     *
80
     * @param mixed $key The key to look for
81
     *
82
     * @return bool
83
     */
84
    public function has($key)
85
    {
86
        return isset($this->items[$key]) || array_key_exists($key, $this->items);
87
    }
88
89
    /**
90
     * Does collection have item at position?
91 2
     *
92
     * Determine if collection has an item at a particular position (indexed from one).
93
     * Position can be positive and start from the beginning or it can be negative and
94 2
     * start from the end.
95 2
     *
96 2
     * @param int $position
97 2
     *
98
     * @return bool
99
     */
100
    public function hasValueAt($position)
101
    {
102
        try {
103
            $this->getKeyAt($position);
104
            return true;
105
        } catch (RuntimeException $e) {
106
            return false;
107
        }
108
    }
109
110
    /**
111
     * Get key at given position
112
     *
113
     * Returns the key at the given position, starting from one. Position can be positive (start from beginning) or
114
     * negative (start from the end).
115 6
     *
116
     * If the position does not exist, a RuntimeException is thrown.
117 6
     *
118 6
     * @param int $position
119 3
     *
120 3
     * @return string
121 6
     *
122 6
     * @throws RuntimeException
123 6
     */
124 5
    public function getKeyAt($position)
125
    {
126 6
        $collection = $this;
127 3
        if ($position < 0) {
128
            $collection = $this->reverse();
129
        }
130
        $i = 1;
131
        foreach ($collection as $key => $val) {
132
            if (abs($position) == $i++) {
133
                return $key;
134
            }
135
        }
136
        throw new RuntimeException("No key at position {$position}");
137
    }
138
139
    /**
140
     * Get value at given position
141
     *
142
     * Returns the value at the given position, starting from one. Position can be positive (start from beginning) or
143
     * negative (start from the end).
144 2
     *
145
     * If the position does not exist, a RuntimeException is thrown.
146 2
     *
147
     * @param int $position
148
     *
149
     * @return mixed
150
     *
151
     * @throws RuntimeException
152
     */
153
    public function getValueAt($position)
154
    {
155
        return $this->get($this->getKeyAt($position));
156
    }
157
158 2
    /**
159
     * Get the key of the first item found matching $item
160 2
     *
161 2
     * @param mixed|callable $item
162 2
     *
163 1
     * @return mixed|null
164 1
     */
165
    public function keyOf($item)
166 2
    {
167 1
        $index = 0;
168
        foreach ($this as $key => $val) {
169 2
            if (is_callable($item)) {
170
                if ($item($val, $key, $index++)) {
171 1
                    return $key;
172
                }
173
            } elseif ($item === $val) {
174
                return $key;
175
            }
176
        }
177
178
        throw new RuntimeException("No item found at given key: {$item}");
179
    }
180
181
    /**
182
     * Get the offset (index) of the first item found that matches $item
183 2
     *
184
     * @param mixed|callable $item
185 2
     *
186 2
     * @return int|null
187 2
     */
188 1
    public function indexOf($item)
189 1
    {
190
        $index = 0;
191 1
        foreach ($this as $key => $val) {
192 1
            if (is_callable($item)) {
193 1
                if ($item($val, $key, $index)) {
194
                    return $index;
195
                }
196 2
            } else {
197 2
                if ($item === $val) {
198
                    return $index;
199 1
                }
200
            }
201
            $index++;
202
        }
203
204
        throw new RuntimeException("No key found for given item: {$item}");
205
    }
206
207
    /**
208
     * Get item by key, with an optional default return value
209
     *
210 8
     * @param mixed $key
211
     * @param mixed $default
212 8
     *
213 8
     * @return mixed
214
     */
215
    public function get($key, $default = null)
216 3
    {
217
        if ($this->has($key)) {
218
            return $this->items[$key];
219
        }
220
221
        return $default;
222
    }
223
224
    /**
225
     * Add an item with no regard to key
226 8
     *
227
     * @param mixed $value
228 8
     *
229
     * @return $this
230 8
     */
231
    public function add($value)
232
    {
233
        $this->items[] = $value;
234
235
        return $this;
236
    }
237
238
    /**
239
     * Set an item at a given key
240
     *
241
     * @param mixed $key
242 14
     * @param mixed $value
243
     * @param bool  $overwrite If false, do not overwrite existing key
244 14
     *
245 14
     * @return $this
246 14
     */
247
    public function set($key, $value, $overwrite = true)
248 14
    {
249
        if ($overwrite || !$this->has($key)) {
250
            $this->items[$key] = $value;
251
        }
252
253
        return $this;
254
    }
255
256
    /**
257
     * Delete an item by key
258 3
     *
259
     * @param mixed $key
260 3
     *
261
     * @return $this
262 3
     */
263
    public function delete($key)
264
    {
265
        unset($this->items[$key]);
266
267
        return $this;
268
    }
269
270 2
    /**
271
     * Clear the collection of all its items.
272 2
     *
273
     * @return $this
274 2
     */
275
    public function clear()
276
    {
277
        $this->items = [];
278
279
        return $this;
280
    }
281
282
    /**
283
     * Determine if collection contains given value
284
     *
285 4
     * @param mixed|callable $val
286
     * @param mixed $key
287 4
     *
288 4
     * @return bool
289 4
     */
290 4
    public function contains($val, $key = null)
291 1
    {
292 1
        $index = 0;
293
        foreach ($this as $k => $v) {
294 1
            $matchkey = is_null($key) || $key === $k;
295 3
            if (is_callable($val)) {
296 3
                if ($val($v, $k, $index)) {
297
                    return $matchkey;
298
                }
299 4
            } else {
300 4
                if ($val === $v) {
301 2
                    return $matchkey;
302
                }
303
            }
304
            $index++;
305
        }
306
        return false;
307
    }
308
309
    /**
310
     * Fetch item from collection by key and remove it from collection
311 1
     *
312
     * @param mixed $key
313 1
     *
314 1
     * @return mixed
315 1
     */
316 1
    public function pull($key)
317
    {
318 1
        if ($this->has($key)) {
319
            $value = $this->get($key);
320
            $this->delete($key);
321
            return $value;
322
        }
323
    }
324
325
    /**
326
     * Join collection items using a delimiter
327 1
     *
328
     * @param string $delim
329 1
     *
330
     * @return string
331
     */
332
    public function join($delim = '')
333
    {
334
        return implode($delim, $this->items);
335
    }
336
337 4
    /**
338
     * Determine if collection has any items
339 4
     *
340
     * @return bool
341
     */
342
    public function isEmpty()
343
    {
344
        return $this->count() == 0;
345
    }
346
347 1
    /**
348
     * Get a collection of only this collection's values (without its keys)
349 1
     *
350
     * @return Collection
351
     */
352
    public function values()
353
    {
354
        return static::factory(array_values($this->items));
355
    }
356
357 1
    /**
358
     * Get a collection of only this collection's keys
359 1
     *
360
     * @return Collection
361
     */
362
    public function keys()
363
    {
364
        return static::factory(array_keys($this->items));
365
    }
366
367 6
    /**
368
     * Get a collection with order reversed
369 6
     *
370
     * @return Collection
371
     */
372
    public function reverse()
373
    {
374
        return static::factory(array_reverse($this->items));
375
    }
376
377 1
    /**
378
     * Get a collection with keys and values flipped
379 1
     *
380 1
     * @return Collection
381 1
     */
382 1
    public function flip()
383 1
    {
384
        $collection = static::factory();
385
        foreach ($this as $key => $val) {
386
            $collection->set($val, $key);
387
        }
388
        return $collection;
389
    }
390
391 1
    /**
392
     * Shuffle the order of this collection's values
393 1
     *
394 1
     * @return Collection
395
     */
396
    public function shuffle()
397
    {
398
        shuffle($this->items);
399
        return $this;
400
    }
401
402
    /**
403
     * Get a random value from the collection
404 1
     * 
405
     * @return mixed
406 1
     */
407
    public function random()
408
    {
409
        return $this->getValueAt(rand(1, $this->count()));
410
    }
411
412
    /**
413
     * Sort the collection (using values)
414
     *
415
     * @param callable $alg
416 2
     *
417
     * @return $this
418 2
     */
419
    public function sort(callable $alg = null)
420 1
    {
421 1
        if (is_null($alg)) {
422 2
            // case-sensitive string comparison is the default sorting mechanism
423
            $alg = 'strcmp';
424 2
        }
425
        uasort($this->items, $alg);
426
427
        return $this;
428
    }
429
430
    /**
431
     * Sort the collection (using keys)
432
     *
433
     * @param callable $alg
434 2
     *
435
     * @return $this
436 2
     */
437
    public function ksort(callable $alg = null)
438 1
    {
439 1
        if (is_null($alg)) {
440 2
            // case-sensitive string comparison is the default sorting mechanism
441
            $alg = 'strcmp';
442 2
        }
443
        uksort($this->items, $alg);
444
445
        return $this;
446
    }
447
448
    /**
449
     * Append items to collection without regard to keys
450
     *
451
     * @param array|Traversable $items
452 2
     *
453
     * @return $this
454 2
     */
455 1
    public function append($items)
456
    {
457
        if (!is_traversable($items)) {
458 1
            throw new RuntimeException("Invalid input type for " . __METHOD__ . ", must be array or Traversable");
459 1
        }
460 1
461
        foreach ($items as $val) {
462 1
            $this->add($val);
463
        }
464
465
        return $this;
466
    }
467
468
    /**
469
     * Return first item or first item where callback returns true
470
     *
471
     * @param callable|null $callback
472 7
     *
473
     * @return mixed|null
474 7
     */
475 7
    public function first(callable $callback = null)
476 7
    {
477 7
        $index = 0;
478
        foreach ($this as $key => $val) {
479 6
            if (is_null($callback) || $callback($val, $key, $index++)) {
480
                return $val;
481
            }
482
        }
483
484
        return null;
485
    }
486
487
    /**
488
     * Return last item or last item where callback returns true
489
     *
490
     * @param callable|null $callback
491 3
     *
492
     * @return mixed|null
493 3
     */
494
    public function last(callable $callback = null)
495
    {
496
        return $this->reverse()->first($callback);
497
    }
498
499
    /**
500
     * Map collection
501
     *
502
     * Create a new collection using the results of a callback function on each item in this collection.
503
     *
504
     * @param callable $callback
505 3
     *
506
     * @return Collection
507 3
     */
508
    public function map(callable $callback)
509 3
    {
510 3
        $collection = static::factory();
511 3
512 3
        $index = 0;
513
        foreach ($this as $key => $val) {
514 3
            $collection->set($key, $callback($val, $key, $index++));
515
        }
516
517
        return $collection;
518
    }
519
520
    /**
521
     * Combine collection with another traversable/collection
522
     *
523
     * Using this collection's keys, and the incoming collection's values, a new collection is created and returned.
524
     *
525
     * @param array|Traversable $items
526 5
     *
527
     * @return Collection
528 5
     */
529 1
    public function combine($items)
530
    {
531
        if (!is_traversable($items)) {
532 4
            throw new RuntimeException("Invalid input type for " . __METHOD__ . ", must be array or Traversable");
533 4
        }
534 1
535
        $items = to_array($items);
536
        if (count($items) != count($this->items)) {
537 3
            throw new RuntimeException("Invalid input for " . __METHOD__ . ", number of items does not match");
538
        }
539
540
        return static::factory(array_combine($this->items, $items));
541
    }
542
543
    /**
544
     * Get a new collection with only distinct values
545 1
     *
546
     * @return Collection
547 1
     */
548 1
    public function distinct()
549 1
    {
550 1
        $collection = static::factory();
551 1
        foreach ($this as $key => $val) {
552 1
            if (!$collection->contains($val)) {
553
                $collection->set($key, $val);
554 1
            }
555
        }
556
557
        return $collection;
558
    }
559
560
    /**
561
     * Remove all duplicate values from collection in-place
562 1
     *
563
     * @return Collection
564 1
     */
565
    public function deduplicate()
566 1
    {
567
        $this->items = array_unique($this->items);
568
569
        return $this;
570
    }
571
572
    /**
573
     * Return a new collection with only filtered keys/values
574
     *
575
     * The callback accepts value, key, index and should return true if the item should be added to the returned
576
     * collection
577
     *
578
     * @param callable $callback
579 2
     *
580
     * @return Collection
581 2
     */
582 2
    public function filter(callable $callback = null)
583 2
    {
584 2
        $collection = static::factory();
585 1
        $index = 0;
586 1
        foreach ($this as $key => $value) {
587 1
            if (is_null($callback)) {
588 1
                if ($value) {
589 1
                    $collection->set($key, $value);
590 1
                }
591 1
            } else {
592
                if ($callback($value, $key, $index++)) {
593 2
                    $collection->set($key, $value);
594
                }
595 2
            }
596
        }
597
598
        return $collection;
599
    }
600
601
    /**
602
     * Fold collection into a single value
603
     *
604
     * Loop through collection calling a callback function and passing the result to the next iteration, eventually
605
     * returning a single value.
606
     *
607
     * @param callable $callback
608
     * @param mixed $initial
609 1
     *
610
     * @return null
611 1
     */
612 1
    public function fold(callable $callback, $initial = null)
613 1
    {
614 1
        $index = 0;
615 1
        $folded = $initial;
616
        foreach ($this as $key => $val) {
617 1
            $folded = $callback($folded, $val, $key, $index++);
618
        }
619
620
        return $folded;
621
    }
622
623
    /**
624
     * Return a merge of this collection and $items
625
     *
626
     * @param array|Traversable $items
627 3
     *
628
     * @return Collection
629 3
     */
630 1
    public function merge($items)
631
    {
632
        if (!is_traversable($items)) {
633 2
            throw new RuntimeException("Invalid input type for " . __METHOD__ . ", must be array or Traversable");
634 2
        }
635 2
636 2
        $collection = clone $this;
637
        foreach ($items as $key => $val) {
638 2
            $collection->set($key, $val);
639
        }
640
641
        return $collection;
642
    }
643
644
    /**
645
     * Create a new collection with a union of this collection and $items
646
     *
647
     * This method is similar to merge, except that existing items will not be overwritten.
648 2
     *
649
     * @param $items
650 2
     */
651 1
    public function union($items)
652
    {
653
        if (!is_traversable($items)) {
654 1
            throw new RuntimeException("Invalid input type for " . __METHOD__ . ", must be array or Traversable");
655 1
        }
656 1
657 1
        $collection = clone $this;
658
        foreach ($items as $key => $val) {
659 1
            $collection->set($key, $val, false);
660
        }
661
662
        return $collection;
663
    }
664
665
    /**
666
     * Call callback for each item in collection, passively
667
     * If at any point the callback returns false, iteration stops.
668
     *
669
     * @param callable $callback
670 2
     *
671
     * @return $this
672 2
     */
673 2
    public function each(callable $callback)
674 2
    {
675 1
        $index = 0;
676
        foreach ($this as $key => $val) {
677 2
            if ($callback($val, $key, $index++) === false) {
678
                break;
679 2
            }
680
        }
681
682
        return $this;
683
    }
684
685
    /**
686
     * Assert callback returns $expected value for each item in collection.
687
     *
688
     * @param callable $callback
689
     * @param bool $expected
690
     *
691
     * @return bool
692 1
     */
693
    public function assert(callable $callback, $expected = true)
694 1
    {
695 1
        $index = 0;
696 1
        foreach ($this as $key => $val) {
697 1
            if ($callback($val, $key, $index++) !== $expected) {
698
                return false;
699 1
            }
700
        }
701 1
702
        return true;
703
    }
704
705
    /**
706
     * Pipe collection through a callback
707
     *
708
     * @param callable $callback
709
     *
710
     * @return mixed
711 1
     */
712
    public function pipe(callable $callback)
713 1
    {
714
        return $callback($this);
715
    }
716
717
    /**
718
     * Get new collection in chunks of $size
719
     *
720
     * Creates a new collection of arrays of $size length. The remainder items will be placed at the end.
721
     *
722
     * @param int $size
723
     *
724
     * @return Collection
725 2
     */
726
    public function chunk($size)
727 2
    {
728
        return static::factory(array_chunk($this->items, $size, true));
729
    }
730
731
    /**
732
     * Get a new collection of $count chunks
733
     *
734
     * @param int $count
735
     *
736
     * @return Collection
737
     */
738
    public function split($count = 1)
739
    {
740 1
        return $this->chunk(ceil($this->count() / $count));
741
    }
742 1
743
    /**
744
     * Get a slice of this collection.
745
     *
746
     * @param int $offset
747
     * @param int|null $length
748
     *
749
     * @return Collection
750
     */
751
    public function slice($offset, $length = null)
752
    {
753 1
        return static::factory(array_slice($this->items, $offset, $length, true));
754
    }
755 1
756
    /**
757
     * Get collection with only differing items
758
     *
759
     * @param array|Traversable $items
760
     *
761
     * @return Collection
762
     */
763
    public function diff($items)
764
    {
765 1
        return static::factory(array_diff($this->items, to_array($items)));
766
    }
767 1
768
    /**
769
     * Get collection with only differing items (by key)
770
     *
771
     * @param array|Traversable $items
772
     *
773
     * @return Collection
774
     */
775
    public function kdiff($items)
776
    {
777 1
        return static::factory(array_diff_key($this->items, to_array($items)));
778
    }
779 1
780
    /**
781
     * Get collection with only intersecting items
782
     *
783
     * @param array|Traversable $items
784
     *
785
     * @return Collection
786
     */
787
    public function intersect($items)
788
    {
789 1
        return static::factory(array_intersect($this->items, to_array($items)));
790
    }
791 1
792
    /**
793
     * Get collection with only intersecting items (by key)
794
     *
795
     * @param array|Traversable $items
796
     *
797
     * @return Collection
798
     */
799
    public function kintersect($items)
800
    {
801 1
        return static::factory(array_intersect_key($this->items, to_array($items)));
802
    }
803 1
804
    /**
805
     * Remove last item in collection and return it
806
     *
807
     * @return mixed
808
     */
809
    public function pop()
810
    {
811 1
        return array_pop($this->items);
812
    }
813 1
814
    /**
815
     * Remove first item in collection and return it (and re-index if numerically indexed)
816
     *
817
     * @return mixed
818
     */
819
    public function shift()
820
    {
821 2
        return array_shift($this->items);
822
    }
823 2
824
    /**
825
     * Add item to the end of the collection
826
     *
827
     * @note This method is no different than add() but I included it for consistency's sake since I have the others
828
     *
829
     * @param mixed $item
830
     *
831
     * @return $this
832
     */
833
    public function push($item)
834
    {
835 1
        return $this->add($item);
836
    }
837 1
838
    /**
839
     * Add item to the beginning of the collection (and re-index if a numerically indexed collection)
840
     *
841
     * @param mixed $item
842
     *
843
     * @return $this
844
     */
845
    public function unshift($item)
846
    {
847 5
        array_unshift($this->items, $item);
848
849 5
        return $this;
850
    }
851 5
852
    /**
853
     * Get new collection padded to specified $size with $value
854
     *
855
     * Using $value, pad the collection to specified $size. If $size is smaller or equal to the size of the collection,
856
     * then no padding takes place. If $size is positive, padding is added to the end, while if negative, padding will
857
     * be added to the beginning.
858
     *
859
     * @param int $size
860
     * @param mixed $value
861
     *
862
     * @return Collection
863
     */
864
    public function pad($size, $value = null)
865
    {
866 3
        $collection = clone $this;
867
        while ($collection->count() < abs($size)) {
868 3
            if ($size > 0) {
869 3
                $collection->add($value);
870 3
            } else {
871 3
                $collection->unshift($value);
872 3
            }
873 3
        }
874
875 3
        return $collection;
876
    }
877 3
878
    /**
879
     * Partition collection into two collections using a callback
880
     *
881
     * Iterates over each element in the collection with a callback. Items where callback returns true are placed in one
882
     * collection and the rest in another. Finally, the two collections are placed in an array and returned for easy use
883
     * with the list() function. ( list($a, $b) = $col->partition(function($val, $key, $index) {}) )
884
     *
885
     * @param callable $callback
886
     *
887
     * @return array<Collection>
888
     */
889
    public function partition(callable $callback)
890
    {
891 2
        $pass = static::factory();
892
        $fail = static::factory();
893 2
894 2
        $index = 0;
895
        foreach ($this as $key => $val) {
896 2
            if ($callback($val, $key, $index++)) {
897 2
                $pass->set($key, $val);
898 1
            } else {
899 1
                $fail->set($key, $val);
900 1
            }
901 1
        }
902
903 2
        return [$pass, $fail];
904
    }
905 2
906
    /**
907
     * Get column values by key
908
     *
909
     * This method expects the collection's data to be tabular in nature (two-dimensional and for the rows to have
910
     * consistently named keys). If the data is not structured this way, it will do the best it can but it is not meant
911
     * for unstructured, non-tabular data so don't expect consistent results.
912
     *
913
     * @param string|int $column The key of the column you want to get
914
     *
915 1
     * @return Collection
916
     */
917 1
    public function getColumn($column)
918
    {
919
        return static::factory(array_column($this->items, $column));
920
    }
921
922
    /**
923
     * Is collection tabular?
924
     *
925
     * Returns true if the data in the collection is tabular in nature, meaning it is at least two-dimensional and each
926
     * row contains the same number of values with the same keys.
927 1
     *
928
     * @return bool
929 1
     */
930
    public function isTabular()
931
    {
932
        $first = $this->first();
933
        return $this->assert(function($row) use ($first) {
934
            if (!is_traversable(($first)) || !is_traversable($row)) {
935 2
                return false;
936
            }
937 2
            return Collection::factory($row)
938 1
                ->kdiff($first)
939
                ->isEmpty();
940
        });
941 1
    }
942
943
    /** ++++                  ++++ **/
944
    /** ++ Interface Compliance ++ **/
945
    /** ++++                  ++++ **/
946
947 1
    /**
948
     * JSON serialize
949 1
     *
950 1
     * @return array
951
     */
952
    public function jsonSerialize()
953
    {
954
        return $this->toArray();
955 1
    }
956
957 1
    /** ++++                  ++++ **/
958 1
    /** ++ Array Access Methods ++ **/
959 1
    /** ++++                  ++++ **/
960
961 1
    /**
962 1
     * Does offset exist?
963
     *
964
     * @param mixed $offset
965
     *
966
     * @return bool
967
     */
968
    public function offsetExists($offset)
969
    {
970
        return $this->has($offset);
971 35
    }
972
973 35
    /**
974
     * Get item at offset
975
     *
976
     * @param mixed $offset
977
     *
978
     * @return mixed
979 35
     */
980
    public function offsetGet($offset)
981 35
    {
982
        if (!$this->has($offset)) {
983
            throw new RuntimeException("Unknown offset: {$offset}");
984
        }
985
986
        return $this->get($offset);
987 34
    }
988
989 34
    /**
990
     * Unset item at offset
991
     *
992
     * @param mixed $offset
993
     *
994
     * @return void
995
     */
996
    public function offsetUnset($offset)
997 93
    {
998
        $this->delete($offset);
999 93
    }
1000 93
1001
    /**
1002
     * Set item at offset
1003
     *
1004
     * @param mixed $offset
1005 36
     * @param mixed $value
1006
     *
1007 36
     * @return $this
1008
     */
1009
    public function offsetSet($offset, $value)
1010
    {
1011
        if (!isset($offset)) {
1012
            $this->add($value);
1013
        }
1014
1015
        $this->set($offset, $value);
1016
    }
1017 10
1018
    /** ++++                  ++++ **/
1019 10
    /** ++   Iterator Methods   ++ **/
1020
    /** ++++                  ++++ **/
1021
1022
    /**
1023
     * {@inheritDoc}
1024
     */
1025
    public function current()
1026
    {
1027
        return current($this->items);
1028
    }
1029
1030
    /**
1031
     * {@inheritDoc}
1032
     */
1033
    public function key()
1034
    {
1035
        return key($this->items);
1036
    }
1037
1038
    /**
1039
     * {@inheritDoc}
1040
     */
1041
    public function next()
1042
    {
1043
        return next($this->items);
1044
    }
1045
1046
    /**
1047
     * {@inheritDoc}
1048
     */
1049
    public function rewind()
1050
    {
1051
        reset($this->items);
1052
    }
1053
1054
    /**
1055
     * {@inheritDoc}
1056
     */
1057
    public function valid()
1058
    {
1059
        return $this->has(key($this->items));
1060
    }
1061
1062
    /** ++++                  ++++ **/
1063
    /** ++   Countable Method   ++ **/
1064
    /** ++++                  ++++ **/
1065
1066
    /**
1067
     * {@inheritDoc}
1068
     */
1069
    public function count()
1070
    {
1071
        return count($this->items);
1072
    }
1073
}