Passed
Branch refactor/164-removeoldcollecti... (61eb5d)
by Luke
02:41
created

AbstractCollection::hasPosition()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
1
<?php
2
/**
3
 * CSVelte: Slender, elegant CSV for PHP
4
 *
5
 * Inspired by Python's CSV module and Frictionless Data and the W3C's CSV
6
 * standardization efforts, CSVelte was written in an effort to take all the
7
 * suck out of working with CSV.
8
 *
9
 * @version   v${CSVELTE_DEV_VERSION}
10
 * @copyright Copyright (c) 2016 Luke Visinoni <[email protected]>
11
 * @author    Luke Visinoni <[email protected]>
12
 * @license   https://github.com/deni-zen/csvelte/blob/master/LICENSE The MIT License (MIT)
13
 */
14
namespace CSVelte\Collection;
15
16
use ArrayAccess;
17
use ArrayIterator;
18
use BadMethodCallException;
19
use Countable;
20
use InvalidArgumentException;
21
use Iterator;
22
use CSVelte\Contract\Collectable;
23
use function CSVelte\is_traversable;
24
25
use OutOfBoundsException;
26
27
/**
28
 * Class AbstractCollection.
29
 *
30
 * This is the abstract class that all other collection classes are based on.
31
 * Although it's possible to use a completely custom Collection class by simply
32
 * implementing the "Collectable" interface, extending this class gives you a
33
 * whole slew of convenient methods for free.
34
 *
35
 * @package CSVelte\Collection
36
 * @since v0.2.2
37
 * @author Luke Visinoni <[email protected]>
38
 * @copyright Copyright (c) 2016 Luke Visinoni <[email protected]>
39
 * @todo Implement Serializable, other Interfaces
40
 * @todo Implement __toString() in such a way that by deault it
41
 *     will return a CSV-formatted string but you can configure
42
 *     it to return other formats if you want
43
 */
44
abstract class AbstractCollection implements
45
    ArrayAccess,
46
    Countable,
47
    Iterator
48
    /*Collectable*/
