Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
16 / 16
CRAP
100.00% covered (success)
100.00%
87 / 87
Dictionary
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
16 / 16
49
100.00% covered (success)
100.00%
87 / 87
 generateHash
100.00% covered (success)
100.00%
1 / 1
8
100.00% covered (success)
100.00%
15 / 15
 getIterator
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 offsetExists
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 offsetGet
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 offsetSet
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 offsetUnset
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 count
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 serialize
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 unserialize
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 fromPairs
100.00% covered (success)
100.00%
1 / 1
7
100.00% covered (success)
100.00%
10 / 10
 fromArray
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
7 / 7
 toPairs
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 keys
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 values
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 sortBy
100.00% covered (success)
100.00%
1 / 1
14
100.00% covered (success)
100.00%
25 / 25
 getCopy
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
1<?php
2
3namespace GamingEngine\Dictionary;
4
5use ArrayAccess;
6use Countable;
7use Serializable;
8use IteratorAggregate;
9
10class Dictionary implements Countable, ArrayAccess, Serializable, IteratorAggregate
11{
12    private $data = [];
13    private $keys = [];
14
15    /**
16     * @internal
17     *
18     * Create hash for each element.
19     *
20     * @param   $value
21     * @return  string
22     */
23    protected function generateHash($value)
24    {
25        if (is_object($value)) {
26            if ($value instanceof \Closure) {
27                throw new \InvalidArgumentException("Closure cannot be Dictionary key.");
28            }
29            return 'object:' . spl_object_hash($value);
30        } elseif (is_string($value)) {
31            return 'string:' . $value;
32        } elseif (is_int($value)) {
33            return 'int:' . $value;
34        } elseif (is_float($value)) {
35            return 'float:' . $value;
36        } elseif (is_bool($value)) {
37            return 'bool:' . ((int)$value);
38        } elseif (is_null($value)) {
39            return 'null:null';
40        } else {
41            throw new \InvalidArgumentException("Invalid Dictionary key.");
42        }
43    }
44
45    /**
46     * {@inheritDoc}
47     * @return DictionaryIterator
48     */
49    public function getIterator()
50    {
51        return new DictionaryIterator($this);
52    }
53
54    /**
55     * {@inheritdoc}
56     */
57    public function offsetExists($offset)
58    {
59        $hash = $this->generateHash($offset);
60        return isset($this->data[$hash]);
61    }
62
63    /**
64     * {@inheritdoc}
65     */
66    public function &offsetGet($offset)
67    {
68        $hash = $this->generateHash($offset);
69        return $this->data[$hash];
70    }
71
72    /**
73     * {@inheritdoc}
74     */
75    public function offsetSet($offset, $value)
76    {
77        $hash = $this->generateHash($offset);
78        if (!isset($this->keys[$hash])) {
79            $this->keys[$hash] = $offset;
80        }
81        $this->data[$hash] = $value;
82    }
83
84    /**
85     * {@inheritdoc}
86     */
87    public function offsetUnset($offset)
88    {
89        $hash = $this->generateHash($offset);
90        unset($this->data[$hash]);
91        unset($this->keys[$hash]);
92    }
93
94    /**
95     * {@inheritdoc}
96     */
97    public function count()
98    {
99        return count($this->data);
100    }
101
102    /**
103     * {@inheritdoc}
104     */
105    public function serialize()
106    {
107        $pairs = [];
108        foreach ($this as $key => $value) {
109            $pairs[] = [$key, $value];
110        }
111        return \serialize($pairs);
112    }
113
114    /**
115     * {@inheritdoc}
116     */
117    public function unserialize($serialized)
118    {
119        $pairs = \unserialize($serialized);
120        foreach ($pairs as $pair) {
121            $this[$pair[0]] = $pair[1];
122        }
123    }
124
125    /**
126     * Create new Dictionary from array of key-value pairs.
127     *
128     * @param   $array      array   Array of pairs. Each pair must be 2-element array.
129     * @return  Dictionary
130     */
131    public static function fromPairs($array)
132    {
133        if (!is_array($array) && !($array instanceof \Traversable)) {
134            throw new \InvalidArgumentException(sprintf(
135                'Dictionary::fromPairs() argument must be array or Traversable, '
136                . 'but is "%s".', gettype($array)
137            ));
138        }
139
140        $result = new Dictionary();
141        foreach ($array as $pair) {
142            if (!is_array($pair) || !isset($pair[0]) || !isset($pair[1])) {
143                throw new \InvalidArgumentException(
144                    'Each element of array or Traversable passed to Dictionary::FromPairs()'
145                    . ' must be two-elements array.'
146                );
147            }
148
149            $result[$pair[0]] = $pair[1];
150        }
151        return $result;
152    }
153
154    /**
155     * Create new Dictionary from standard PHP array.
156     *
157     * @param  $array        array   Array to create Dictionary from.
158     * @return Dictionary
159     */
160    public static function fromArray($array)
161    {
162        if (!is_array($array) && !($array instanceof \Traversable)) {
163            throw new \InvalidArgumentException(sprintf(
164                'Dictionary::fromArray() argument must be array or Traversable, '
165                . 'but is "%s".', gettype($array)
166            ));
167        }
168
169        $result = new Dictionary();
170        foreach ($array as $key => $value) {
171            $result[$key] = $value;
172        }
173        return $result;
174    }
175
176    /**
177     * Get array of all key-value pairs in dictionary.
178     *
179     * @return  array   Array of pairs. Each pair is 2-element array.
180     */
181    public function toPairs()
182    {
183        $result = [];
184        foreach ($this as $key => $value) {
185            $result[] = [$key, $value];
186        }
187        return $result;
188    }
189
190    /**
191     * Return array with all keys stored in dictionary.
192     *
193     * @return  array   Dictionary's keys.
194     */
195    public function keys()
196    {
197        return array_values($this->keys);
198    }
199
200    /**
201     * Return array with all values stored in dictionary.
202     *
203     * @return  array   Dictionary's values.
204     */
205    public function values()
206    {
207        return array_values($this->data);
208    }
209
210    /**
211     * Sort Dictionary by values returned by callback.
212     *
213     * @param   string|callable $callback   Callback returning value for each Dictionary's element.
214     *                                      If argument is a string "values" or "keys", it will be sorted
215     *                                      by Dictionary's values or keys.
216     * @param   string          $direction  Sorting direction. "ASC" or "DESC".
217     * @return  $this
218     */
219    public function sortBy($callback = null, $direction = 'ASC')
220    {
221        if (!is_callable($callback)) {
222            if (is_null($callback) or is_string($callback) && 'values' === strtolower($callback)) {
223                $callback = function ($value, $key) { return $value; };
224            } else if (is_string($callback) && 'keys' === strtolower($callback)) {
225                $callback = function ($value, $key) { return $key; };
226            } else {
227                throw new \InvalidArgumentException(sprintf(
228                    'Dictionary::sort() argument must be "keys", "values" or callable, '
229                    . 'but is "%s".', gettype($callback)
230                ));
231            }
232        }
233
234        if ($direction !== SORT_ASC && $direction !== SORT_DESC) {
235            if (is_string($direction)) {
236                switch (strtolower($direction)) {
237                    case 'asc':
238                        $direction = SORT_ASC;
239                        break;
240                    case 'desc':
241                        $direction = SORT_DESC;
242                        break;
243                    default:
244                        throw new \InvalidArgumentException(sprintf(
245                            'Direction must be "asc" or "desc", but is "%s".', $direction));
246                }
247            } else {
248                throw new \InvalidArgumentException(sprintf(
249                    'Direction must be string "asc" or "desc", but is "%s".', gettype($direction)));
250            }
251        }
252
253        $order = [];
254        foreach ($this as $key => $value) {
255            $order[] = $callback($value, $key);
256        }
257
258        array_multisort($order, $direction, SORT_REGULAR, $this->data, $this->keys);
259
260        return $this; //to allow chaining
261    }
262
263    /**
264     * Return copy of Dictionary.
265     * Created for chaining with sortBy().
266     *
267     * @return  Dictionary  Exact copy of Dictionary.
268     */
269    public function getCopy()
270    {
271        return clone $this;
272    }
273}