Passed
Push — develop ( eb0d73...33e885 )
by nguereza
02:57
created

HashMap::first()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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

273
            $collection->/** @scrutinizer ignore-call */ 
274
                         getKeyType(),
Loading history...
274
            sprintf(
275
                'The new map key should be of type %s',
276
                $this->keyType
277
            )
278
        );
279
280
        TypeCheck::isEqual(
281
            $this->getValueType(),
282
            $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

282
            $collection->/** @scrutinizer ignore-call */ 
283
                         getValueType(),
Loading history...
283
            sprintf(
284
                'The new map value should be of type %s',
285
                $this->valueType
286
            )
287
        );
288
289
        return new $this(
290
            $this->keyType,
291
            $this->valueType,
292
            array_merge($this->all(), $collection->all())
293
        );
294
    }
295
296
    /**
297
     * {@inheritedoc}
298
     */
299
    public function first()
300
    {
301
        throw new InvalidOperationException('Can not call this method in map');
302
    }
303
304
    /**
305
     * {@inheritedoc}
306
     */
307
    public function last()
308
    {
309
        throw new InvalidOperationException('Can not call this method in map');
310
    }
311
312
     /**
313
     * {@inheritedoc}
314
     */
315
    public function remove($key): void
316
    {
317
        if ($this->isEmpty()) {
318
            throw new OutOfRangeException('The collection is empty');
319
        }
320
321
        if (!$this->data->offsetExists($key)) {
322
            throw new OutOfRangeException(sprintf(
323
                'The collection key [%s] does not exists',
324
                $key
325
            ));
326
        }
327
328
        $this->data->offsetUnset($key);
329
    }
330
331
     /**
332
      *
333
      * @param int $offset
334
      * @param int|null $length
335
      * @return HashMap<T>|null
336
      */
337
    public function slice(int $offset, ?int $length = null): ?BaseCollection
338
    {
339
        $newData = array_slice($this->all(), $offset, $length, true);
340
341
        return count($newData) > 0
342
            ? new $this(
343
                $this->keyType,
344
                $this->valueType,
345
                $newData
346
            )
347
            : null;
348
    }
349
350
     /**
351
      *
352
      * @param callable $callback
353
      * @return HashMap<T>|null
354
      */
355
    public function sort(callable $callback): ?BaseCollection
356
    {
357
        $data = $this->all();
358
359
        return uasort($data, $callback)
360
                ? new $this(
361
                    $this->keyType,
362
                    $this->valueType,
363
                    $data
364
                )
365
                : null;
366
    }
367
368
    /**
369
     *
370
     * @param mixed $key
371
     * @param T $value
372
     * @return bool
373
     * @throws OutOfRangeException
374
     */
375
    public function update($key, $value): bool
376
    {
377
        $this->validateEntry($key, $value);
378
379
        if (!$this->data->offsetExists($key)) {
380
            throw new OutOfRangeException(sprintf(
381
                'The collection key [%s] does not exists',
382
                $key
383
            ));
384
        }
385
386
        $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

386
        $this->data[$key]->/** @scrutinizer ignore-call */ 
387
                           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...
387
388
        return $this->data[$key]->getValue() === $value;
389
    }
390
391
    /**
392
     *
393
     * @return array<mixed, T>
394
     */
395
    public function all(): array
396
    {
397
        $data = [];
398
        foreach ($this->data as $pair) {
399
            $data[$pair->getKey()] = $pair->getValue();
400
        }
401
402
        return $data;
403
    }
404
405
    /**
406
     *
407
     * @return string
408
     */
409
    public function toJson(): string
410
    {
411
        /* Thank to interface JsonSerializable */
412
        $json = json_encode($this);
413
        return $json === false ? '' : $json;
414
    }
415
416
417
    /**
418
     * Validate the type of key and value
419
     * @param mixed $key
420
     * @param mixed $value
421
     *
422
     * @return bool
423
     */
424
    protected function validateEntry($key, $value): bool
425
    {
426
        TypeCheck::isValueOf(
427
            $key,
428
            $this->keyType,
429
            sprintf(
430
                'The key type specified for this map is [%s], you cannot pass [%s]',
431
                $this->keyType,
432
                gettype($key)
433
            )
434
        );
435
436
        TypeCheck::isValueOf(
437
            $value,
438
            $this->valueType,
439
            sprintf(
440
                'The value type specified for this map is [%s], you cannot pass [%s]',
441
                $this->valueType,
442
                gettype($value)
443
            )
444
        );
445
446
        return false;
447
    }
448
449
    /**
450
     * Initialize the pair values
451
     * @param array<mixed, mixed> $data
452
     * @return void
453
     */
454
    protected function initializePairs(array $data): void
455
    {
456
        foreach ($data as $key => $value) {
457
            $this->data[$key] = new Pair($key, $value);
458
        }
459
    }
460
}
461