Passed
Push — develop ( a803f3...78e549 )
by nguereza
03:14
created

HashMap::get()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 5
rs 10
cc 2
nc 2
nop 1
1
<?php
2
3
/**
4
 * Platine Collection
5
 *
6
 * Platine Collection provides a flexible and simple PHP collection implementation.
7
 *
8
 * This content is released under the MIT License (MIT)
9
 *
10
 * Copyright (c) 2020 Platine Collection
11
 *
12
 * Permission is hereby granted, free of charge, to any person obtaining a copy
13
 * of this software and associated documentation files (the "Software"), to deal
14
 * in the Software without restriction, including without limitation the rights
15
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
 * copies of the Software, and to permit persons to whom the Software is
17
 * furnished to do so, subject to the following conditions:
18
 *
19
 * The above copyright notice and this permission notice shall be included in all
20
 * copies or substantial portions of the Software.
21
 *
22
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
 * SOFTWARE.
29
 */
30
31
/**
32
 *  @file HashMap.php
33
 *
34
 *  The hash map class is like an associative array
35
 *
36
 *  @package    Platine\Collection\Map
37
 *  @author Platine Developers Team
38
 *  @copyright  Copyright (c) 2020
39
 *  @license    http://opensource.org/licenses/MIT  MIT License
40
 *  @link   http://www.iacademy.cf
41
 *  @version 1.0.0
42
 *  @filesource
43
 */
44
45
declare(strict_types=1);
46
47
namespace Platine\Collection\Map;
48
49
use OutOfRangeException;
50
use Platine\Collection\BaseCollection;
51
use Platine\Collection\Exception\InvalidOperationException;
52
use Platine\Collection\MergeableInterface;
53
use Platine\Collection\SortableInterface;
54
use Platine\Collection\TypeCheck;
55
56
/**
57
 * Class HashMap
58
 * @package Platine\Collection\Map
59
 * @template T
60
 * @extends BaseCollection<T>
61
 * @implements MergeableInterface<T>
62
 * @implements SortableInterface<T>
63
 */
64
class HashMap extends BaseCollection implements
65
    MapInterface,
66
    MergeableInterface,
67
    SortableInterface
