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