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 = array(), $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('Passed variable is not an array or object, using empty array instead');
185        }
186
187        if (is_object($data) && ($data instanceof self || $data instanceof \ArrayObject)) {
188            $data = $data->getArrayCopy();
189        }
190        if (!is_array($data)) {
191            $data = (array) $data;
192        }
193
194        $storage = $this->storage;
195
196        $this->storage = $data;
197
198        return $storage;
199    }
200
201    /**
202     * Creates a copy of the ArrayObject.
203     *
204     * @return array
205     */
206    public function getArrayCopy()
207    {
208        return $this->storage;
209    }
210
211    /**
212     * Gets the behavior flags.
213     *
214     * @return int
215     */
216    public function getFlags()
217    {
218        return $this->flag;
219    }
220
221    /**
222     * Create a new iterator from an ArrayObject instance
223     *
224     * @return \Iterator
225     */
226    public function getIterator()
227    {
228        $class = $this->iteratorClass;
229
230        return new $class($this->storage);
231    }
232
233    /**
234     * Gets the iterator classname for the ArrayObject.
235     *
236     * @return string
237     */
238    public function getIteratorClass()
239    {
240        return $this->iteratorClass;
241    }
242
243    /**
244     * Sort the entries by key
245     *
246     * @return void
247     */
248    public function ksort()
249    {
250        ksort($this->storage);
251    }
252
253    /**
254     * Sort an array using a case insensitive "natural order" algorithm
255     *
256     * @return void
257     */
258    public function natcasesort()
259    {
260        natcasesort($this->storage);
261    }
262
263    /**
264     * Sort entries using a "natural order" algorithm
265     *
266     * @return void
267     */
268    public function natsort()
269    {
270        natsort($this->storage);
271    }
272
273    /**
274     * Returns whether the requested key exists
275     *
276     * @param  mixed $key
277     * @return bool
278     */
279    public function offsetExists($key)
280    {
281        return isset($this->storage[$key]);
282    }
283
284    /**
285     * Returns the value at the specified key
286     *
287     * @param  mixed $key
288     * @return mixed
289     */
290    public function &offsetGet($key)
291    {
292        $ret = null;
293        if (!$this->offsetExists($key)) {
294            return $ret;
295        }
296        $ret =& $this->storage[$key];
297
298        return $ret;
299    }
300
301    /**
302     * Sets the value at the specified key to value
303     *
304     * @param  mixed $key
305     * @param  mixed $value
306     * @return void
307     */
308    public function offsetSet($key, $value)
309    {
310        $this->storage[$key] = $value;
311    }
312
313    /**
314     * Unsets the value at the specified key
315     *
316     * @param  mixed $key
317     * @return void
318     */
319    public function offsetUnset($key)
320    {
321        if ($this->offsetExists($key)) {
322            unset($this->storage[$key]);
323        }
324    }
325
326    /**
327     * Serialize an ArrayObject
328     *
329     * @return string
330     */
331    public function serialize()
332    {
333        return serialize(get_object_vars($this));
334    }
335
336    /**
337     * Sets the behavior flags
338     *
339     * @param  int  $flags
340     * @return void
341     */
342    public function setFlags($flags)
343    {
344        $this->flag = $flags;
345    }
346
347    /**
348     * Sets the iterator classname for the ArrayObject
349     *
350     * @param  string $class
351     * @return void
352     */
353    public function setIteratorClass($class)
354    {
355        if (class_exists($class)) {
356            $this->iteratorClass = $class;
357
358            return ;
359        }
360
361        if (strpos($class, '\\') === 0) {
362            $class = '\\' . $class;
363            if (class_exists($class)) {
364                $this->iteratorClass = $class;
365
366                return ;
367            }
368        }
369
370        throw new Exception\InvalidArgumentException('The iterator class does not exist');
371    }
372
373    /**
374     * Sort the entries with a user-defined comparison function and maintain key association
375     *
376     * @param  callable $function
377     * @return void
378     */
379    public function uasort($function)
380    {
381        if (is_callable($function)) {
382            uasort($this->storage, $function);
383        }
384    }
385
386    /**
387     * Sort the entries by keys using a user-defined comparison function
388     *
389     * @param  callable $function
390     * @return void
391     */
392    public function uksort($function)
393    {
394        if (is_callable($function)) {
395            uksort($this->storage, $function);
396        }
397    }
398
399    /**
400     * Unserialize an ArrayObject
401     *
402     * @param  string $data
403     * @return void
404     */
405    public function unserialize($data)
406    {
407        $ar                        = unserialize($data);
408        $this->protectedProperties = array_keys(get_object_vars($this));
409
410        $this->setFlags($ar['flag']);
411        $this->exchangeArray($ar['storage']);
412        $this->setIteratorClass($ar['iteratorClass']);
413
414        foreach ($ar as $k => $v) {
415            switch ($k) {
416                case 'flag':
417                    $this->setFlags($v);
418                    break;
419                case 'storage':
420                    $this->exchangeArray($v);
421                    break;
422                case 'iteratorClass':
423                    $this->setIteratorClass($v);
424                    break;
425                case 'protectedProperties':
426                    continue;
427                default:
428                    $this->__set($k, $v);
429            }
430        }
431    }
432}
433