68
{
69
70
    /**
71
     * The type of the key
72
     * @var mixed
73
     */
74
    protected $keyType;
75
76
    /**
77
     * The type of the value
78
     * @var mixed
79
     */
80
    protected $valueType;
81
82
    /**
83
     * Create new instance
84
     * @param mixed $keyType
85
     * @param mixed $valueType
86
     * @param array<mixed, mixed> $initials
87
     */
88
    public function __construct($keyType, $valueType, array $initials = [])
89
    {
90
        $this->keyType = $keyType;
91
        $this->valueType = $valueType;
92
93
        foreach ($initials as $key => $value) {
94
            $this->validateEntry($key, $value);
95
        }
96
97
        parent::__construct($initials);
98
        $this->initializePairs($initials);
99
    }
100
101
    /**
102
     * {@inheritedoc}
103
     */
104
    public function add($key, $value): void
105
    {
106
        $this->validateEntry($key, $value);
107
        $this->data->offsetSet($key, new Pair($key, $value));
108
    }
109
110
    /**
111
     *
112
     * @param HashMap<T> $collection
113
     * @return HashMap<T>
114
     * @throws InvalidOperationException
115
     */
116
    public function diff(BaseCollection $collection): BaseCollection
117
    {
118
        if (!$collection instanceof self) {
119
            throw new InvalidOperationException(
120
                'You should only compare a Map against another Map'
121
            );
122
        }
123
124
        if ($this->keyType !== $collection->getKeyType()) {
125
            throw new InvalidOperationException(sprintf(
126
                'The key type for this map is [%s], you cannot pass a map with [%s] as key type',
127
                $this->keyType,
128
                $collection->getKeyType()
129
            ));
130
        }
131
132
        if ($this->valueType !== $collection->getValueType()) {
133
            throw new InvalidOperationException(sprintf(
134
                'The value type for this map is [%s], you cannot pass a map with [%s] as value type',
135
                $this->keyType,
136
                $collection->getKeyType()
137
            ));
138
        }
139
140
        $diffValues = array_udiff_uassoc(
141
            $this->all(),
142
            $collection->all(),
143
            function ($a, $b) {
144
                return $a <=> $b;
145
            },
146
            function ($c, $d) {
147
                return $c <=> $d;
148
            }
149
        );
150
151
        return new self($this->keyType, $this->valueType, $diffValues);
152
    }
153
154
    /**
155
     * Return the type of the key
156
     * @return mixed
157
     */
158
    public function getKeyType()
159
    {
160
        return $this->keyType;
161
    }
162
163
    /**
164
     * Return the type of the value
165
     * @return mixed
166
     */
167
    public function getValueType()
168
    {
169
        return $this->valueType;
170
    }
171
172
    /**
173
     *
174
     * @param array<mixed, mixed> $data
175
     * @return void
176
     */
177
    public function fill(array $data): void
178
    {
179
        foreach ($data as $key => $value) {
180
            $this->add($key, $value);
181
        }
182
    }
183
184
    /**
185
     *
186
     * @param callable $callback
187
     * @return $this|null
188
     */
189
    public function filter(callable $callback): ?self
190
    {
191
        $matches = [];
192
193
        foreach ($this->data as $key => $value) {
194
            $val = call_user_func($callback, $value->getKey(), $value->getValue());
195
            if ($val === true) {
196
                $matches[$value->getKey()] = $value->getValue();
197
            }
198
        }
199
200
        return count($matches) > 0
201
                ? new $this($this->keyType, $this->valueType, $matches)
202
                : null;
203
    }
204
205
    /**
206
     *
207
     * @param callable $callback
208
     * @return $this|null
209
     */
210
    public function map(callable $callback): ?self
211
    {
212
        $matches = array_map($callback, $this->all());
213
214
        return count($matches) > 0
215
                ? new $this($this->keyType, $this->valueType, $this->all())
216
                : null;
217
    }
218
219
     /**
220
     * {@inheritedoc}
221
     */
222
    public function equals(BaseCollection $collection): bool
223
    {
224
        if (!$collection instanceof self) {
225
            throw new InvalidOperationException(
226
                'You should only compare an map against another map'
227
            );
228
        }
229
230
        return $this->all() == $collection->all();
231
    }
232
233
    /**
234
     *
235
     * @param callable $callback
236
     * @return void
237
     */
238
    public function forEach(callable $callback): void
239
    {
240
        $data = $this->all();
241
        array_walk($data, $callback);
242
243
        $this->initializePairs($data);
244
    }
245
246
     /**
247
     * {@inheritedoc}
248
     */
249
    public function get($key)
250
    {
251
        return $this->data->offsetExists($key)
252
               ? $this->data->offsetGet($key)->getValue()
253
               : null;
254
    }
255
256
     /**
257
     * {@inheritedoc}
258
      * @param HashMap<T> $collection
259
     */
260
    public function merge(BaseCollection $collection): BaseCollection
261
    {
262
        TypeCheck::isEqual(
263
            $this->getKeyType(),
264
            $collection->getKeyType(),
0 ignored issues
show
Bug introduced by
The method getKeyType() does not exist on Platine\Collection\BaseCollection. It seems like you code against a sub-type of Platine\Collection\BaseCollection such as Platine\Collection\Map\HashMap. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

264
            $collection->/** @scrutinizer ignore-call */ 
265
                         getKeyType(),
Loading history...
265
            sprintf(
266
                'The new map key should be of type %s',
267
                $this->keyType
268
            )
269
        );
270
271
        TypeCheck::isEqual(
272
            $this->getValueType(),
273
            $collection->getValueType(),
0 ignored issues
show
Bug introduced by
The method getValueType() does not exist on Platine\Collection\BaseCollection. It seems like you code against a sub-type of Platine\Collection\BaseCollection such as Platine\Collection\Map\HashMap. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

273
            $collection->/** @scrutinizer ignore-call */ 
274
                         getValueType(),
Loading history...
274
            sprintf(
275
                'The new map value should be of type %s',
276
                $this->valueType
277
            )
278
        );
279
280
        return new $this(
281
            $this->keyType,
282
            $this->valueType,
283
            array_merge($this->all(), $collection->all())
284
        );
285
    }
286
287
     /**
288
     * {@inheritedoc}
289
     */
290
    public function remove($key): void
291
    {
292
        if ($this->isEmpty()) {
293
            throw new OutOfRangeException('The collection is empty');
294
        }
295
296
        if (!$this->data->offsetExists($key)) {
297
            throw new OutOfRangeException(sprintf(
298
                'The collection key [%s] does not exists',
299
                $key
300
            ));
301
        }
302
303
        $this->data->offsetUnset($key);
304
    }
305
306
     /**
307
     * {@inheritedoc}
308
     */
309
    public function slice(int $offset, ?int $length): ?BaseCollection
310
    {
311
        $newData = array_slice($this->all(), $offset, $length, true);
312
313
        return count($newData) > 0
314
            ? new $this(
315
                $this->keyType,
316
                $this->valueType,
317
                $newData
318
            )
319
            : null;
320
    }
321
322
     /**
323
     * {@inheritedoc}
324
     */
325
    public function sort(callable $callback): ?BaseCollection
326
    {
327
        $data = $this->all();
328
329
        return uasort($data, $callback)
330
                ? new $this(
331
                    $this->keyType,
332
                    $this->valueType,
333
                    $data
334
                )
335
                : null;
336
    }
337
338
     /**
339
     * {@inheritedoc}
340
     */
341
    public function update($key, $value): bool
342
    {
343
        $this->validateEntry($key, $value);
344
345
        if (!$this->data->offsetExists($key)) {
346
            throw new OutOfRangeException(sprintf(
347
                'The collection key [%s] does not exists',
348
                $key
349
            ));
350
        }
351
352
        $this->data[$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

352
        $this->data[$key]->/** @scrutinizer ignore-call */ 
353
                           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...
353
354
        return $this->data[$key]->getValue() === $value;
355
    }
356
357
    /**
358
     *
359
     * @return array<mixed, mixed>
360
     */
361
    public function all(): array
362
    {
363
        $data = [];
364
        foreach ($this->data as $pair) {
365
            $data[$pair->getKey()] = $pair->getValue();
366
        }
367
368
        return $data;
369
    }
370
371
    /**
372
     *
373
     * @return string
374
     */
375
    public function toJson(): string
376
    {
377
        $json = json_encode($this->data);
378
        return $json === false ? '' : $json;
379
    }
380
381
382
    /**
383
     * Validate the type of key and value
384
     * @param mixed $key
385
     * @param mixed $value
386
     *
387
     * @return bool
388
     */
389
    protected function validateEntry($key, $value): bool
390
    {
391
        TypeCheck::isValueOf(
392
            $key,
393
            $this->keyType,
394
            sprintf(
395
                'The key type specified for this map is [%s], you cannot pass [%s]',
396
                $this->keyType,
397
                gettype($key)
398
            )
399
        );
400
401
        TypeCheck::isValueOf(
402
            $key,
403
            $this->valueType,
404
            sprintf(
405
                'The value type specified for this map is [%s], you cannot pass [%s]',
406
                $this->valueType,
407
                gettype($value)
408
            )
409
        );
410
411
        return false;
412
    }
413
414
    /**
415
     * Initialize the pair values
416
     * @param array<mixed, mixed> $data
417
     * @return void
418
     */
419
    protected function initializePairs(array $data): void
420
    {
421
        foreach ($data as $key => $value) {
422
            $this->data[$key] = new Pair($key, $value);
423
        }
424
    }
425
}
426