Passed
Push — master ( ede5c4...d0b3a2 )
by Max
02:45
created

Dictionary::validateEntry()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 18
ccs 12
cts 12
cp 1
rs 9.9
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PHPCollections\Collections;
6
7
use PHPCollections\Checker;
8
use PHPCollections\Exceptions\InvalidOperationException;
9
use PHPCollections\Interfaces\DictionaryInterface;
10
use PHPCollections\Interfaces\MergeableInterface;
11
use PHPCollections\Interfaces\SortableInterface;
12
13
/**
14
 * A Pair object collection
15
 * represented by a generic
16
 * type key and value.
17
 */
18
class Dictionary extends BaseCollection implements DictionaryInterface, MergeableInterface, SortableInterface
19
{
20
    /**
21
     * The type of the keys
22
     * for this dictionary.
23
     *
24
     * @var mixed
25
     */
26
    private $keyType;
27
28
    /**
29
     * The type of the values
30
     * for this dictionary.
31
     *
32
     * @var mixed
33
     */
34
    private $valueType;
35
36
    /**
37
     * Creates a new Dictionary.
38
     *
39
     * @param mixed $keyType
40
     * @param mixed $valueType
41
     * @param array $data
42
     *
43
     * @throws \InvalidArgumentException
44
     */
45 22
    public function __construct($keyType, $valueType, array $data = [])
46
    {
47 22
        $this->keyType = $keyType;
48 22
        $this->valueType = $valueType;
49
50 22
        foreach ($data as $key => $value) {
51 8
            $this->validateEntry($key, $value);
52
        }
53
54 22
        parent::__construct($data);
55 22
        $this->initializePairs($data);
56 22
    }
57
58
    /**
59
     * Adds a new value to the dictionary.
60
     *
61
     * @param mixed $key
62
     * @param mixed $value
63
     *
64
     * @throws \InvalidArgumentException
65
     *
66
     * @return void
67
     */
68 22
    public function add($key, $value): void
69
    {
70 22
        $this->validateEntry($key, $value);
71 22
        $this->dataHolder->offsetSet($key, new Pair($key, $value));
72 22
    }
73
74
    /**
75
     * Gets the difference between two Dictionary.
76
     *
77
     * @param \PHPCollections\Collections\Dictionary $newDictionary
78
     *
79
     * @throws \PHPCollections\Exceptions\InvalidOperationException
80
     *
81
     * @return \PHPCollections\Collections\Dictionary
82
     */
83 4
    public function diff(BaseCollection $newDictionary): BaseCollection
84
    {
85 4
        if (!is_a($newDictionary, self::class)) {
86 1
            throw new InvalidOperationException('You should only compare a Dictionary against another Dictionary');
87
        }
88
89 3
        if ($this->keyType !== $newDictionary->getKeyType()) {
90 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()));
91
        }
92
93 2
        if ($this->valueType !== $newDictionary->getValueType()) {
94 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()));
95
        }
96
97
        $diffValues = array_udiff_uassoc($this->toArray(), $newDictionary->toArray(), function ($firstValue, $secondValue) {
98 1
            return $firstValue <=> $secondValue;
99
        }, function ($firstKey, $secondKey) {
100 1
            return $firstKey <=> $secondKey;
101 1
        });
102
103 1
        return new self($this->keyType, $this->valueType, $diffValues);
104
    }
105
106
    /**
107
     * Filters the collection applying
108
     * a given callback.
109
     *
110
     * @param callable $callback
111
     *
112
     * @return \PHPCollections\Collections\Dictionary|null
113
     */
114 1
    public function filter(callable $callback): ?self
115
    {
116 1
        $matcheds = [];
117
118 1
        foreach ($this->dataHolder as $key => $value) {
119 1
            if (call_user_func($callback, $value->getKey(), $value->getValue()) === true) {
120 1
                $matcheds[$value->getKey()] = $value->getValue();
121
            }
122
        }
123
124 1
        return count($matcheds) > 0 ? new $this($this->keyType, $this->valueType, $matcheds) : null;
125
    }
126
127
    /**
128
     * Iterates over every element of the collection.
129
     *
130
     * @param callable $callback
131
     *
132
     * @return void
133
     */
