Passed
Push — travis-php74 ( e33f92...3a6a6e )
by Sam
07:33
created

ArrayList::setDataClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\ORM;
4
5
use ArrayIterator;
6
use InvalidArgumentException;
7
use LogicException;
8
use SilverStripe\Dev\Debug;
9
use SilverStripe\Dev\Deprecation;
10
use SilverStripe\View\ArrayData;
11
use SilverStripe\View\ViewableData;
12
13
/**
14
 * A list object that wraps around an array of objects or arrays.
15
 *
16
 * Note that (like DataLists), the implementations of the methods from SS_Filterable, SS_Sortable and
17
 * SS_Limitable return a new instance of ArrayList, rather than modifying the existing instance.
18
 *
19
 * For easy reference, methods that operate in this way are:
20
 *
21
 *   - limit
22
 *   - reverse
23
 *   - sort
24
 *   - filter
25
 *   - exclude
26
 */
27
class ArrayList extends ViewableData implements SS_List, Filterable, Sortable, Limitable
28
{
29
30
    /**
31
     * Holds the items in the list
32
     *
33
     * @var array
34
     */
35
    protected $items = [];
36
37
    /**
38
     *
39
     * @param array $items - an initial array to fill this object with
40
     */
41
    public function __construct(array $items = [])
42
    {
43
        $this->items = array_values($items);
44
        parent::__construct();
45
    }
46
47
    /**
48
     * Underlying type class for this list
49
     *
50
     * @var string
51
     */
52
    protected $dataClass = null;
53
54
    /**
55
     * Return the class of items in this list, by looking at the first item inside it.
56
     *
57
     * @return string
58
     */
59
    public function dataClass()
60
    {
61
        if ($this->dataClass) {
62
            return $this->dataClass;
63
        }
64
        if (count($this->items) > 0) {
65
            return get_class($this->items[0]);
66
        }
67
        return null;
68
    }
69
70
    /**
71
     * Hint this list to a specific type
72
     *
73
     * @param string $class
74
     * @return $this
75
     */
76
    public function setDataClass($class)
77
    {
78
        $this->dataClass = $class;
79
        return $this;
80
    }
81
82
    /**
83
     * Return the number of items in this list
84
     *
85
     * @return int
86
     */
87
    public function count()
88
    {
89
        return count($this->items);
90
    }
91
92
    /**
93
     * Returns true if this list has items
94
     *
95
     * @return bool
96
     */
97
    public function exists()
98
    {
99
        return !empty($this->items);
100
    }
101
102
    /**
103
     * Returns an Iterator for this ArrayList.
104
     * This function allows you to use ArrayList in foreach loops
105
     *
106
     * @return ArrayIterator
107
     */
108
    public function getIterator()
109
    {
110
        $items = array_map(
111
            function ($item) {
112
                return is_array($item) ? new ArrayData($item) : $item;
113
            },
114
            $this->items
115
        );
116
        return new ArrayIterator($items);
117
    }
118
119
    /**
120
     * Return an array of the actual items that this ArrayList contains.
121
     *
122
     * @return array
123
     */
124
    public function toArray()
125
    {
126
        return $this->items;
127
    }
128
129
    /**
130
     * Walks the list using the specified callback
131
     *
132
     * @param callable $callback
133
     * @return $this
134
     */
135
    public function each($callback)
136
    {
137
        foreach ($this as $item) {
138
            $callback($item);
139
        }
140
        return $this;
141
    }
142
143
    public function debug()
144
    {
145
        $val = "<h2>" . static::class . "</h2><ul>";
146
        foreach ($this->toNestedArray() as $item) {
147
            $val .= "<li style=\"list-style-type: disc; margin-left: 20px\">" . Debug::text($item) . "</li>";
148
        }
149
        $val .= "</ul>";
150
        return $val;
151
    }
152
153
    /**
154
     * Return this list as an array and every object it as an sub array as well
155
     *
156
     * @return array
157
     */
158
    public function toNestedArray()
159
    {
160
        $result = [];
161
162
        foreach ($this->items as $item) {
163
            if (is_object($item)) {
164
                if (method_exists($item, 'toMap')) {
165
                    $result[] = $item->toMap();
166
                } else {
167
                    $result[] = (array) $item;
168
                }
169
            } else {
170
                $result[] = $item;
171
            }
172
        }
173
174
        return $result;
175
    }
176
177
    /**
178
     * Get a sub-range of this dataobjectset as an array
179
     *
180
     * @param int $length
181
     * @param int $offset
182
     * @return static
183
     */
184
    public function limit($length, $offset = 0)
185
    {
186
        // Type checking: designed for consistency with DataList::limit()
187
        if (!is_numeric($length) || !is_numeric($offset)) {
0 ignored issues
show
introduced by
The condition is_numeric($offset) is always true.
Loading history...
introduced by
The condition is_numeric($length) is always true.
Loading history...
188
            Deprecation::notice(
189
                '4.3',
190
                'Arguments to ArrayList::limit() should be numeric'
191
            );
192
        }
193
194
        if ($length < 0 || $offset < 0) {
195
            Deprecation::notice(
196
                '4.3',
197
                'Arguments to ArrayList::limit() should be positive'
198
            );
199
        }
200
201
        if (!$length) {
202
            if ($length === 0) {
203
                Deprecation::notice(
204
                    '4.3',
205
                    "limit(0) is deprecated in SS4. In SS5 a limit of 0 will instead return no records."
206
                );
207
            }
208
209
            $length = count($this->items);
210
        }
211
212
        $list = clone $this;
213
        $list->items = array_slice($this->items, $offset, $length);
214
215
        return $list;
216
    }
217
218
    /**
219
     * Add this $item into this list
220
     *
221
     * @param mixed $item
222
     */
223
    public function add($item)
224
    {
225
        $this->push($item);
226
    }
227
228
    /**
229
     * Remove this item from this list
230
     *
231
     * @param mixed $item
232
     */
233
    public function remove($item)
234
    {
235
        $renumberKeys = false;
236
        foreach ($this->items as $key => $value) {
237
            if ($item === $value) {
238
                $renumberKeys = true;
239
                unset($this->items[$key]);
240
            }
241
        }
242
        if ($renumberKeys) {
243
            $this->items = array_values($this->items);
244
        }
245
    }
246
247
    /**
248
     * Replaces an item in this list with another item.
249
     *
250
     * @param array|object $item
251
     * @param array|object $with
252
     * @return void;
253
     */
254
    public function replace($item, $with)
255
    {
256
        foreach ($this->items as $key => $candidate) {
257
            if ($candidate === $item) {
258
                $this->items[$key] = $with;
259
                return;
260
            }
261
        }
262
    }
263
264
    /**
265
     * Merges with another array or list by pushing all the items in it onto the
266
     * end of this list.
267
     *
268
     * @param array|object $with
269
     */
270
    public function merge($with)
271
    {
272
        foreach ($with as $item) {
273
            $this->push($item);
274
        }
275
    }
276
277
    /**
278
     * Removes items from this list which have a duplicate value for a certain
279
     * field. This is especially useful when combining lists.
280
     *
281
     * @param string $field
282
     * @return $this
283
     */
284
    public function removeDuplicates($field = 'ID')
285
    {
286
        $seen = [];
287
        $renumberKeys = false;
288
289
        foreach ($this->items as $key => $item) {
290
            $value = $this->extractValue($item, $field);
291
292
            if (array_key_exists($value, $seen)) {
293
                $renumberKeys = true;
294
                unset($this->items[$key]);
295
            }
296
297
            $seen[$value] = true;
298
        }
299
300
        if ($renumberKeys) {
301
            $this->items = array_values($this->items);
302
        }
303
304
        return $this;
305
    }
306
307
    /**
308
     * Pushes an item onto the end of this list.
309
     *
310
     * @param array|object $item
311
     */
312
    public function push($item)
313
    {
314
        $this->items[] = $item;
315
    }
316
317
    /**
318
     * Pops the last element off the end of the list and returns it.
319
     *
320
     * @return array|object
321
     */
322
    public function pop()
323
    {
324
        return array_pop($this->items);
325
    }
326
327
    /**
328
     * Add an item onto the beginning of the list.
329
     *
330
     * @param array|object $item
331
     */
332
    public function unshift($item)
333
    {
334
        array_unshift($this->items, $item);
335
    }
336
337
    /**
338
     * Shifts the item off the beginning of the list and returns it.
339
     *
340
     * @return array|object
341
     */
342
    public function shift()
343
    {
344
        return array_shift($this->items);
345
    }
346
347
    /**
348
     * Returns the first item in the list
349
     *
350
     * @return mixed
351
     */
352
    public function first()
353
    {
354
        if (empty($this->items)) {
355
            return null;
356
        }
357
358
        return reset($this->items);
359
    }
360
361
    /**
362
     * Returns the last item in the list
363
     *
364
     * @return mixed
365
     */
366
    public function last()
367
    {
368
        if (empty($this->items)) {
369
            return null;
370
        }
371
372
        return end($this->items);
373
    }
374
375
    /**
376
     * Returns a map of this list
377
     *
378
     * @param string $keyfield The 'key' field of the result array
379
     * @param string $titlefield The value field of the result array
380
     * @return Map
381
     */
382
    public function map($keyfield = 'ID', $titlefield = 'Title')
383
    {
384
        $list = clone $this;
385
        return new Map($list, $keyfield, $titlefield);
386
    }
387
388
    /**
389
     * Find the first item of this list where the given key = value
390
     *
391
     * @param string $key
392
     * @param string $value
393
     * @return mixed
394
     */
395
    public function find($key, $value)
396
    {
397
        foreach ($this->items as $item) {
398
            if ($this->extractValue($item, $key) == $value) {
399
                return $item;
400
            }
401
        }
402
        return null;
403
    }
404
405
    /**
406
     * Returns an array of a single field value for all items in the list.
407
     *
408
     * @param string $colName
409
     * @return array
410
     */
411
    public function column($colName = 'ID')
412
    {
413
        $result = [];
414
415
        foreach ($this->items as $item) {
416
            $result[] = $this->extractValue($item, $colName);
417
        }
418
419
        return $result;
420
    }
421
422
    /**
423
     * Returns a unique array of a single field value for all the items in the list
424
     *
425
     * @param string $colName
426
     * @return array
427
     */
428
    public function columnUnique($colName = 'ID')
429
    {
430
        return array_unique($this->column($colName));
431
    }
432
433
    /**
434
     * You can always sort a ArrayList
435
     *
436
     * @param string $by
437
     * @return bool
438
     */
439
    public function canSortBy($by)
440
    {
441
        return true;
442
    }
443
444
    /**
445
     * Reverses an {@link ArrayList}
446
     *
447
     * @return ArrayList
448
     */
449
    public function reverse()
450
    {
451
        $list = clone $this;
452
        $list->items = array_reverse($this->items);
453
454
        return $list;
455
    }
456
457
    /**
458
     * Parses a specified column into a sort field and direction
459
     *
460
     * @param string $column String to parse containing the column name
461
     * @param mixed $direction Optional Additional argument which may contain the direction
462
     * @return array Sort specification in the form array("Column", SORT_ASC).
463
     */
464
    protected function parseSortColumn($column, $direction = null)
465
    {
466
        // Substitute the direction for the column if column is a numeric index
467
        if ($direction && (empty($column) || is_numeric($column))) {
468
            $column = $direction;
469
            $direction = null;
470
        }
471
472
        // Parse column specification, considering possible ansi sql quoting
473
        // Note that table prefix is allowed, but discarded
474
        if (preg_match('/^("?(?<table>[^"\s]+)"?\\.)?"?(?<column>[^"\s]+)"?(\s+(?<direction>((asc)|(desc))(ending)?))?$/i', $column, $match)) {
475
            $column = $match['column'];
476
            if (empty($direction) && !empty($match['direction'])) {
477
                $direction = $match['direction'];
478
            }
479
        } else {
480
            throw new InvalidArgumentException("Invalid sort() column");
481
        }
482
483
        // Parse sort direction specification
484
        if (empty($direction) || preg_match('/^asc(ending)?$/i', $direction)) {
485
            $direction = SORT_ASC;
486
        } elseif (preg_match('/^desc(ending)?$/i', $direction)) {
487
            $direction = SORT_DESC;
488
        } else {
489
            throw new InvalidArgumentException("Invalid sort() direction");
490
        }
491
492
        return array($column, $direction);
493
    }
494
495
    /**
496
     * Sorts this list by one or more fields. You can either pass in a single
497
     * field name and direction, or a map of field names to sort directions.
498
     *
499
     * Note that columns may be double quoted as per ANSI sql standard
500
     *
501
     * @return static
502
     * @see SS_List::sort()
503
     * @example $list->sort('Name'); // default ASC sorting
504
     * @example $list->sort('Name DESC'); // DESC sorting
505
     * @example $list->sort('Name', 'ASC');
506
     * @example $list->sort(array('Name'=>'ASC,'Age'=>'DESC'));
507
     */
508
    public function sort()
509
    {
510
        $args = func_get_args();
511
512
        if (count($args)==0) {
513
            return $this;
514
        }
515
        if (count($args)>2) {
516
            throw new InvalidArgumentException('This method takes zero, one or two arguments');
517
        }
518
        $columnsToSort = [];
519
520
        // One argument and it's a string
521
        if (count($args)==1 && is_string($args[0])) {
522
            list($column, $direction) = $this->parseSortColumn($args[0]);
523
            $columnsToSort[$column] = $direction;
524
        } elseif (count($args)==2) {
525
            list($column, $direction) = $this->parseSortColumn($args[0], $args[1]);
526
            $columnsToSort[$column] = $direction;
527
        } elseif (is_array($args[0])) {
528
            foreach ($args[0] as $key => $value) {
529
                list($column, $direction) = $this->parseSortColumn($key, $value);
530
                $columnsToSort[$column] = $direction;
531
            }
532
        } else {
533
            throw new InvalidArgumentException("Bad arguments passed to sort()");
534
        }
535
536
        // Store the original keys of the items as a sort fallback, so we can preserve the original order in the event
537
        // that array_multisort is unable to work out a sort order for them. This also prevents array_multisort trying
538
        // to inspect object properties which can result in errors with circular dependencies
539
        $originalKeys = array_keys($this->items);
540
541
        // This the main sorting algorithm that supports infinite sorting params
542
        $multisortArgs = [];
543
        $values = [];
544
        $firstRun = true;
545
        foreach ($columnsToSort as $column => $direction) {
546
            // The reason these are added to columns is of the references, otherwise when the foreach
547
            // is done, all $values and $direction look the same
548
            $values[$column] = [];
549
            $sortDirection[$column] = $direction;
550
            // We need to subtract every value into a temporary array for sorting
551
            foreach ($this->items as $index => $item) {
552
                $values[$column][] = strtolower($this->extractValue($item, $column));
553
            }
554
            // PHP 5.3 requires below arguments to be reference when using array_multisort together
555
            // with call_user_func_array
556
            // First argument is the 'value' array to be sorted
557
            $multisortArgs[] = &$values[$column];
558
            // First argument is the direction to be sorted,
559
            $multisortArgs[] = &$sortDirection[$column];
560
            if ($firstRun) {
561
                $multisortArgs[] = SORT_REGULAR;
562
            }
563
            $firstRun = false;
564
        }
565
566
        $multisortArgs[] = &$originalKeys;
567
568
        $list = clone $this;
569
        // As the last argument we pass in a reference to the items that all the sorting will be applied upon
570
        $multisortArgs[] = &$list->items;
571
        call_user_func_array('array_multisort', $multisortArgs);
572
        return $list;
573
    }
574
575
    /**
576
     * Returns true if the given column can be used to filter the records.
577
     *
578
     * It works by checking the fields available in the first record of the list.
579
     *
580
     * @param string $by
581
     * @return bool
582
     */
583
    public function canFilterBy($by)
584
    {
585
        if (empty($this->items)) {
586
            return false;
587
        }
588
589
        $firstRecord = $this->first();
590
591
        return array_key_exists($by, $firstRecord);
0 ignored issues
show
Bug introduced by
It seems like $firstRecord can also be of type null; however, parameter $search of array_key_exists() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

591
        return array_key_exists($by, /** @scrutinizer ignore-type */ $firstRecord);
Loading history...
592
    }
593
594
    /**
595
     * Filter the list to include items with these charactaristics
596
     *
597
     * @return ArrayList
598
     * @see SS_List::filter()
599
     * @example $list->filter('Name', 'bob'); // only bob in the list
600
     * @example $list->filter('Name', array('aziz', 'bob'); // aziz and bob in list
601
     * @example $list->filter(array('Name'=>'bob, 'Age'=>21)); // bob with the Age 21 in list
602
     * @example $list->filter(array('Name'=>'bob, 'Age'=>array(21, 43))); // bob with the Age 21 or 43
603
     * @example $list->filter(array('Name'=>array('aziz','bob'), 'Age'=>array(21, 43)));
604
     *          // aziz with the age 21 or 43 and bob with the Age 21 or 43
605
     */
606
    public function filter()
607
    {
608
609
        $keepUs = call_user_func_array([$this, 'normaliseFilterArgs'], func_get_args());
610
611
        $itemsToKeep = [];
612
        foreach ($this->items as $item) {
613
            $keepItem = true;
614
            foreach ($keepUs as $column => $value) {
615
                if ((is_array($value) && !in_array($this->extractValue($item, $column), $value))
616
                    || (!is_array($value) && $this->extractValue($item, $column) != $value)
617
                ) {
618
                    $keepItem = false;
619
                    break;
620
                }
621
            }
622
            if ($keepItem) {
623
                $itemsToKeep[] = $item;
624
            }
625
        }
626
627
        $list = clone $this;
628
        $list->items = $itemsToKeep;
629
        return $list;
630
    }
631
632
    /**
633
     * Return a copy of this list which contains items matching any of these charactaristics.
634
     *
635
     * @example // only bob in the list
636
     *          $list = $list->filterAny('Name', 'bob');
637
     * @example // azis or bob in the list
638
     *          $list = $list->filterAny('Name', array('aziz', 'bob');
639
     * @example // bob or anyone aged 21 in the list
640
     *          $list = $list->filterAny(array('Name'=>'bob, 'Age'=>21));
641
     * @example // bob or anyone aged 21 or 43 in the list
642
     *          $list = $list->filterAny(array('Name'=>'bob, 'Age'=>array(21, 43)));
643
     * @example // all bobs, phils or anyone aged 21 or 43 in the list
644
     *          $list = $list->filterAny(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43)));
645
     *
646
     * @param string|array See {@link filter()}
0 ignored issues
show
Bug introduced by
The type SilverStripe\ORM\See was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
647
     * @return static
648
     */
649
    public function filterAny()
650
    {
651
        $keepUs = $this->normaliseFilterArgs(...func_get_args());
652
653
        $itemsToKeep = [];
654
655
        foreach ($this->items as $item) {
656
            foreach ($keepUs as $column => $value) {
657
                $extractedValue = $this->extractValue($item, $column);
658
                $matches = is_array($value) ? in_array($extractedValue, $value) : $extractedValue == $value;
659
                if ($matches) {
660
                    $itemsToKeep[] = $item;
661
                    break;
662
                }
663
            }
664
        }
665
666
        $list = clone $this;
667
        $list->items = array_unique($itemsToKeep, SORT_REGULAR);
668
        return $list;
669
    }
670
671
    /**
672
     * Take the "standard" arguments that the filter/exclude functions take and return a single array with
673
     * 'colum' => 'value'
674
     *
675
     * @param $column array|string The column name to filter OR an assosicative array of column => value
676
     * @param $value array|string|null The values to filter the $column against
677
     *
678
     * @return array The normalised keyed array
679
     */
680
    protected function normaliseFilterArgs($column, $value = null)
681
    {
682
        $args = func_get_args();
683
        if (count($args) > 2) {
684
            throw new InvalidArgumentException('filter takes one array or two arguments');
685
        }
686
687
        if (count($args) === 1 && !is_array($args[0])) {
688
            throw new InvalidArgumentException('filter takes one array or two arguments');
689
        }
690
691
        $keepUs = [];
692
        if (count($args) === 2) {
693
            $keepUs[$args[0]] = $args[1];
694
        }
695
696
        if (count($args) === 1 && is_array($args[0])) {
697
            foreach ($args[0] as $key => $val) {
698
                $keepUs[$key] = $val;
699
            }
700
        }
701
702
        return $keepUs;
703
    }
704
705
    /**
706
     * Filter this list to only contain the given Primary IDs
707
     *
708
     * @param array $ids Array of integers, will be automatically cast/escaped.
709
     * @return ArrayList
710
     */
711
    public function byIDs($ids)
712
    {
713
        $ids = array_map('intval', $ids); // sanitize
714
        return $this->filter('ID', $ids);
715
    }
716
717
    public function byID($id)
718
    {
719
        $firstElement = $this->filter("ID", $id)->first();
720
721
        if ($firstElement === false) {
722
            return null;
723
        }
724
725
        return $firstElement;
726
    }
727
728
    /**
729
     * @see Filterable::filterByCallback()
730
     *
731
     * @example $list = $list->filterByCallback(function($item, $list) { return $item->Age == 9; })
732
     * @param callable $callback
733
     * @return ArrayList
734
     */
735
    public function filterByCallback($callback)
736
    {
737
        if (!is_callable($callback)) {
738
            throw new LogicException(sprintf(
739
                "SS_Filterable::filterByCallback() passed callback must be callable, '%s' given",
740
                gettype($callback)
741
            ));
742
        }
743
744
        $output = static::create();
745
746
        foreach ($this as $item) {
747
            if (call_user_func($callback, $item, $this)) {
748
                $output->push($item);
749
            }
750
        }
751
752
        return $output;
753
    }
754
755
    /**
756
     * Exclude the list to not contain items with these charactaristics
757
     *
758
     * @return ArrayList
759
     * @see SS_List::exclude()
760
     * @example $list->exclude('Name', 'bob'); // exclude bob from list
761
     * @example $list->exclude('Name', array('aziz', 'bob'); // exclude aziz and bob from list
762
     * @example $list->exclude(array('Name'=>'bob, 'Age'=>21)); // exclude bob that has Age 21
763
     * @example $list->exclude(array('Name'=>'bob, 'Age'=>array(21, 43))); // exclude bob with Age 21 or 43
764
     * @example $list->exclude(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43)));
765
     *          // bob age 21 or 43, phil age 21 or 43 would be excluded
766
     */
767
    public function exclude()
768
    {
769
        $removeUs = $this->normaliseFilterArgs(...func_get_args());
770
771
        $hitsRequiredToRemove = count($removeUs);
772
        $matches = [];
773
        foreach ($removeUs as $column => $excludeValue) {
774
            foreach ($this->items as $key => $item) {
775
                if (!is_array($excludeValue) && $this->extractValue($item, $column) == $excludeValue) {
776
                    $matches[$key] = isset($matches[$key]) ? $matches[$key] + 1 : 1;
777
                } elseif (is_array($excludeValue) && in_array($this->extractValue($item, $column), $excludeValue)) {
778
                    $matches[$key] = isset($matches[$key]) ? $matches[$key] + 1 : 1;
779
                }
780
            }
781
        }
782
783
        $keysToRemove = array_keys($matches, $hitsRequiredToRemove);
784
785
        $itemsToKeep = [];
786
        foreach ($this->items as $key => $value) {
787
            if (!in_array($key, $keysToRemove)) {
788
                $itemsToKeep[] = $value;
789
            }
790
        }
791
792
        $list = clone $this;
793
        $list->items = $itemsToKeep;
794
        return $list;
795
    }
796
797
    protected function shouldExclude($item, $args)
798
    {
799
    }
800
801
802
    /**
803
     * Returns whether an item with $key exists
804
     *
805
     * @param mixed $offset
806
     * @return bool
807
     */
808
    public function offsetExists($offset)
809
    {
810
        return array_key_exists($offset, $this->items);
811
    }
812
813
    /**
814
     * Returns item stored in list with index $key
815
     *
816
     * @param mixed $offset
817
     * @return DataObject
818
     */
819
    public function offsetGet($offset)
820
    {
821
        if ($this->offsetExists($offset)) {
822
            return $this->items[$offset];
823
        }
824
        return null;
825
    }
826
827
    /**
828
     * Set an item with the key in $key
829
     *
830
     * @param mixed $offset
831
     * @param mixed $value
832
     */
833
    public function offsetSet($offset, $value)
834
    {
835
        if ($offset == null) {
836
            $this->items[] = $value;
837
        } else {
838
            $this->items[$offset] = $value;
839
        }
840
    }
841
842
    /**
843
     * Unset an item with the key in $key
844
     *
845
     * @param mixed $offset
846
     */
847
    public function offsetUnset($offset)
848
    {
849
        unset($this->items[$offset]);
850
    }
851
852
    /**
853
     * Extracts a value from an item in the list, where the item is either an
854
     * object or array.
855
     *
856
     * @param array|object $item
857
     * @param string $key
858
     * @return mixed
859
     */
860
    protected function extractValue($item, $key)
861
    {
862
        if (is_object($item)) {
863
            if (method_exists($item, 'hasMethod') && $item->hasMethod($key)) {
864
                return $item->{$key}();
865
            }
866
            return $item->{$key};
867
        }
868
869
        if (array_key_exists($key, $item)) {
870
            return $item[$key];
871
        }
872
873
        return null;
874
    }
875
}
876