Completed
Pull Request — master (#10)
by Luke
18:21 queued 09:34
created

Collection::isTabular()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
nc 1
nop 0
dl 0
loc 12
ccs 9
cts 9
cp 1
crap 3
rs 9.8666
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
{
37
    /** @var array The items for this collection */
38
    protected $items;
39
40
    /**
41
     * Collection constructor.
42
     *
43
     * @param array $items
44
     */
45 100
    public function __construct(array $items = [])
46
    {
47 100
        $this->items = $items;
48 100
        $this->rewind();
49 100
    }
50
51
    /**
52
     * Generate a collection from an array of items.
53
     * I created this method so that it's possible to extend a collection more easily.
54
     *
55
     * @param mixed $items
56
     *
57
     * @return Collection
58
     */
59 37
    public static function factory($items = null)
60
    {
61 37
        if (is_null($items)) {
62 10
            $items = [];
63 10
        }
64 37
        return new Collection(to_array($items));
65
    }
66
67
    /**
68
     * Get collection as an array
69
     *
70
     * @return array
71
     */
72 50
    public function toArray()
73
    {
74 50
        return $this->items;
75
    }
76
77
    /**
78
     * Determine if collection has a given key
79
     *
80
     * @param mixed $key The key to look for
81
     *
82
     * @return bool
83
     */
84 50
    public function has($key)
85
    {
86 50
        return isset($this->items[$key]) || array_key_exists($key, $this->items);
87
    }
88
89
    /**
90
     * Does collection have item at position?
91
     *
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
     * start from the end.
95
     *
96
     * @param int $position
97
     *
98
     * @return bool
99
     */
100 2
    public function hasValueAt($position)
101
    {
102
        try {
103 2
            $this->getKeyAt($position);
104 2
            return true;
105 2
        } catch (RuntimeException $e) {
106 2
            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
     *
116
     * If the position does not exist, a RuntimeException is thrown.
117
     *
118
     * @param int $position
119
     *
120
     * @return string
121
     *
122
     * @throws RuntimeException
123
     */
124 6
    public function getKeyAt($position)
125
    {
126 6
        $collection = $this;
127 6
        if ($position < 0) {
128 3
            $collection = $this->reverse();
129 3
        }
130 6
        $i = 1;
131 6
        foreach ($collection as $key => $val) {
132 6
            if (abs($position) == $i++) {
133 5
                return $key;
134
            }
135 6
        }
136 3
        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
     *
145
     * If the position does not exist, a RuntimeException is thrown.
146
     *
147
     * @param int $position
148
     *
149
     * @return mixed
150
     *
151
     * @throws RuntimeException
152
     */
153 2
    public function getValueAt($position)
154
    {
155 2
        return $this->get($this->getKeyAt($position));
156
    }
157
158
    /**
159
     * Get the key of the first item found matching $item
160
     *
161
     * @param mixed|callable $item
162
     *
163
     * @return mixed|null
164
     */
165 3
    public function keyOf($item)
166
    {
167 3
        $index = 0;
168 3
        foreach ($this as $key => $val) {
169 3
            if (is_callable($item)) {
170 1
                if ($item($val, $key, $index++)) {
171 1
                    return $key;
172
                }
173 3
            } elseif ($item === $val) {
174 1
                return $key;
175
            }
176 3
        }
177
178 1
        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
     *
184
     * @param mixed|callable $item
185
     *
186
     * @return int|null
187
     */
188 3
    public function indexOf($item)
189
    {
190 3
        $index = 0;
191 3
        foreach ($this as $key => $val) {
192 3
            if (is_callable($item)) {
193 1
                if ($item($val, $key, $index)) {
194 1
                    return $index;
195
                }
196 1
            } else {
197 2
                if ($item === $val) {
198 1
                    return $index;
199
                }
200
            }
201 3
            $index++;
202 3
        }
203
204 1
        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
     * @param mixed $key
211
     * @param mixed $default
212
     *
213
     * @return mixed
214
     */
215 8
    public function get($key, $default = null)
216
    {
217 8
        if ($this->has($key)) {
218 8
            return $this->items[$key];
219
        }
220
221 3
        return $default;
222
    }
223
224
    /**
225
     * Add an item with no regard to key
226
     *
227
     * @param mixed $value
228
     *
229
     * @return $this
230
     */
231 8
    public function add($value)
232
    {
233 8
        $this->items[] = $value;
234
235 8
        return $this;
236
    }
237
238
    /**
239
     * Set an item at a given key
240
     *
241
     * @param mixed $key
242
     * @param mixed $value
243
     * @param bool  $overwrite If false, do not overwrite existing key
244
     *
245
     * @return $this
246
     */
247 14
    public function set($key, $value, $overwrite = true)
248
    {
249 14
        if ($overwrite || !$this->has($key)) {
250 14
            $this->items[$key] = $value;
251 14
        }
252
253 14
        return $this;
254
    }
255
256
    /**
257
     * Delete an item by key
258
     *
259
     * @param mixed $key
260
     *
261
     * @return $this
262
     */
263 3
    public function delete($key)
264
    {
265 3
        unset($this->items[$key]);
266
267 3
        return $this;
268
    }
269
270
    /**
271
     * Clear the collection of all its items.
272
     *
273
     * @return $this
274
     */
275 2
    public function clear()
276
    {
277 2
        $this->items = [];
278
279 2
        return $this;
280
    }
281
282
    /**
283
     * Determine if collection contains given value
284
     *
285
     * @param mixed|callable $val
286
     * @param mixed $key
287
     *
288
     * @return bool
289
     */
290 4
    public function contains($val, $key = null)
291
    {
292 4
        $index = 0;
293 4
        foreach ($this as $k => $v) {
294 4
            $matchkey = is_null($key) || $key === $k;
295 4
            if (is_callable($val)) {
296 1
                if ($val($v, $k, $index)) {
297 1
                    return $matchkey;
298
                }
299 1
            } else {
300 3
                if ($val === $v) {
301 3
                    return $matchkey;
302
                }
303
            }
304 4
            $index++;
305 4
        }
306 2
        return false;
307
    }
308
309
    /**
310
     * Fetch item from collection by key and remove it from collection
311
     *
312
     * @param mixed $key
313
     *
314
     * @return mixed
315
     */
316 1
    public function pull($key)
317
    {
318 1
        if ($this->has($key)) {
319 1
            $value = $this->get($key);
320 1
            $this->delete($key);
321 1
            return $value;
322
        }
323 1
    }
324
325
    /**
326
     * Join collection items using a delimiter
327
     *
328
     * @param string $delim
329
     *
330
     * @return string
331
     */
332 1
    public function join($delim = '')
333
    {
334 1
        return implode($delim, $this->items);
335
    }
336
337
    /**
338
     * Determine if collection has any items
339
     *
340
     * @return bool
341
     */
342 5
    public function isEmpty()
343
    {
344 5
        return $this->count() == 0;
345
    }
346
347
    /**
348
     * Get a collection of only this collection's values (without its keys)
349
     *
350
     * @return Collection
351
     */
352 1
    public function values()
353
    {
354 1
        return static::factory(array_values($this->items));
355
    }
356
357
    /**
358
     * Get a collection of only this collection's keys
359
     *
360
     * @return Collection
361
     */
362 1
    public function keys()
363
    {
364 1
        return static::factory(array_keys($this->items));
365
    }
366
367
    /**
368
     * Get a collection with order reversed
369
     *
370
     * @return Collection
371
     */
372 6
    public function reverse()
373
    {
374 6
        return static::factory(array_reverse($this->items));
375
    }
376
377
    /**
378
     * Get a collection with keys and values flipped
379
     *
380
     * @return Collection
381
     */
382 1
    public function flip()
383
    {
384 1
        $collection = static::factory();
385 1
        foreach ($this as $key => $val) {
386 1
            $collection->set($val, $key);
387 1
        }
388 1
        return $collection;
389
    }
390
391
    /**
392
     * Shuffle the order of this collection's values
393
     *
394
     * @return Collection
395
     */
396 1
    public function shuffle()
397
    {
398 1
        shuffle($this->items);
399 1
        return $this;
400
    }
401
402
    /**
403
     * Get a random value from the collection
404
     * 
405
     * @return mixed
406
     */
407 1
    public function random()
408
    {
409 1
        return $this->getValueAt(rand(1, $this->count()));
410
    }
411
412
    /**
413
     * Sort the collection (using values)
414
     *
415
     * @param callable $alg
416
     *
417
     * @return $this
418
     */
419 2
    public function sort(callable $alg = null)
420
    {
421 2
        if (is_null($alg)) {
422
            // case-sensitive string comparison is the default sorting mechanism
423 1
            $alg = 'strcmp';
424 1
        }
425 2
        uasort($this->items, $alg);
426
427 2
        return $this;
428
    }
429
430
    /**
431
     * Sort the collection (using keys)
432
     *
433
     * @param callable $alg
434
     *
435
     * @return $this
436
     */
437 2
    public function ksort(callable $alg = null)
438
    {
439 2
        if (is_null($alg)) {
440
            // case-sensitive string comparison is the default sorting mechanism
441 1
            $alg = 'strcmp';
442 1
        }
443 2
        uksort($this->items, $alg);
444
445 2
        return $this;
446
    }
447
448
    /**
449
     * Append items to collection without regard to keys
450
     *
451
     * @param array|Traversable $items
452
     *
453
     * @return $this
454
     */
455 2
    public function append($items)
456
    {
457 2
        if (!is_traversable($items)) {
458 1
            throw new RuntimeException("Invalid input type for " . __METHOD__ . ", must be array or Traversable");
459
        }
460
461 1
        foreach ($items as $val) {
462 1
            $this->add($val);
463 1
        }
464
465 1
        return $this;
466
    }
467
468
    /**
469
     * Return first item or first item where callback returns true
470
     *
471
     * @param callable|null $callback
472
     *
473
     * @return mixed|null
474
     */
475 8
    public function first(callable $callback = null)
476
    {
477 8
        $index = 0;
478 8
        foreach ($this as $key => $val) {
479 8
            if (is_null($callback) || $callback($val, $key, $index++)) {
480 8
                return $val;
481
            }
482 6
        }
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
     *
492
     * @return mixed|null
493
     */
494 3
    public function last(callable $callback = null)
495
    {
496 3
        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
     *
506
     * @return Collection
507
     */
508 3
    public function map(callable $callback)
509
    {
510 3
        $collection = static::factory();
511
512 3
        $index = 0;
513 3
        foreach ($this as $key => $val) {
514 3
            $collection->set($key, $callback($val, $key, $index++));
515 3
        }
516
517 3
        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
     *
527
     * @return Collection
528
     */
529 5
    public function combine($items)
530
    {
531 5
        if (!is_traversable($items)) {
532 1
            throw new RuntimeException("Invalid input type for " . __METHOD__ . ", must be array or Traversable");
533
        }
534
535 4
        $items = to_array($items);
536 4
        if (count($items) != count($this->items)) {
537 1
            throw new RuntimeException("Invalid input for " . __METHOD__ . ", number of items does not match");
538
        }
539
540 3
        return static::factory(array_combine($this->items, $items));
541
    }
542
543
    /**
544
     * Get a new collection with only distinct values
545
     *
546
     * @return Collection
547
     */
548 1
    public function distinct()
549
    {
550 1
        $collection = static::factory();
551 1
        foreach ($this as $key => $val) {
552 1
            if (!$collection->contains($val)) {
553 1
                $collection->set($key, $val);
554 1
            }
555 1
        }
556
557 1
        return $collection;
558
    }
559
560
    /**
561
     * Remove all duplicate values from collection in-place
562
     *
563
     * @return Collection
564
     */
565 1
    public function deduplicate()
566
    {
567 1
        $this->items = array_unique($this->items);
568
569 1
        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
     *
580
     * @return Collection
581
     */
582 2
    public function filter(callable $callback = null)
583
    {
584 2
        $collection = static::factory();
585 2
        $index = 0;
586 2
        foreach ($this as $key => $value) {
587 2
            if (is_null($callback)) {
588 1
                if ($value) {
589 1
                    $collection->set($key, $value);
590 1
                }
591 1
            } else {
592 1
                if ($callback($value, $key, $index++)) {
593 1
                    $collection->set($key, $value);
594 1
                }
595
            }
596 2
        }
597
598 2
        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
     *
610
     * @return null
611
     */
612 1
    public function fold(callable $callback, $initial = null)
613
    {
614 1
        $index = 0;
615 1
        $folded = $initial;
616 1
        foreach ($this as $key => $val) {
617 1
            $folded = $callback($folded, $val, $key, $index++);
618 1
        }
619
620 1
        return $folded;
621
    }
622
623
    /**
624
     * Return a merge of this collection and $items
625
     *
626
     * @param array|Traversable $items
627
     *
628
     * @return Collection
629
     */
630 3
    public function merge($items)
631
    {
632 3
        if (!is_traversable($items)) {
633 1
            throw new RuntimeException("Invalid input type for " . __METHOD__ . ", must be array or Traversable");
634
        }
635
636 2
        $collection = clone $this;
637 2
        foreach ($items as $key => $val) {
638 2
            $collection->set($key, $val);
639 2
        }
640
641 2
        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
     *
649
     * @param $items
650
     */
651 2
    public function union($items)
652
    {
653 2
        if (!is_traversable($items)) {
654 1
            throw new RuntimeException("Invalid input type for " . __METHOD__ . ", must be array or Traversable");
655
        }
656
657 1
        $collection = clone $this;
658 1
        foreach ($items as $key => $val) {
659 1
            $collection->set($key, $val, false);
660 1
        }
661
662 1
        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
     *
671
     * @return $this
672
     */
673 2
    public function each(callable $callback)
674
    {
675 2
        $index = 0;
676 2
        foreach ($this as $key => $val) {
677 2
            if ($callback($val, $key, $index++) === false) {
678 1
                break;
679
            }
680 2
        }
681
682 2
        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
     */
693 2
    public function assert(callable $callback, $expected = true)
694
    {
695 2
        $index = 0;
696 2
        foreach ($this as $key => $val) {
697 2
            if ($callback($val, $key, $index++) !== $expected) {
698 2
                return false;
699
            }
700 2
        }
701
702 2
        return true;
703
    }
704
705
    /**
706
     * Pipe collection through a callback
707
     *
708
     * @param callable $callback
709
     *
710
     * @return mixed
711
     */
712 1
    public function pipe(callable $callback)
713
    {
714 1
        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
     */
726 2
    public function chunk($size)
727
    {
728 2
        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 1
    public function split($count = 1)
739
    {
740 1
        return $this->chunk(ceil($this->count() / $count));
741
    }
742
743
    /**
744
     * Get a slice of this collection.
745
     *
746
     * @param int $offset
747
     * @param int|null $length
748
     *
749
     * @return Collection
750
     */
751 1
    public function slice($offset, $length = null)
752
    {
753 1
        return static::factory(array_slice($this->items, $offset, $length, true));
754
    }
755
756
    /**
757
     * Get collection with only differing items
758
     *
759
     * @param array|Traversable $items
760
     *
761
     * @return Collection
762
     */
763 1
    public function diff($items)
764
    {
765 1
        return static::factory(array_diff($this->items, to_array($items)));
766
    }
767
768
    /**
769
     * Get collection with only differing items (by key)
770
     *
771
     * @param array|Traversable $items
772
     *
773
     * @return Collection
774
     */
775 2
    public function kdiff($items)
776
    {
777 2
        return static::factory(array_diff_key($this->items, to_array($items)));
778
    }
779
780
    /**
781
     * Get collection with only intersecting items
782
     *
783
     * @param array|Traversable $items
784
     *
785
     * @return Collection
786
     */
787 1
    public function intersect($items)
788
    {
789 1
        return static::factory(array_intersect($this->items, to_array($items)));
790
    }
791
792
    /**
793
     * Get collection with only intersecting items (by key)
794
     *
795
     * @param array|Traversable $items
796
     *
797
     * @return Collection
798
     */
799 1
    public function kintersect($items)
800
    {
801 1
        return static::factory(array_intersect_key($this->items, to_array($items)));
802
    }
803
804
    /**
805
     * Remove last item in collection and return it
806
     *
807
     * @return mixed
808
     */
809 1
    public function pop()
810
    {
811 1
        return array_pop($this->items);
812
    }
813
814
    /**
815
     * Remove first item in collection and return it (and re-index if numerically indexed)
816
     *
817
     * @return mixed
818
     */
819 2
    public function shift()
820
    {
821 2
        return array_shift($this->items);
822
    }
823
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 1
    public function push($item)
834
    {
835 1
        return $this->add($item);
836
    }
837
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 5
    public function unshift($item)
846
    {
847 5
        array_unshift($this->items, $item);
848
849 5
        return $this;
850
    }
851
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 3
    public function pad($size, $value = null)
865
    {
866 3
        $collection = clone $this;
867 3
        while ($collection->count() < abs($size)) {
868 3
            if ($size > 0) {
869 3
                $collection->add($value);
870 3
            } else {
871 3
                $collection->unshift($value);
872
            }
873 3
        }
874
875 3
        return $collection;
876
    }
877
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 2
    public function partition(callable $callback)
890
    {
891 2
        $pass = static::factory();
892 2
        $fail = static::factory();
893
894 2
        $index = 0;
895 2
        foreach ($this as $key => $val) {
896 1
            if ($callback($val, $key, $index++)) {
897 1
                $pass->set($key, $val);
898 1
            } else {
899 1
                $fail->set($key, $val);
900
            }
901 2
        }
902
903 2
        return [$pass, $fail];
904
    }
905
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
     * @return Collection
916
     */
917 3
    public function getColumn($column)
918
    {
919 3
        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
     *
928
     * @return bool
929
     */
930 1
    public function isTabular()
931
    {
932 1
        $first = $this->first();
933 1
        return $this->assert(function($row) use ($first) {
934 1
            if (!is_traversable(($first)) || !is_traversable($row)) {
935 1
                return false;
936
            }
937 1
            return Collection::factory($row)
938 1
                ->kdiff($first)
939 1
                ->isEmpty();
940 1
        });
941
    }
942
943
    /** ++++                  ++++ **/
944
    /** ++ Interface Compliance ++ **/
945
    /** ++++                  ++++ **/
946
947
    /**
948
     * JSON serialize
949
     *
950
     * @return array
951
     */
952 1
    public function jsonSerialize()
953
    {
954 1
        return $this->toArray();
955
    }
956
957
    /** ++++                  ++++ **/
958
    /** ++ Array Access Methods ++ **/
959
    /** ++++                  ++++ **/
960
961
    /**
962
     * Does offset exist?
963
     *
964
     * @param mixed $offset
965
     *
966
     * @return bool
967
     */
968 1
    public function offsetExists($offset)
969
    {
970 1
        return $this->has($offset);
971
    }
972
973
    /**
974
     * Get item at offset
975
     *
976
     * @param mixed $offset
977
     *
978
     * @return mixed
979
     */
980 2
    public function offsetGet($offset)
981
    {
982 2
        if (!$this->has($offset)) {
983 1
            throw new RuntimeException("Unknown offset: {$offset}");
984
        }
985
986 1
        return $this->get($offset);
987
    }
988
989
    /**
990
     * Unset item at offset
991
     *
992
     * @param mixed $offset
993
     *
994
     * @return void
995
     */
996 1
    public function offsetUnset($offset)
997
    {
998 1
        $this->delete($offset);
999 1
    }
1000
1001
    /**
1002
     * Set item at offset
1003
     *
1004
     * @param mixed $offset
1005
     * @param mixed $value
1006
     *
1007
     * @return $this
1008
     */
1009 1
    public function offsetSet($offset, $value)
1010
    {
1011 1
        if (!isset($offset)) {
1012 1
            $this->add($value);
1013 1
        }
1014
1015 1
        $this->set($offset, $value);
1016 1
    }
1017
1018
    /** ++++                  ++++ **/
1019
    /** ++   Iterator Methods   ++ **/
1020
    /** ++++                  ++++ **/
1021
1022
    /**
1023
     * {@inheritDoc}
1024
     */
1025 38
    public function current()
1026
    {
1027 38
        return current($this->items);
1028
    }
1029
1030
    /**
1031
     * {@inheritDoc}
1032
     */
1033 38
    public function key()
1034
    {
1035 38
        return key($this->items);
1036
    }
1037
1038
    /**
1039
     * {@inheritDoc}
1040
     */
1041 37
    public function next()
1042
    {
1043 37
        return next($this->items);
1044
    }
1045
1046
    /**
1047
     * {@inheritDoc}
1048
     */
1049 100
    public function rewind()
1050
    {
1051 100
        reset($this->items);
1052 100
    }
1053
1054
    /**
1055
     * {@inheritDoc}
1056
     */
1057 39
    public function valid()
1058
    {
1059 39
        return $this->has(key($this->items));
1060
    }
1061
1062
    /** ++++                  ++++ **/
1063
    /** ++   Countable Method   ++ **/
1064
    /** ++++                  ++++ **/
1065
1066
    /**
1067
     * {@inheritDoc}
1068
     */
1069 11
    public function count()
1070
    {
1071 11
        return count($this->items);
1072
    }
1073
}