134 1
    public function forEach(callable $callback): void
135
    {
136 1
        $data = $this->toArray();
137
138 1
        array_walk($data, $callback);
139 1
        $this->initializePairs($data);
140 1
    }
141
142
    /**
143
     * Returns the value for the specified
144
     * key or null if it's not defined.
145
     *
146
     * @param mixed $key
147
     *
148
     * @return mixed|null
149
     */
150 5
    public function get($key)
151
    {
152 5
        return $this->dataHolder->offsetExists($key) ?
153 5
               $this->dataHolder->offsetGet($key)->getValue() :
154 5
               null;
155
    }
156
157
    /**
158
     * Returns the key type for this collection.
159
     *
160
     * @return mixed
161
     */
162 5
    public function getKeyType()
163
    {
164 5
        return $this->keyType;
165
    }
166
167
    /**
168
     * Returns the key value for this collection.
169
     *
170
     * @return mixed
171
     */
172 4
    public function getValueType()
173
    {
174 4
        return $this->valueType;
175
    }
176
177
    /**
178
     * Populates the container with Pair objects.
179
     *
180
     * @param array $data
181
     *
182
     * @return void
183
     */
184 22
    private function initializePairs(array $data): void
185
    {
186 22
        foreach ($data as $key => $value) {
187 9
            $this->dataHolder[$key] = new Pair($key, $value);
188
        }
189 22
    }
190
191
    /**
192
     * Updates elements in the collection by
193
     * applying a given callback function.
194
     *
195
     * @param callable $callback
196
     *
197
     * @return \PHPCollections\Collections\Dictionary|null
198
     */
199 1
    public function map(callable $callback): ?self
200
    {
201 1
        $matcheds = array_map($callback, $this->toArray());
202
203 1
        return count($matcheds) > 0 ? new $this($this->keyType, $this->valueType, $this->toArray()) : null;
204
    }
205
206
    /**
207
     * Merges two dictionaries into a new one.
208
     *
209
     * @param \PHPCollections\Collections\Dictionary $newDictionary
210
     *
211
     * @throws \InvalidArgumentException
212
     *
213
     * @return \PHPCollections\Collections\Dictionary
214
     */
215 1
    public function merge(BaseCollection $newDictionary): BaseCollection
216
    {
217 1
        Checker::isEqual(
218 1
            $newDictionary->getKeyType(), $this->getKeyType(),
0 ignored issues
show
Bug introduced by
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 ignore-call  annotation

218
            $newDictionary->/** @scrutinizer ignore-call */ 
219
                            getKeyType(), $this->getKeyType(),
Loading history...
219 1
            sprintf('The new Dictionary key should be of type %s', $this->getKeyType())
220
        );
221 1
        Checker::isEqual(
222 1
            $newDictionary->getValueType(), $this->getValueType(),
0 ignored issues
show
Bug introduced by
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 ignore-call  annotation

222
            $newDictionary->/** @scrutinizer ignore-call */ 
223
                            getValueType(), $this->getValueType(),
Loading history...
223 1
            sprintf('The new Dictionary value type should be of type %s', $this->getValueType())
224
        );
225
226 1
        return new $this(
227 1
            $this->keyType,
228 1
            $this->valueType,
229 1
            array_merge($this->toArray(), $newDictionary->toArray())
230
        );
231
    }
232
233
    /**
234
     * Removes a value from the dictionary.
235
     *
236
     * @param mixed $key
237
     *
238
     * @throws \OutOfRangeException
239
     *
240
     * @return bool
241
     */
242 1
    public function remove($key): void
243
    {
244 1
        if ($this->isEmpty()) {
245
            throw new OutOfRangeException('You\'re trying to remove data from an empty collection');
0 ignored issues
show
Bug introduced by
The type PHPCollections\Collections\OutOfRangeException was not found. Did you mean OutOfRangeException? If so, make sure to prefix the type with \.
Loading history...
246
        }
247
248 1
        if (!$this->dataHolder->offsetExists($key)) {
249
            throw new OutOfRangeException(sprintf('The %s key does not exists for this collection', $key));
250
        }
251
252 1
        $this->dataHolder->offsetUnset($key);
253 1
    }
