Issues (4)

src/Map/HashMap.php (3 issues)

Labels
Severity
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   https://www.platine-php.com
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
     * Create new instance
71
     * @param mixed $keyType
72
     * @param mixed $valueType
73
     * @param array<mixed, T> $initials
74
     */
75
    public function __construct(
76
        protected mixed $keyType,
77
        protected mixed $valueType,
78
        array $initials = []
79
    ) {
80
        foreach ($initials as $key => $value) {
81
            $this->validateEntry($key, $value);
82
        }
83
84
        parent::__construct($initials);
85
        $this->initializePairs($initials);
86
    }
87
88
    /**
89
     *
90
     * @param mixed $key
91
     * @param T $value
0 ignored issues
show
The type Platine\Collection\Map\T was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
92
     * @return void
93
     */
94
    public function add(mixed $key, mixed $value): void
95
    {
96
        $this->validateEntry($key, $value);
97
        $this->data->offsetSet($key, new Pair($key, $value));
98
    }
99
100
    /**
101
     *
102
     * @param BaseCollection<T> $collection
103
     * @return HashMap<T>
104
     * @throws InvalidOperationException
105
     */
106
    public function diff(BaseCollection $collection): BaseCollection
107
    {
108
        if (!$collection instanceof self) {
109
            throw new InvalidOperationException(
110
                'You should only compare a Map against another Map'
111
            );
112
        }
113
114
        if ($this->keyType !== $collection->getKeyType()) {
115
            throw new InvalidOperationException(sprintf(
116
                'The key type for this map is [%s], you cannot pass a map with [%s] as key type',
117
                $this->keyType,
118
                $collection->getKeyType()
119
            ));
120
        }
121
122
        if ($this->valueType !== $collection->getValueType()) {
123
            throw new InvalidOperationException(sprintf(
124
                'The value type for this map is [%s], you cannot pass a map with [%s] as value type',
125
                $this->keyType,
126
                $collection->getKeyType()
127
            ));
128
        }
129
130
        $diffValues = array_udiff_uassoc(
131
            $this->all(),
132
            $collection->all(),
133
            function ($a, $b) {
134
                return $a <=> $b;
135
            },
136
            function ($c, $d) {
137
                return $c <=> $d;
138
            }
139
        );
140
141
        return new self($this->keyType, $this->valueType, $diffValues);
142
    }
143
144
    /**
145
     * Return the type of the key
146
     * @return mixed
147
     */
148
    public function getKeyType(): mixed
149
    {
150
        return $this->keyType;
151
    }
152
153
    /**
154
     * Return the type of the value
155
     * @return mixed
156
     */
157
    public function getValueType(): mixed
158
    {
159
        return $this->valueType;
160
    }
161
162
    /**
163
     *
164
     * @param array<mixed, T> $data
165
     * @return void
166
     */
167
    public function fill(array $data): void
168
    {
169
        foreach ($data as $key => $value) {
170
            $this->add($key, $value);
171
        }
172
    }
173
174
    /**
175
     *
176
     * @param callable $callback
177
     * @return $this|null
178
     */
179
    public function filter(callable $callback): ?self
180
    {
181
        $matches = [];
182
183
        foreach ($this->data as $value) {
184
            $val = call_user_func($callback, $value->getKey(), $value->getValue());
185
            if ($val === true) {
186
                $matches[$value->getKey()] = $value->getValue();
187
            }
188
        }
189
190
        return count($matches) > 0
191
                ? new $this($this->keyType, $this->valueType, $matches)
192
                : null;
193
    }
194
195
    /**
196
     *
197
     * @param callable $callback
198
     * @return $this|null
199
     */
200
    public function map(callable $callback): ?self
201
    {
202
        $matches = array_map($callback, $this->all());
203
204
        return count($matches) > 0
205
                ? new $this($this->keyType, $this->valueType, $this->all())
206
                : null;
207
    }
208
209
     /**
210
      *
211
      * @param BaseCollection<T> $collection
212
      * @return bool
213
      * @throws InvalidOperationException
214
      */
215
    public function equals(BaseCollection $collection): bool
216
    {
217
        if (!$collection instanceof self) {
218
            throw new InvalidOperationException(
219
                'You should only compare an map against another map'
220
            );
221
        }
222
223
        return $this->all() == $collection->all();
224
    }
225
226
    /**
227
     *
228
     * @param callable $callback
229
     * @return void
230
     */
231
    public function forEach(callable $callback): void
232
    {
233
        $data = $this->all();
234
        array_walk($data, $callback);
235
236
        $this->initializePairs($data);
237
    }
238
239
     /**
240
      * Return the value for given key
241
      * @param mixed $key
242
      * @return T|null
243
      */
244
    public function get(mixed $key): mixed
245
    {
246
        return $this->data->offsetExists($key)
247
               ? $this->data->offsetGet($key)->getValue()
248
               : null;
249
    }
250
251
     /**
252
     * {@inheritedoc}
253
      * @param HashMap<T> $collection
254
      * @return HashMap<T>
255
     */
