1 | <?php |
||||||
2 | |||||||
3 | declare(strict_types=1); |
||||||
4 | |||||||
5 | namespace PHPCollections\Collections; |
||||||
6 | |||||||
7 | use OutOfRangeException; |
||||||
8 | use PHPCollections\Checker; |
||||||
9 | use PHPCollections\Exceptions\InvalidOperationException; |
||||||
10 | use PHPCollections\Interfaces\DictionaryInterface; |
||||||
11 | use PHPCollections\Interfaces\MergeableInterface; |
||||||
12 | use PHPCollections\Interfaces\SortableInterface; |
||||||
13 | |||||||
14 | /** |
||||||
15 | * A Pair object collection |
||||||
16 | * represented by a generic |
||||||
17 | * type key and value. |
||||||
18 | */ |
||||||
19 | class Dictionary extends BaseCollection implements DictionaryInterface, MergeableInterface, SortableInterface |
||||||
20 | { |
||||||
21 | /** |
||||||
22 | * The type of the keys |
||||||
23 | * for this dictionary. |
||||||
24 | * |
||||||
25 | * @var mixed |
||||||
26 | */ |
||||||
27 | private $keyType; |
||||||
28 | |||||||
29 | /** |
||||||
30 | * The type of the values |
||||||
31 | * for this dictionary. |
||||||
32 | * |
||||||
33 | * @var mixed |
||||||
34 | */ |
||||||
35 | private $valueType; |
||||||
36 | |||||||
37 | /** |
||||||
38 | * Creates a new Dictionary. |
||||||
39 | * |
||||||
40 | * @param mixed $keyType |
||||||
41 | * @param mixed $valueType |
||||||
42 | * @param array $data |
||||||
43 | * |
||||||
44 | * @throws \InvalidArgumentException |
||||||
45 | */ |
||||||
46 | 27 | public function __construct($keyType, $valueType, array $data = []) |
|||||
47 | { |
||||||
48 | 27 | $this->keyType = $keyType; |
|||||
49 | 27 | $this->valueType = $valueType; |
|||||
50 | |||||||
51 | 27 | foreach ($data as $key => $value) { |
|||||
52 | 11 | $this->validateEntry($key, $value); |
|||||
53 | } |
||||||
54 | |||||||
55 | 27 | parent::__construct($data); |
|||||
56 | 27 | $this->initializePairs($data); |
|||||
57 | 27 | } |
|||||
58 | |||||||
59 | /** |
||||||
60 | * Adds a new value to the dictionary. |
||||||
61 | * |
||||||
62 | * @param mixed $key |
||||||
63 | * @param mixed $value |
||||||
64 | * |
||||||
65 | * @throws \InvalidArgumentException |
||||||
66 | * |
||||||
67 | * @return void |
||||||
68 | */ |
||||||
69 | 27 | public function add($key, $value): void |
|||||
70 | { |
||||||
71 | 27 | $this->validateEntry($key, $value); |
|||||
72 | 27 | $this->dataHolder->offsetSet($key, new Pair($key, $value)); |
|||||
73 | 27 | } |
|||||
74 | |||||||
75 | /** |
||||||
76 | * Gets the difference between two Dictionary. |
||||||
77 | * |
||||||
78 | * @param \PHPCollections\Collections\Dictionary $newDictionary |
||||||
79 | * |
||||||
80 | * @throws \PHPCollections\Exceptions\InvalidOperationException |
||||||
81 | * |
||||||
82 | * @return \PHPCollections\Collections\Dictionary |
||||||
83 | */ |
||||||
84 | 4 | public function diff(BaseCollection $newDictionary): BaseCollection |
|||||
85 | { |
||||||
86 | 4 | if (!$newDictionary instanceof self) { |
|||||
87 | 1 | throw new InvalidOperationException('You should only compare a Dictionary against another Dictionary'); |
|||||
88 | } |
||||||
89 | |||||||
90 | 3 | if ($this->keyType !== $newDictionary->getKeyType()) { |
|||||
91 | 1 | throw new InvalidOperationException(sprintf('The key type for this Dictionary is %s, you cannot pass a Dictionary with %s as key type', $this->keyType, $newDictionary->getKeyType())); |
|||||
92 | } |
||||||
93 | |||||||
94 | 2 | if ($this->valueType !== $newDictionary->getValueType()) { |
|||||
95 | 1 | throw new InvalidOperationException(sprintf('The value type for this Dictionary is %s, you cannot pass a Dictionary with %s as value types', $this->valueType, $newDictionary->getValueType())); |
|||||
96 | } |
||||||
97 | |||||||
98 | $diffValues = array_udiff_uassoc($this->toArray(), $newDictionary->toArray(), function ($firstValue, $secondValue) { |
||||||
99 | 1 | return $firstValue <=> $secondValue; |
|||||
100 | }, function ($firstKey, $secondKey) { |
||||||
101 | 1 | return $firstKey <=> $secondKey; |
|||||
102 | 1 | }); |
|||||
103 | |||||||
104 | 1 | return new self($this->keyType, $this->valueType, $diffValues); |
|||||
105 | } |
||||||
106 | |||||||
107 | /** |
||||||
108 | * Determines if two Dictionary objects are equal. |
||||||
109 | * |
||||||
110 | * @param \PHPCollections\Collections\Dictionary $newDictionary |
||||||
111 | * |
||||||
112 | * @return \PHPCollections\Collections\Dictionary |
||||||
113 | */ |
||||||
114 | 2 | public function equals(BaseCollection $newDictionary): bool |
|||||
115 | { |
||||||
116 | 2 | if (!$newDictionary instanceof self) { |
|||||
117 | 1 | throw new InvalidOperationException('You should only compare an Dictionary against another Dictionary'); |
|||||
118 | } |
||||||
119 | |||||||
120 | 1 | return $this->toArray() == $newDictionary->toArray(); |
|||||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||||||
121 | } |
||||||
122 | |||||||
123 | /** |
||||||
124 | * Fills the dictionary with data. |
||||||
125 | * |
||||||
126 | * @param array $data |
||||||
127 | * |
||||||
128 | * @return void |
||||||
129 | */ |
||||||
130 | 1 | public function fill(array $data): void |
|||||
131 | { |
||||||
132 | 1 | foreach ($data as $key => $entry) { |
|||||
133 | 1 | $this->add($key, $entry); |
|||||
134 | } |
||||||
135 | 1 | } |
|||||
136 | |||||||
137 | /** |
||||||
138 | * Filters the collection applying |
||||||
139 | * a given callback. |
||||||
140 | * |
||||||
141 | * @param callable $callback |
||||||
142 | * |
||||||
143 | * @return \PHPCollections\Collections\Dictionary|null |
||||||
144 | */ |
||||||
145 | 1 | public function filter(callable $callback): ?self |
|||||
146 | { |
||||||
147 | 1 | $matcheds = []; |
|||||
148 | |||||||
149 | 1 | foreach ($this->dataHolder as $key => $value) { |
|||||
150 | 1 | if (call_user_func($callback, $value->getKey(), $value->getValue()) === true) { |
|||||
151 | 1 | $matcheds[$value->getKey()] = $value->getValue(); |
|||||
152 | } |
||||||
153 | } |
||||||
154 | |||||||
155 | 1 | return count($matcheds) > 0 ? new $this($this->keyType, $this->valueType, $matcheds) : null; |
|||||
156 | } |
||||||
157 | |||||||
158 | /** |
||||||
159 | * Iterates over every element of the collection. |
||||||
160 | * |
||||||
161 | * @param callable $callback |
||||||
162 | * |
||||||
163 | * @return void |
||||||
164 | */ |
||||||
165 | 1 | public function forEach(callable $callback): void |
|||||
166 | { |
||||||
167 | 1 | $data = $this->toArray(); |
|||||
168 | |||||||
169 | 1 | array_walk($data, $callback); |
|||||
170 | 1 | $this->initializePairs($data); |
|||||
171 | 1 | } |
|||||
172 | |||||||
173 | /** |
||||||
174 | * Returns the value for the specified |
||||||
175 | * key or null if it's not defined. |
||||||
176 | * |
||||||
177 | * @param mixed $key |
||||||
178 | * |
||||||
179 | * @return mixed|null |
||||||
180 | */ |
||||||
181 | 5 | public function get($key) |
|||||
182 | { |
||||||
183 | 5 | return $this->dataHolder->offsetExists($key) ? |
|||||
184 | 5 | $this->dataHolder->offsetGet($key)->getValue() : |
|||||
185 | 5 | null; |
|||||
186 | } |
||||||
187 | |||||||
188 | /** |
||||||
189 | * Returns the key type for this collection. |
||||||
190 | * |
||||||
191 | * @return mixed |
||||||
192 | */ |
||||||
193 | 5 | public function getKeyType() |
|||||
194 | { |
||||||
195 | 5 | return $this->keyType; |
|||||
196 | } |
||||||
197 | |||||||
198 | /** |
||||||
199 | * Returns the key value for this collection. |
||||||
200 | * |
||||||
201 | * @return mixed |
||||||
202 | */ |
||||||
203 | 4 | public function getValueType() |
|||||
204 | { |
||||||
205 | 4 | return $this->valueType; |
|||||
206 | } |
||||||
207 | |||||||
208 | /** |
||||||
209 | * Populates the container with Pair objects. |
||||||
210 | * |
||||||
211 | * @param array $data |
||||||
212 | * |
||||||
213 | * @return void |
||||||
214 | */ |
||||||
215 | 27 | private function initializePairs(array $data): void |
|||||
216 | { |
||||||
217 | 27 | foreach ($data as $key => $value) { |
|||||
218 | 12 | $this->dataHolder[$key] = new Pair($key, $value); |
|||||
219 | } |
||||||
220 | 27 | } |
|||||
221 | |||||||
222 | /** |
||||||
223 | * Updates elements in the collection by |
||||||
224 | * applying a given callback function. |
||||||
225 | * |
||||||
226 | * @param callable $callback |
||||||
227 | * |
||||||
228 | * @return \PHPCollections\Collections\Dictionary|null |
||||||
229 | */ |
||||||
230 | 1 | public function map(callable $callback): ?self |
|||||
231 | { |
||||||
232 | 1 | $matcheds = array_map($callback, $this->toArray()); |
|||||
233 | |||||||
234 | 1 | return count($matcheds) > 0 ? new $this($this->keyType, $this->valueType, $this->toArray()) : null; |
|||||
235 | } |
||||||
236 | |||||||
237 | /** |
||||||
238 | * Merges two dictionaries into a new one. |
||||||
239 | * |
||||||
240 | * @param \PHPCollections\Collections\Dictionary $newDictionary |
||||||
241 | * |
||||||
242 | * @throws \InvalidArgumentException |
||||||
243 | * |
||||||
244 | * @return \PHPCollections\Collections\Dictionary |
||||||
245 | */ |
||||||
246 | 1 | public function merge(BaseCollection $newDictionary): BaseCollection |
|||||
247 | { |
||||||
248 | 1 | Checker::isEqual( |
|||||
249 | 1 | $newDictionary->getKeyType(), $this->getKeyType(), |
|||||
0 ignored issues
–
show
The method
getKeyType() does not exist on PHPCollections\Collections\BaseCollection . It seems like you code against a sub-type of PHPCollections\Collections\BaseCollection such as PHPCollections\Collections\Dictionary .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
250 | 1 | sprintf('The new Dictionary key should be of type %s', $this->getKeyType()) |
|||||
251 | ); |
||||||
252 | 1 | Checker::isEqual( |
|||||
253 | 1 | $newDictionary->getValueType(), $this->getValueType(), |
|||||
0 ignored issues
–
show
The method
getValueType() does not exist on PHPCollections\Collections\BaseCollection . It seems like you code against a sub-type of PHPCollections\Collections\BaseCollection such as PHPCollections\Collections\Dictionary .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
254 | 1 | sprintf('The new Dictionary value type should be of type %s', $this->getValueType()) |
|||||
255 | ); |
||||||
256 | |||||||
257 | 1 | return new $this( |
|||||
258 | 1 | $this->keyType, |
|||||
259 | 1 | $this->valueType, |
|||||
260 | 1 | array_merge($this->toArray(), $newDictionary->toArray()) |
|||||
261 | ); |
||||||
262 | } |
||||||
263 | |||||||
264 | /** |
||||||
265 | * Removes a value from the dictionary. |
||||||
266 | * |
||||||
267 | * @param mixed $key |
||||||
268 | * |
||||||
269 | * @throws \OutOfRangeException |
||||||
270 | * |
||||||
271 | * @return bool |
||||||
272 | */ |
||||||
273 | 1 | public function remove($key): void |
|||||
274 | { |
||||||
275 | 1 | if ($this->isEmpty()) { |
|||||
276 | throw new OutOfRangeException('You\'re trying to remove data from an empty collection'); |
||||||
277 | } |
||||||
278 | |||||||
279 | 1 | if (!$this->dataHolder->offsetExists($key)) { |
|||||
280 | throw new OutOfRangeException(sprintf('The %s key does not exists for this collection', $key)); |
||||||
281 | } |
||||||
282 | |||||||
283 | 1 | $this->dataHolder->offsetUnset($key); |
|||||
284 | 1 | } |
|||||
285 | |||||||
286 | /** |
||||||
287 | * Returns a portion of the Dictionary. |
||||||
288 | * |
||||||
289 | * @param int $offset |
||||||
290 | * @param int|null $length |
||||||
291 | * |
||||||
292 | * @return \PHPCollections\Collections\Dictionary|null |
||||||
293 | */ |
||||||
294 | 1 | public function slice(int $offset, ?int $length = null): ?BaseCollection |
|||||
295 | { |
||||||
296 | 1 | $newData = array_slice($this->toArray(), $offset, $length, true); |
|||||
297 | |||||||
298 | 1 | return count($newData) > 0 ? new self($this->keyType, $this->valueType, $newData) : null; |
|||||
299 | } |
||||||
300 | |||||||
301 | /** |
||||||
302 | * Returns a new Dictionary with the |
||||||
303 | * values ordered by a given callback |
||||||
304 | * if couldn't sort returns null. |
||||||
305 | * |
||||||
306 | * @param callable $callback |
||||||
307 | * |
||||||
308 | * @return \PHPCollections\Collections\Dictionary|null |
||||||
309 | */ |
||||||
310 | 1 | public function sort(callable $callback): ?BaseCollection |
|||||
311 | { |
||||||
312 | 1 | $data = $this->toArray(); |
|||||
313 | |||||||
314 | 1 | return uasort($data, $callback) ? new $this($this->keyType, $this->valueType, $data) : null; |
|||||
315 | } |
||||||
316 | |||||||
317 | /** |
||||||
318 | * Returns an array representation |
||||||
319 | * of your dictionary data. |
||||||
320 | * |
||||||
321 | * @return array |
||||||
322 | */ |
||||||
323 | 16 | public function toArray(): array |
|||||
324 | { |
||||||
325 | 16 | $array = []; |
|||||
326 | |||||||
327 | 16 | foreach ($this->dataHolder as $pair) { |
|||||
328 | 15 | $array[$pair->getKey()] = $pair->getValue(); |
|||||
329 | } |
||||||
330 | |||||||
331 | 16 | return $array; |
|||||
332 | } |
||||||
333 | |||||||
334 | /** |
||||||
335 | * Returns a JSON representation |
||||||
336 | * of your dictionary data. |
||||||
337 | * |
||||||
338 | * @return string |
||||||
339 | */ |
||||||
340 | public function toJson(): string |
||||||
341 | { |
||||||
342 | return json_encode($this->dataHolder); |
||||||
343 | } |
||||||
344 | |||||||
345 | /** |
||||||
346 | * Updates the value of one Pair |
||||||
347 | * in the collection. |
||||||
348 | * |
||||||
349 | * @param mixed $key |
||||||
350 | * @param mixed $value |
||||||
351 | * |
||||||
352 | * @throws \InvalidArgumentException |
||||||
353 | * @throws \PHPCollections\Exceptions\InvalidOperationException |
||||||
354 | * |
||||||
355 | * @return bool |
||||||
356 | */ |
||||||
357 | 1 | public function update($key, $value): bool |
|||||
358 | { |
||||||
359 | 1 | $this->validateEntry($key, $value); |
|||||
360 | |||||||
361 | 1 | if (!$this->dataHolder->offsetExists($key)) { |
|||||
362 | 1 | throw new InvalidOperationException('You cannot update a non-existent value'); |
|||||
363 | } |
||||||
364 | |||||||
365 | 1 | $this->dataHolder[$key]->setValue($value); |
|||||
0 ignored issues
–
show
The method
setValue() does not exist on null .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed.
Loading history...
|
|||||||
366 | |||||||
367 | 1 | return $this->dataHolder[$key]->getValue() === $value; |
|||||
368 | } |
||||||
369 | |||||||
370 | /** |
||||||
371 | * Validates that a key and value are of the |
||||||
372 | * specified types in the class. |
||||||
373 | * |
||||||
374 | * @param mixed $key |
||||||
375 | * @param mixed $value |
||||||
376 | * |
||||||
377 | * @throws \InvalidArgumentException |
||||||
378 | * |
||||||
379 | * @return bool |
||||||
380 | */ |
||||||
381 | 27 | private function validateEntry($key, $value): bool |
|||||
382 | { |
||||||
383 | 27 | Checker::valueIsOfType( |
|||||
384 | 27 | $key, $this->keyType, |
|||||
385 | 27 | sprintf( |
|||||
386 | 27 | 'The %s type specified for this dictionary is %s, you cannot pass %s %s', |
|||||
387 | 27 | 'key', $this->keyType, getArticle(gettype($key)), gettype($key) |
|||||
388 | ) |
||||||
389 | ); |
||||||
390 | 27 | Checker::valueIsOfType( |
|||||
391 | 27 | $value, $this->valueType, |
|||||
392 | 27 | sprintf( |
|||||
393 | 27 | 'The %s type specified for this dictionary is %s, you cannot pass %s %s', |
|||||
394 | 27 | 'value', $this->valueType, getArticle(gettype($value)), gettype($value) |
|||||
395 | ) |
||||||
396 | ); |
||||||
397 | |||||||
398 | 27 | return true; |
|||||
399 | } |
||||||
400 | } |
||||||
401 |