1<?php
2
3/**
4 * @see       https://github.com/laminas/laminas-stdlib for the canonical source repository
5 * @copyright https://github.com/laminas/laminas-stdlib/blob/master/COPYRIGHT.md
6 * @license   https://github.com/laminas/laminas-stdlib/blob/master/LICENSE.md New BSD License
7 */
8
9namespace Laminas\Stdlib;
10
11use ArrayAccess;
12use Countable;
13use IteratorAggregate;
14use Serializable;
15
16/**
17 * Custom framework ArrayObject implementation
18 *
19 * Extends version-specific "abstract" implementation.
20 */
21class ArrayObject implements IteratorAggregate, ArrayAccess, Serializable, Countable
22{
23    /**
24     * Properties of the object have their normal functionality
25     * when accessed as list (var_dump, foreach, etc.).
26     */
27    const STD_PROP_LIST = 1;
28
29    /**
30     * Entries can be accessed as properties (read and write).
31     */
32    const ARRAY_AS_PROPS = 2;
33
34    /**
35     * @var array
36     */
37    protected $storage;
38
39    /**
40     * @var int
41     */
42    protected $flag;
43
44    /**
45     * @var string
46     */
47    protected $iteratorClass;
48
49    /**
50     * @var array
51     */
52    protected $protectedProperties;
53
54    /**
55     * Constructor
56     *
57     * @param array  $input
58     * @param int    $flags
59     * @param string $iteratorClass
60     */
61    public function __construct($input = [], $flags = self::STD_PROP_LIST, $iteratorClass = 'ArrayIterator')
62    {
63        $this->setFlags($flags);
64        $this->storage = $input;
65        $this->setIteratorClass($iteratorClass);
66        $this->protectedProperties = array_keys(get_object_vars($this));
67    }
68
69    /**
70     * Returns whether the requested key exists
71     *
72     * @param  mixed $key
73     * @return bool
74     */
75    public function __isset($key)
76    {
77        if ($this->flag == self::ARRAY_AS_PROPS) {
78            return $this->offsetExists($key);
79        }
80        if (in_array($key, $this->protectedProperties)) {
81            throw new Exception\InvalidArgumentException('$key is a protected property, use a different key');
82        }
83
84        return isset($this->$key);
85    }
86
87    /**
88     * Sets the value at the specified key to value
89     *
90     * @param  mixed $key
91     * @param  mixed $value
92     * @return void
93     */
94    public function __set($key, $value)
95    {
96        if ($this->flag == self::ARRAY_AS_PROPS) {
97            return $this->offsetSet($key, $value);
98        }
99        if (in_array($key, $this->protectedProperties)) {
100            throw new Exception\InvalidArgumentException('$key is a protected property, use a different key');
101        }
102        $this->$key = $value;
103    }
104
105    /**
106     * Unsets the value at the specified key
107     *
108     * @param  mixed $key
109     * @return void
110     */
111    public function __unset($key)
112    {
113        if ($this->flag == self::ARRAY_AS_PROPS) {
114            return $this->offsetUnset($key);
115        }
116        if (in_array($key, $this->protectedProperties)) {
117            throw new Exception\InvalidArgumentException('$key is a protected property, use a different key');
118        }
119        unset($this->$key);
120    }
121
122    /**
123     * Returns the value at the specified key by reference
124     *
125     * @param  mixed $key
126     * @return mixed
127     */
128    public function &__get($key)
129    {
130        $ret = null;
131        if ($this->flag == self::ARRAY_AS_PROPS) {
132            $ret =& $this->offsetGet($key);
133
134            return $ret;
135        }
136        if (in_array($key, $this->protectedProperties)) {
137            throw new Exception\InvalidArgumentException('$key is a protected property, use a different key');
138        }
139
140        return $this->$key;
141    }
142
143    /**
144     * Appends the value
145     *
146     * @param  mixed $value
147     * @return void
148     */
149    public function append($value)
150    {
151        $this->storage[] = $value;
152    }
153
154    /**
155     * Sort the entries by value
156     *
157     * @return void
158     */
159    public function asort()
160    {
161        asort($this->storage);
162    }
163
164    /**
165     * Get the number of public properties in the ArrayObject
166     *
167     * @return int
168     */
169    public function count()
170    {
171        return count($this->storage);
172    }
173
174    /**
175     * Exchange the array for another one.
176     *
177     * @param  array|ArrayObject $data
178     * @return array
179     */
180    public function exchangeArray($data)
181    {
182        if (! is_array($data) && ! is_object($data)) {
183            throw new Exception\InvalidArgumentException(
184                'Passed variable is not an array or object, using empty array instead'
185            );
186        }
187
188        if (is_object($data) && ($data instanceof self || $data instanceof \ArrayObject)) {
189            $data = $data->getArrayCopy();
190        }
191        if (! is_array($data)) {
192            $data = (array) $data;
193        }
194
195        $storage = $this->storage;
196
197        $this->storage = $data;
198
199        return $storage;
200    }
201
202    /**
203     * Creates a copy of the ArrayObject.
204     *
205     * @return array
206     */
207    public function getArrayCopy()
208    {
209        return $this->storage;
210    }
211
212    /**
213     * Gets the behavior flags.
214     *
215     * @return int
216     */
217    public function getFlags()
218    {
219        return $this->flag;
220    }
221
222    /**
223     * Create a new iterator from an ArrayObject instance
224     *
225     * @return \Iterator
226     */
227    public function getIterator()
228    {
229        $class = $this->iteratorClass;
230
231        return new $class($this->storage);
232    }
233
234    /**
235     * Gets the iterator classname for the ArrayObject.
236     *
237     * @return string
238     */
239    public function getIteratorClass()
240    {
241        return $this->iteratorClass;
242    }
243
244    /**
245     * Sort the entries by key
246     *
247     * @return void
248     */
249    public function ksort()
250    {
251        ksort($this->storage);
252    }
253
254    /**
255     * Sort an array using a case insensitive "natural order" algorithm
256     *
257     * @return void
258     */
259    public function natcasesort()
260    {
261        natcasesort($this->storage);
262    }
263
264    /**
265     * Sort entries using a "natural order" algorithm
266     *
267     * @return void
268     */
269    public function natsort()
270    {
271        natsort($this->storage);
272    }
273
274    /**
275     * Returns whether the requested key exists
276     *
277     * @param  mixed $key
278     * @return bool
279     */
280    public function offsetExists($key)
281    {
282        return isset($this->storage[$key]);
283    }
284
285    /**
286     * Returns the value at the specified key
287     *
288     * @param  mixed $key
289     * @return mixed
290     */
291    public function &offsetGet($key)
292    {
293        $ret = null;
294        if (! $this->offsetExists($key)) {
295            return $ret;
296        }
297        $ret =& $this->storage[$key];
298
299        return $ret;
300    }
301
302    /**
303     * Sets the value at the specified key to value
304     *
305     * @param  mixed $key
306     * @param  mixed $value
307     * @return void
308     */
309    public function offsetSet($key, $value)
310    {
311        $this->storage[$key] = $value;
312    }
313
314    /**
315     * Unsets the value at the specified key
316     *
317     * @param  mixed $key
318     * @return void
319     */
320    public function offsetUnset($key)
321    {
322        if ($this->offsetExists($key)) {
323            unset($this->storage[$key]);
324        }
325    }
326
327    /**
328     * Serialize an ArrayObject
329     *
330     * @return string
331     */
332    public function serialize()
333    {
334        return serialize(get_object_vars($this));
335    }
336
337    /**
338     * Sets the behavior flags
339     *
340     * @param  int  $flags
341     * @return void
342     */
343    public function setFlags($flags)
344    {
345        $this->flag = $flags;
346    }
347
348    /**
349     * Sets the iterator classname for the ArrayObject
350     *
351     * @param  string $class
352     * @return void
353     */
354    public function setIteratorClass($class)
355    {
356        if (class_exists($class)) {
357            $this->iteratorClass = $class;
358
359            return ;
360        }
361
362        if (strpos($class, '\\') === 0) {
363            $class = '\\' . $class;
364            if (class_exists($class)) {
365                $this->iteratorClass = $class;
366
367                return ;
368            }
369        }
370
371        throw new Exception\InvalidArgumentException('The iterator class does not exist');
372    }
373
374    /**
375     * Sort the entries with a user-defined comparison function and maintain key association
376     *
377     * @param  callable $function
378     * @return void
379     */
380    public function uasort($function)
381    {
382        if (is_callable($function)) {
383            uasort($this->storage, $function);
384        }
385    }
386
387    /**
388     * Sort the entries by keys using a user-defined comparison function
389     *
390     * @param  callable $function
391     * @return void
392     */
393    public function uksort($function)
394    {
395        if (is_callable($function)) {
396            uksort($this->storage, $function);
397        }
398    }
399
400    /**
401     * Unserialize an ArrayObject
402     *
403     * @param  string $data
404     * @return void
405     */
406    public function unserialize($data)
407    {
408        $ar                        = unserialize($data);
409        $this->protectedProperties = array_keys(get_object_vars($this));
410
411        $this->setFlags($ar['flag']);
412        $this->exchangeArray($ar['storage']);
413        $this->setIteratorClass($ar['iteratorClass']);
414
415        foreach ($ar as $k => $v) {
416            switch ($k) {
417                case 'flag':
418                    $this->setFlags($v);
419                    break;
420                case 'storage':
421                    $this->exchangeArray($v);
422                    break;
423                case 'iteratorClass':
424                    $this->setIteratorClass($v);
425                    break;
426                case 'protectedProperties':
427                    break;
428                default:
429                    $this->__set($k, $v);
430            }
431        }
432    }
433}
434