256
    public function merge(BaseCollection $collection): BaseCollection
257
    {
258
        TypeCheck::isEqual(
259
            $this->getKeyType(),
260
            $collection->getKeyType(),
0 ignored issues
show
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

260
            $collection->/** @scrutinizer ignore-call */ 
261
                         getKeyType(),
Loading history...
261
            sprintf(
262
                'The new map key should be of type %s',
263
                $this->keyType
264
            )
265
        );
266
267
        TypeCheck::isEqual(
268
            $this->getValueType(),
269
            $collection->getValueType(),
0 ignored issues
show
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

269
            $collection->/** @scrutinizer ignore-call */ 
270
                         getValueType(),
Loading history...
270
            sprintf(
271
                'The new map value should be of type %s',
272
                $this->valueType
273
            )
274
        );
275
276
        return new $this(
277
            $this->keyType,
278
            $this->valueType,
279
            array_merge($this->all(), $collection->all())
280
        );
281
    }
282
283
    /**
284
     * {@inheritedoc}
285
     */
286
    public function first(): mixed
287
    {
288
        throw new InvalidOperationException('Can not call this method in map');
289
    }
290
291
    /**
292
     * {@inheritedoc}
293
     */
294
    public function last(): mixed
295
    {
296
        throw new InvalidOperationException('Can not call this method in map');
297
    }
298
299
     /**
300
     * {@inheritedoc}
301
     */
302
    public function remove(mixed $key): void
303
    {
304
        if ($this->isEmpty()) {
305
            throw new OutOfRangeException('The collection is empty');
306
        }
307
308
        if (!$this->data->offsetExists($key)) {
309
            throw new OutOfRangeException(sprintf(
310
                'The collection key [%s] does not exists',
311
                $key
312
            ));
313
        }
314
315
        $this->data->offsetUnset($key);
316
    }
317
318
     /**
319
      *
320
      * @param int $offset
321
      * @param int|null $length
322
      * @return HashMap<T>|null
323
      */
324
    public function slice(int $offset, ?int $length = null): ?BaseCollection
325
    {
326
        $newData = array_slice($this->all(), $offset, $length, true);
327
328
        return count($newData) > 0
329
            ? new $this(
330
                $this->keyType,
331
                $this->valueType,
332
                $newData
333
            )
334
            : null;
335
    }
336
337
     /**
338
      *
339
      * @param callable $callback
340
      * @return HashMap<T>|null
341
      */
342
    public function sort(callable $callback): ?BaseCollection
343
    {
344
        $data = $this->all();
345
346
        return uasort($data, $callback)
347
                ? new $this(
348
                    $this->keyType,
349
                    $this->valueType,
350
                    $data
351
                )
352
                : null;
353
    }
354
355
    /**
356
     *
357
     * @param mixed $key
358
     * @param T $value
359
     * @return bool
360
     * @throws OutOfRangeException
361
     */
362
    public function update(mixed $key, mixed $value): bool
363
    {
364
        $this->validateEntry($key, $value);
365
366
        if (!$this->data->offsetExists($key)) {
367
            throw new OutOfRangeException(sprintf(
368
                'The collection key [%s] does not exists',
369
                $key
370
            ));
371
        }
372
373
        $this->data[$key]->setValue($value);
374
375
        return $this->data[$key]->getValue() === $value;
376
    }
377
378
    /**
379
     *
380
     * @return array<mixed, T>
381
     */
382
    public function all(): array
383
    {
384
        $data = [];
385
        foreach ($this->data as $pair) {
386
            $data[$pair->getKey()] = $pair->getValue();
387
        }
388
389
        return $data;
390
    }
391
392
    /**
393
     *
394
     * @return string
395
     */
396
    public function toJson(): string
397
    {
398
        /* Thank to interface JsonSerializable */
399
        $json = json_encode($this);
400
        return $json === false ? '' : $json;
401
    }
402
403
404
    /**
405
     * Validate the type of key and value
406
     * @param mixed $key
407
     * @param mixed $value
408
     *
409
     * @return bool
410
     */
411
    protected function validateEntry(mixed $key, mixed $value): bool
412
    {
413
        TypeCheck::isValueOf(
414
            $key,
415
            $this->keyType,
416
            sprintf(
417
                'The key type specified for this map is [%s], you cannot pass [%s]',
418
                $this->keyType,
419
                gettype($key)
420
            )
421
        );
422
423
        TypeCheck::isValueOf(
424
            $value,
425
            $this->valueType,
426
            sprintf(
427
                'The value type specified for this map is [%s], you cannot pass [%s]',
428
                $this->valueType,
429
                gettype($value)
430
            )
431
        );
432
433
        return false;
434
    }
435
436
    /**
437
     * Initialize the pair values
438
     * @param array<mixed, mixed> $data
439
     * @return void
440
     */
441
    protected function initializePairs(array $data): void
442
    {
443
        foreach ($data as $key => $value) {
444
            $this->data[$key] = new Pair($key, $value);
445
        }
446
    }
447
}
448