254
255
    /**
256
     * Returns a portion of the Dictionary.
257
     *
258
     * @param int      $offset
259
     * @param int|null $length
260
     *
261
     * @return PHPCollections\Collections\Dictionary|null
0 ignored issues
show
Bug introduced by
The type PHPCollections\Collectio...\Collections\Dictionary was not found. Did you mean PHPCollections\Collections\Dictionary? If so, make sure to prefix the type with \.
Loading history...
262
     */
263 1
    public function slice(int $offset, ?int $length = null): ?BaseCollection
264
    {
265 1
        $newData = array_slice($this->toArray(), $offset, $length, true);
266
267 1
        return count($newData) > 0 ? new self($this->keyType, $this->valueType, $newData) : null;
0 ignored issues
show
Bug Best Practice introduced by
The expression return count($newData) >...eType, $newData) : null also could return the type PHPCollections\Collections\Dictionary which is incompatible with the documented return type PHPCollections\Collectio...ections\Dictionary|null.
Loading history...
268
    }
269
270
    /**
271
     * Returns a new Dictionary with the
272
     * values ordered by a given callback
273
     * if couldn't sort returns null.
274
     *
275
     * @param callable $callback
276
     *
277
     * @return \PHPCollections\Collections\Dictionary|null
278
     */
279 1
    public function sort(callable $callback): ?BaseCollection
280
    {
281 1
        $data = $this->toArray();
282
283 1
        return uasort($data, $callback) ? new $this($this->keyType, $this->valueType, $data) : null;
284
    }
285
286
    /**
287
     * Returns an array representation
288
     * of your dictionary data.
289
     *
290
     * @return array
291
     */
292 14
    public function toArray(): array
293
    {
294 14
        $array = [];
295
296 14
        foreach ($this->dataHolder as $pair) {
297 13
            $array[$pair->getKey()] = $pair->getValue();
298
        }
299
300 14
        return $array;
301
    }
302
303
    /**
304
     * Returns a JSON representation
305
     * of your dictionary data.
306
     *
307
     * @return string
308
     */
309
    public function toJson(): string
310
    {
311
        return json_encode($this->dataHolder);
312
    }
313
314
    /**
315
     * Updates the value of one Pair
316
     * in the collection.
317
     *
318
     * @param mixed $key
319
     * @param mixed $value
320
     *
321
     * @throws \InvalidArgumentException
322
     * @throws \PHPCollections\Exceptions\InvalidOperationException
323
     *
324
     * @return bool
325
     */
326 1
    public function update($key, $value): bool
327
    {
328 1
        $this->validateEntry($key, $value);
329
330 1
        if (!$this->dataHolder->offsetExists($key)) {
331 1
            throw new InvalidOperationException('You cannot update a non-existent value');
332
        }
333
334 1
        $this->dataHolder[$key]->setValue($value);
0 ignored issues
show
Bug introduced by
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 ignore-call  annotation

334
        $this->dataHolder[$key]->/** @scrutinizer ignore-call */ 
335
                                 setValue($value);

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...
335
336 1
        return $this->dataHolder[$key]->getValue() === $value;
337
    }
338
339
    /**
340
     * Validates that a key and value are of the
341
     * specified types in the class.
342
     *
343
     * @param mixed $key
344
     * @param mixed $value
345
     *
346
     * @throws \InvalidArgumentException
347
     *
348
     * @return bool
349
     */
350 22
    private function validateEntry($key, $value): bool
351
    {
352 22
        Checker::valueIsOfType(
353 22
            $key, $this->keyType,
354 22
            sprintf(
355 22
                'The %s type specified for this dictionary is %s, you cannot pass %s %s',
356 22
                'key', $this->keyType, getArticle(gettype($key)), gettype($key)
357
            )
358
        );
359 22
        Checker::valueIsOfType(
360 22
            $value, $this->valueType,
361 22
            sprintf(
362 22
                'The %s type specified for this dictionary is %s, you cannot pass %s %s',
363 22
                'value', $this->valueType, getArticle(gettype($value)), gettype($value)
364
            )
365
        );
366
367 22
        return true;
368
    }
369
}
370