Issues (8)

src/Collections/Dictionary.php (4 issues)

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
The expression return $this->toArray() ...ewDictionary->toArray() returns the type boolean which is incompatible with the documented return type PHPCollections\Collections\Dictionary.
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 ignore-call  annotation

249
            $newDictionary->/** @scrutinizer ignore-call */ 
250
                            getKeyType(), $this->getKeyType(),
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 ignore-call  annotation

253
            $newDictionary->/** @scrutinizer ignore-call */ 
254
                            getValueType(), $this->getValueType(),
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 ignore-call  annotation

365
        $this->dataHolder[$key]->/** @scrutinizer ignore-call */ 
366
                                 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...
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