49
{
50
    /**
51
     * @var array The collection of data this object represents
52
     */
53
    protected $data = [];
54
55
    /**
56
     * @var boolean True unless we have advanced past the end of the data array
57
     */
58
    protected $isValid = true;
59
60
    /**
61
     * AbstractCollection constructor.
62
     *
63
     * @param mixed $data The data to wrap
64
     */
65
    public function __construct($data = [])
66
    {
67
        $this->setData($data);
68
    }
69
70
    /**
71
     * Invoke object.
72
     *
73
     * Magic "invoke" method. Called when object is invoked as if it were a function.
74
     *
75
     * @param mixed $val The value (depends on other param value)
76
     * @param mixed $index The index (depends on other param value)
77
     * @return mixed (Depends on parameter values)
78
     */
79
    public function __invoke($val = null, $index = null)
80
    {
81
        if (is_null($val)) {
82
            if (is_null($index)) {
83
                return $this->toArray();
84
            } else {
85
                return $this->delete($index);
86
            }
87
        } else {
88
            if (is_null($index)) {
89
                // @todo cast $val to array?
90
                return $this->merge($val);
91
            } else {
92
                return $this->set($val, $index);
93
            }
94
        }
95
    }
96
97
    /** BEGIN ArrayAccess methods */
98
99
    /**
100
     * Whether a offset exists.
101
     *
102
     * @param mixed $offset An offset to check for.
103
     * @return boolean true on success or false on failure.
104
     * @link http://php.net/manual/en/arrayaccess.offsetexists.php
105
     */
106
    public function offsetExists($offset)
107
    {
108
        return $this->has($offset);
109
    }
110
111
    /**
112
     * Offset to retrieve.
113
     *
114
     * @param mixed $offset The offset to retrieve.
115
     * @return mixed Can return all value types.
116
     * @link http://php.net/manual/en/arrayaccess.offsetget.php
117
     */
118
    public function offsetGet($offset)
119
    {
120
        return $this->get($offset, null, true);
121
    }
122
123
    /**
124
     * Offset to set.
125
     *
126
     * @param mixed $offset The offset to assign the value to.
127
     * @param mixed $value The value to set.
128
     * @link http://php.net/manual/en/arrayaccess.offsetset.php
129
     */
130
    public function offsetSet($offset, $value)
131
    {
132
        $this->set($offset, $value);
133
    }
134
135
    /**
136
     * Offset to unset.
137
     *
138
     * @param mixed $offset The offset to unset.
139
     * @link http://php.net/manual/en/arrayaccess.offsetunset.php
140
     */
141
    public function offsetUnset($offset)
142
    {
143
        $this->delete($offset);
144
    }
145
146
    /** END ArrayAccess methods */
147
148
    /** BEGIN Countable methods */
149
150
    public function count()
151
    {
152
        return count($this->data);
153
    }
154
155
    /** END Countable methods */
156
157
    /** BEGIN Iterator methods */
158
159
    /**
160
     * Return the current element.
161
     *
162
     * Returns the current element in the collection. The internal array pointer
163
     * of the data array wrapped by the collection should not be advanced by this
164
     * method. No side effects. Return current element only.
165
     *
166
     * @return mixed
167
     */
168
    public function current ()
169
    {
170
        return current($this->data);
171
    }
172
173
    /**
174
     * Return the current key.
175
     *
176
     * Returns the current key in the collection. No side effects.
177
     *
178
     * @return mixed
179
     */
180
    public function key ()
181
    {
182
        return key($this->data);
183
    }
184
185
    /**
186
     * Advance the internal pointer forward.
187
     *
188
     * Although this method will return the current value after advancing the
189
     * pointer, you should not expect it to. The interface does not require it
190
     * to return any value at all.
191
     *
192
     * @return mixed
193
     */
194
    public function next ()
195
    {
196
        $next = next($this->data);
197
        $key = key($this->data);
198
        if (isset($key)) {
199
            return $next;
200
        }
201
        $this->isValid = false;
202
    }
203
204
    /**
205
     * Rewind the internal pointer.
206
     *
207
     * Return the internal pointer to the first element in the collection. Again,
208
     * this method is not required to return anything by its interface, so you
209
     * should not count on a return value.
210
     *
211
     * @return mixed
212
     */
213
    public function rewind ()
214
    {
215
        $this->isValid = true;
216
        return reset($this->data);
217
    }
218
219
    /**
220
     * Is internal pointer in a valid position?
221
     *
222
     * If the internal pointer is advanced beyond the end of the collection, this method will return false.
223
     *
224
     * @return bool True if internal pointer isn't past the end
225
     */
226
    public function valid ()
227
    {
228
        return $this->isValid;
229
    }
230
231
    /** END Iterator methods */
232
233
    /**
234
     * Set collection data.
235
     *
236
     * Sets the collection data.
237
     *
238
     * @param array $data The data to wrap
239
     * @return $this
240
     */
241
    protected function setData($data)
242
    {
243
        if (is_null($data)) {
244
            $data = [];
245
        }
246
        $this->assertCorrectInputDataType($data);
247
        $data = $this->prepareData($data);
248
        foreach ($data as $index => $value) {
249
            $this->set($index, $value);
250
        }
251
252
        return $this;
253
    }
254
255
    public function sort($alg = null)
256
    {
257
        if (is_null($alg)) {
258
            $alg = 'natcasesort';
259
        }
260
        $alg($this->data);
261
        return static::factory($this->data);
262
    }
263
264
    /**
265
     * Does this collection have a value at given index?
266
     *
267
     * @param mixed $index The index to check
268
     * @return bool
269
     */
270
    public function has($index)
271
    {
272
        return array_key_exists($index, $this->data);
273
    }
274
275
    /**
276
     * Get value at a given index.
277
     *
278
     * Accessor for this collection of data. You can optionally provide a default
279
     * value for when the collection doesn't find a value at the given index. It can
280
     * also optionally throw an OutOfBoundsException if no value is found.
281
     *
282
     * @param mixed $index The index of the data you want to get
283
     * @param mixed $default The default value to return if none available
284
     * @param bool $throw True if you want an exception to be thrown if no data found at $index
285
     * @throws OutOfBoundsException If $throw is true and $index isn't found
286
     * @return mixed The data found at $index or failing that, the $default
287
     * @todo Use OffsetGet, OffsetSet, etc. internally here and on set, has, delete, etc.
288
     */
289
    public function get($index, $default = null, $throw = false)
290
    {
291
        if (isset($this->data[$index])) {
292
            return $this->data[$index];
293
        }
294
        if ($throw) {
295
            throw new OutOfBoundsException(__CLASS__ . " could not find value at index " . $index);
296
        }
297
        return $default;
298
    }
299
300
    /**
301
     * Set a value at a given index.
302
     *
303
     * Setter for this collection. Allows setting a value at a given index.
304
     *
305
     * @param mixed $index The index to set a value at
306
     * @param mixed $val The value to set $index to
307
     *
308
     * @return $this
309
     */
310
    public function set($index, $val)
311
    {
312
        $this->data[$index] = $val;
313
314
        return $this;
315
    }
316
317
    /**
318
     * Unset a value at a given index.
319
     *
320
     * Unset (delete) value at the given index.
321
     *
322
     * @param mixed $index The index to unset
323
     * @param bool $throw True if you want an exception to be thrown if no data found at $index
324
     *
325
     * @throws OutOfBoundsException If $throw is true and $index isn't found
326
     *
327
     * @return $this
328
     */
329
    public function delete($index, $throw = false)
330
    {
331
        if (isset($this->data[$index])) {
332
            unset($this->data[$index]);
333
        } else {
334
            if ($throw) {
335
                throw new OutOfBoundsException('No value found at given index: ' . $index);
336
            }
337
        }
338
339
        return $this;
340
    }
341
342
    /**
343
     * Does this collection have a value at specified numerical position?
344
     *
345
     * Returns true if collection contains a value (any value including null)
346
     * at specified numerical position.
347
     *
348
     * @param int $pos The position
349
     *
350
     * @return bool
351
     *
352
     * @todo I feel like it would make more sense  to have this start at position 1 rather than 0
353
     */
354
    public function hasPosition($pos)
355
    {
356
        try {
357
            $this->getKeyAtPosition($pos);
358
            return true;
359
        } catch (OutOfBoundsException $e) {
360
            return false;
361
        }
362
    }
363
364
    /**
365
     * Return value at specified numerical position.
366
     *
367
     * @param int $pos The numerical position
368
     *
369
     * @throws OutOfBoundsException if no pair at position
370
     *
371
     * @return mixed
372
     */
373
    public function getValueAtPosition($pos)
374
    {
375
        return $this->data[$this->getKeyAtPosition($pos)];
376
    }
377
378
    /**
379
     * Return key at specified numerical position.
380
     *
381
     * @param int $pos The numerical position
382
     *
383
     * @throws OutOfBoundsException if no pair at position
384
     *
385
     * @return mixed
386
     */
387
    public function getKeyAtPosition($pos)
388
    {
389
        $i = 0;
390
        foreach ($this as $key => $val) {
391
            if ($i === $pos) {
392
                return $key;
393
            }
394
            $i++;
395
        }
396
        throw new OutOfBoundsException("No element at expected position: $pos");
397
    }
398
399
    /**
400
     * @param int $pos The numerical position
401
     *
402
     * @throws OutOfBoundsException if no pair at position
403
     *
404
     * @return array
405
     */
406
    public function getPairAtPosition($pos)
407
    {
408
        $pairs = $this->pairs();
409
        return $pairs[$this->getKeyAtPosition($pos)];
410
    }
411
412
    /**
413
     * Get collection as array.
414
     *
415
     * @return array This collection as an array
416
     */
417
    public function toArray()
418
    {
419
        $arr = [];
420
        foreach ($this as $index => $value) {
421
            if (is_object($value) && method_exists($value, 'toArray')) {
422
                $value = $value->toArray();
423
            }
424
            $arr[$index] = $value;
425
        }
426
        return $arr;
427
    }
428
429
    /**
430
     * Get this collection's keys as a collection.
431
     *
432
     * @return AbstractCollection Containing this collection's keys
433
     */
434
    public function keys()
435
    {
436
        return static::factory(array_keys($this->data));
437
    }
438
439
    /**
440
     * Get this collection's values as a collection.
441
     *
442
     * This method returns this collection's values but completely re-indexed (numerically).
443
     *
444
     * @return AbstractCollection Containing this collection's values
445
     */
446
    public function values()
447
    {
448
        return static::factory(array_values($this->data));
449
    }
450
451
    /**
452
     * Merge data into collection.
453
     *
454
     * Merges input data into this collection. Input can be an array or another collection. Returns a NEW collection object.
455
     *
456
     * @param Traversable|array $data The data to merge with this collection
457
     * @return AbstractCollection A new collection with $data merged in
458
     */
459
    public function merge($data)
460
    {
461
        $this->assertCorrectInputDataType($data);
462
        $coll = static::factory($this->data);
463
        foreach ($data as $index => $value) {
464
            $coll->set($index, $value);
465
        }
466
        return $coll;
467
    }
468
469
    /**
470
     * Determine if this collection contains a value.
471
     *
472
     * Allows you to pass in a value or a callback function and optionally an index,
473
     * and tells you whether or not this collection contains that value. If the $index param is specified, only that index will be looked under.
474
     *
475
     * @param mixed|callable $value The value to check for
476
     * @param mixed $index The (optional) index to look under
477
     * @return boolean True if this collection contains $value
478
     * @todo Maybe add $identical param for identical comparison (===)
479
     * @todo Allow negative offset for second param
480
     */
481
    public function contains($value, $index = null)
482
    {
483
        return (bool) $this->first(function($val, $key) use ($value, $index) {
484
            if (is_callable($value)) {
485
                $found = $value($val, $key);
486
            } else {
487
                $found = ($value == $val);
488
            }
489
            if ($found) {
490
                if (is_null($index)) {
491
                    return true;
492
                }
493
                if (is_array($index)) {
494
                    return in_array($key, $index);
495
                }
496
                return $key == $index;
497
            }
498
            return false;
499
        });
500
    }
501
502
503
    /**
504
     * Get duplicate values.
505
     *
506
     * Returns a collection of arrays where the key is the duplicate value
507
     * and the value is an array of keys from the original collection.
508
     *
509
     * @return AbstractCollection A new collection with duplicate values.
510
     */
511
    public function duplicates()
512
    {
513
        $dups = [];
514
        $this->walk(function($val, $key) use (&$dups) {
515
            $dups[$val][] = $key;
516
        });
517
        return static::factory($dups)->filter(function($val) {
518
            return (count($val) > 1);
519
        });
520
    }
521
522
    /**
523
     * Pop an element off the end of this collection.
524
     *
525
     * @return mixed The last item in this collectio n
526
     */
527
    public function pop()
528
    {
529
        return array_pop($this->data);
530
    }
531
532
    /**
533
     * Shift an element off the beginning of this collection.
534
     *
535
     * @return mixed The first item in this collection
536
     */
537
    public function shift()
538
    {
539
        return array_shift($this->data);
540
    }
541
542
    /**
543
     * Push a item(s) onto the end of this collection.
544
     *
545
     * Returns a new collection with $items added.
546
     *
547
     * @param array $items Any number of arguments will be pushed onto the
548
     * @return mixed The first item in this collection
549
     */
550
    public function push(...$items)
551
    {
552
        array_push($this->data, ...$items);
553
        return static::factory($this->data);
554
    }
555
556
    /**
557
     * Unshift item(s) onto the beginning of this collection.
558
     *
559
     * Returns a new collection with $items added.
560
     *
561
     * @return mixed The first item in this collection
562
     */
563
    public function unshift(...$items)
564
    {
565
        array_unshift($this->data, ...$items);
566
        return static::factory($this->data);
567
    }
568
569
    /**
570
     * Pad this collection to a certain size.
571
     *
572
     * Returns a new collection, padded to the given size, with the given value.
573
     *
574
     * @param int $size The number of items that should be in the collection
575
     * @param null $with The value to pad the collection with
576
     * @return AbstractCollection A new collection padded to specified length
577
     */
578
    public function pad($size, $with = null)
579
    {
580
        return static::factory(array_pad($this->data, $size, $with));
581
    }
582
583
    /**
584
     * Apply a callback to each item in collection.
585
     *
586
     * Applies a callback to each item in collection and returns a new collection
587
     * containing each iteration's return value.
588
     *
589
     * @param callable $callback The callback to apply
590
     * @return AbstractCollection A new collection with callback return values
591
     */
592
    public function map(callable $callback)
593
    {
594
        return static::factory(array_map($callback, $this->data));
595
    }
596
597
    /**
598
     * Apply a callback to each item in collection.
599
     *
600
     * Applies a callback to each item in collection. The callback should return
601
     * false to filter any item from the collection.
602
     *
603
     * @param callable $callback The callback function
604
     * @param null $extraContext Extra context to pass as third param in callback
605
     * @return $this
606
     * @see php.net array_walk
607
     */
608
    public function walk(callable $callback, $extraContext = null)
609
    {
610
        array_walk($this->data, $callback, $extraContext);
611
        return $this;
612
    }
613
614
    /**
615
     * Get each key/value as an array pair
616
     *
617
     * Returns a collection of arrays where each item in the collection is [key,value]
618
     *
619
     * @return AbstractCollection
620
     */
621
    public function pairs()
622
    {
623
        return static::factory(array_map(
624
            function ($key, $val) {
625
                return [$key, $val];
626
            },
627
            array_keys($this->data),
628
            array_values($this->data)
629
        ));
630
    }
631
632
    /**
633
     * Reduce the collection to a single value.
634
     *
635
     * Using a callback function, this method will reduce this collection to a
636
     * single value.
637
     *
638
     * @param callable $callback The callback function used to reduce
639
     * @param null $initial The initial carry value
640
     * @return mixed The single value produced by reduction algorithm
641
     */
642
    public function reduce(callable $callback, $initial = null)
643
    {
644
        return array_reduce($this->data, $callback, $initial);
645
    }
646
647
    /**
648
     * Filter the collection.
649
     *
650
     * Using a callback function, this method will filter out unwanted values, returning
651
     * a new collection containing only the values that weren't filtered.
652
     *
653
     * @param callable $callback The callback function used to filter
654
     * @param int $flag array_filter flag(s) (ARRAY_FILTER_USE_KEY or ARRAY_FILTER_USE_BOTH)
655
     * @return AbstractCollection A new collection with only values that weren't filtered
656
     * @see php.net array_filter
657
     */
658
    public function filter(callable $callback, $flag = ARRAY_FILTER_USE_BOTH)
659
    {
660
        return static::factory(array_filter($this->data, $callback, $flag));
661
    }
662
663
    /**
664
     * Return the first item that meets given criteria.
665
     *
666
     * Using a callback function, this method will return the first item in the collection
667
     * that causes the callback function to return true.
668
     *
669
     * @param callable $callback The callback function
670
     * @return null|mixed The first item in the collection that causes callback to return true
671
     */
672
    public function first(callable $callback)
673
    {
674
        foreach ($this->data as $index => $value) {
675
            if ($callback($value, $index)) {
676
                return $value;
677
            }
678
        }
679
        return null;
680
    }
681
682
    /**
683
     * Return the last item that meets given criteria.
684
     *
685
     * Using a callback function, this method will return the last item in the collection
686
     * that causes the callback function to return true.
687
     *
688
     * @param callable $callback The callback function
689
     * @return null|mixed The last item in the collection that causes callback to return true
690
     */
691
    public function last(callable $callback)
692
    {
693
        $reverse = $this->reverse(true);
694
        return $reverse->first($callback);
695
    }
696
697
    /**
698
     * Returns collection in reverse order.
699
     *
700
     * @param null $preserveKeys True if you want to preserve collection's keys
701
     * @return AbstractCollection This collection in reverse order.
702
     */
703
    public function reverse($preserveKeys = null)
704
    {
705
        return static::factory(array_reverse($this->data, $preserveKeys));
706
    }
707
708
    /**
709
     * Get unique items.
710
     *
711
     * Returns a collection of all the unique items in this collection.
712
     *
713
     * @return AbstractCollection This collection with duplicate items removed
714
     */
715
    public function unique()
716
    {
717
        return static::factory(array_unique($this->data));
718
    }
719
720
    /**
721
     * Convert collection to string
722
     *
723
     * @return string A string representation of this collection
724
     * @todo Eventually I would like to add a $delim property so that
725
     *     I can easily join collection items together with a particular
726
     * character (or set of characters). I would then add a few methods
727
     * to change the delim property. It would default to a comma.
728
     */
729
    public function __toString()
730
    {
731
        return $this->join();
732
    }
733
734
    /**
735
     * Join collection together using a delimiter.
736
     *
737
     * @param string $delimiter The delimiter string/char
738
     *
739
     * @return string
740
     */
741
    public function join($delimiter = '')
742
    {
743
        return implode($delimiter, $this->data);
744
    }
745
746
    /**
747
     * Counts how many times each value occurs in a collection.
748
     *
749
     * Returns a new collection with values as keys and how many times that
750
     * value appears in the collection. Works best with scalar values but will
751
     * attempt to work on collections of objects as well.
752
     *
753
     * @return AbstractCollection
754
     *
755
     * @todo Right now, collections of arrays or objects are supported via the
756
     * __toString() or spl_object_hash()
757
     * @todo NumericCollection::counts() does the same thing...
758
     */
759
    public function frequency()
760
    {
761
        $frequency = [];
762
        foreach ($this as $key => $val) {
763
            if (!is_scalar($val)) {
764
                if (!is_object($val)) {
765
                    $val = new ArrayIterator($val);
766
                }
767
768
                if (method_exists($val, '__toString')) {
769
                    $val = (string)$val;
770
                } else {
771
                    $val = spl_object_hash($val);
772
                }
773
            }
774
            if (!isset($frequency[$val])) {
775
                $frequency[$val] = 0;
776
            }
777
            $frequency[$val]++;
778
        }
779
780
        return static::factory($frequency);
781
    }
782
783
    /**
784
     * Collection factory method.
785
     *
786
     * This method will analyze input data and determine the most appropriate Collection
787
     * class to use. It will then instantiate said Collection class with the given
788
     * data and return it.
789
     *
790
     * @param mixed $data The data to wrap
791
     * @return AbstractCollection A collection containing $data
792
     */
793
    public static function factory($data = null)
794
    {
795
        if (static::isTabular($data)) {
796
            $class = TabularCollection::class;
797
        } elseif (static::isMultiDimensional($data)) {
798
            $class = MultiCollection::class;
799
        } elseif (static::isAllNumeric($data)) {
800
            $class = NumericCollection::class;
801
        } elseif (static::isCharacterSet($data)) {
802
            $class = CharCollection::class;
803
        } else {
804
            $class = Collection::class;
805
        }
806
        return new $class($data);
807
    }
808
809
    /**
810
     * Assert input data is of the correct structure
811
     *
812
     * @param mixed $data Data to check
813
     * @throws InvalidArgumentException If invalid data structure
814
     */
815
    protected function assertCorrectInputDataType($data)
816
    {
817
        if (!$this->isConsistentDataStructure($data)) {
818
            throw new InvalidArgumentException(__CLASS__ . ' expected traversable data, got: ' . gettype ($data));
819
        }
820
    }
821
822
    /**
823
     * Convert input data to an array.
824
     *
825
     * Convert the input data to an array that can be worked with by a collection.
826
     *
827
     * @param mixed $data The input data
828
     * @return array
829
     */
830
    protected function prepareData($data)
831
    {
832
        return $data;
833
    }
834
835
    /**
836
     * Is input data tabular?
837
     *
838
     * Returns true if input data is tabular in nature. This means that it is a
839
     * two-dimensional array with the same keys (columns) for each element (row).
840
     *
841
     * @param mixed $data The data structure to check
842
     * @return boolean True if data structure is tabular
843
     */
844
    public static function isTabular($data)
845
    {
846
        if (!is_traversable($data)) {
847
            return false;
848
        }
849
        foreach ($data as $row) {
850
            if (!is_traversable($row)) {
851
                return false;
852
            }
853
            $columns = array_keys($row);
854
            if (!isset($cmp_columns)) {
855
                $cmp_columns = $columns;
856
            } else {
857
                if ($cmp_columns != $columns) {
858
                    return false;
859
                }
860
            }
861
            // if row contains an array it isn't tabular
862
            if (array_reduce($row, function($carry, $item){
863
                return is_array($item) && $carry;
864
            }, true)) {
865
                return false;
866
            }
867
        }
868
        return true;
869
    }
870
871
    /**
872
     * Check data for multiple dimensions.
873
     *
874
     * This method is to determine whether a data structure is multi-dimensional.
875
     * That is to say, it is a traversable structure that contains at least one
876
     * traversable structure.
877
     *
878
     * @param mixed $data The input data
879
     * @return bool
880
     */
881
    public static function isMultiDimensional($data)
882
    {
883
        if (!is_traversable($data)) {
884
            return false;
885
        }
886
        foreach ($data as $elem) {
887
            if (is_traversable($elem)) {
888
                return true;
889
            }
890
        }
891
        return false;
892
    }
893
894
    /**
895
     * Determine if structure contains all numeric values.
896
     *
897
     * @param mixed $data The input data
898
     * @return bool
899
     */
900
    public static function isAllNumeric($data)
901
    {
902
        if (!is_traversable($data)) {
903
            return false;
904
        }
905
        foreach ($data as $val) {
906
            if (!is_numeric($val)) {
907
                return false;
908
            }
909
        }
910
        return true;
911
    }
912
913
    /**
914
     * Is data a string of characters?
915
     *
916
     * Just checks to see if input is a string of characters or a string
917
     * of digits.
918
     *
919
     * @param mixed $data Data to check
920
     * @return bool
921
     */
922
    public static function isCharacterSet($data)
923
    {
924
        return (
925
            is_string($data) ||
926
            is_numeric($data)
927
        );
928
    }
929
930
    /**
931
     * Determine whether data is consistent with a given collection type.
932
     *
933
     * This method is used to determine whether input data is consistent with a
934
     * given collection type. For instance, CharCollection requires a string.
935
     * NumericCollection requires an array or traversable set of numeric data.
936
     * TabularCollection requires a two-dimensional data structure where all the
937
     * keys are the same in every row.
938
     *
939
     * @param mixed $data Data structure to check for consistency
940
     * @return boolean
941
     */
942
    abstract protected function isConsistentDataStructure($data);
943
944
}