Issues (158)

src/EnumMap.php (38 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace MabeEnum;
6
7
use ArrayAccess;
8
use Countable;
9
use InvalidArgumentException;
10
use Iterator;
11
use IteratorAggregate;
12
use UnexpectedValueException;
13
14
/**
15
 * A map of enumerators and data values (EnumMap<T of Enum, mixed>).
16
 *
17
 * @template T of Enum
18
 * @implements ArrayAccess<T, mixed>
19
 * @implements IteratorAggregate<T, mixed>
20
 *
21
 * @copyright 2020, Marc Bennewitz
22
 * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
23
 * @link http://github.com/marc-mabe/php-enum for the canonical source repository
24
 */
25
class EnumMap implements ArrayAccess, Countable, IteratorAggregate
26
{
27
    /**
28
     * The classname of the enumeration type
29
     * @var class-string<T>
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<T> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<T>.
Loading history...
30
     */
31
    private $enumeration;
32
33
    /**
34
     * Internal map of ordinal number and data value
35
     * @var array<int, mixed>
36
     */
37
    private $map = [];
38
39
    /**
40
     * Constructor
41
     * @param class-string<T> $enumeration The classname of the enumeration type
0 ignored issues
show
Expected 49 spaces after parameter type; 1 found
Loading history...
Documentation Bug introduced by
The doc comment class-string<T> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<T>.
Loading history...
42
     * @param null|iterable<T|null|bool|int|float|string|array<mixed>, mixed> $map Initialize map
0 ignored issues
show
Expected 9 spaces after parameter name; 1 found
Loading history...
43
     * @throws InvalidArgumentException
44
     */
45 21
    public function __construct(string $enumeration, iterable $map = null)
46
    {
47 21
        if (!\is_subclass_of($enumeration, Enum::class)) {
48 1
            throw new InvalidArgumentException(\sprintf(
49 1
                '%s can handle subclasses of %s only',
50 1
                 __CLASS__,
51 1
                Enum::class
52
            ));
53
        }
54 20
        $this->enumeration = $enumeration;
55
56 20
        if ($map) {
0 ignored issues
show
$map is of type iterable|null, thus it always evaluated to false.
Loading history...
57 3
            $this->addIterable($map);
58
        }
59 20
    }
60
61
    /**
62
     * Add virtual private property "__pairs" with a list of key-value-pairs
63
     * to the result of var_dump.
64
     *
65
     * This helps debugging as internally the map is using the ordinal number.
66
     *
67
     * @return array<string, mixed>
68
     */
69 1
    public function __debugInfo(): array {
70 1
        $dbg = (array)$this;
71 1
        $dbg["\0" . self::class . "\0__pairs"] = array_map(function ($k, $v) {
72 1
            return [$k, $v];
73 1
        }, $this->getKeys(), $this->getValues());
74 1
        return $dbg;
75
    }
76
77
    /* write access (mutable) */
78
79
    /**
80
     * Adds the given enumerator (object or value) mapping to the specified data value.
81
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
0 ignored issues
show
Missing parameter comment
Loading history...
The type MabeEnum\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...
82
     * @param mixed                                                 $value
0 ignored issues
show
Missing parameter comment
Loading history...
Expected 37 spaces after parameter type; 49 found
Loading history...
83
     * @throws InvalidArgumentException On an invalid given enumerator
84
     * @see offsetSet()
85
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
86 13
    public function add($enumerator, $value): void
87
    {
88 13
        $ord = ($this->enumeration)::get($enumerator)->getOrdinal();
89 12
        $this->map[$ord] = $value;
90 12
    }
91
92
    /**
93
     * Adds the given iterable, mapping enumerators (objects or values) to data values.
94
     * @param iterable<T|null|bool|int|float|string|array<mixed>, mixed> $map
0 ignored issues
show
Missing parameter comment
Loading history...
95
     * @throws InvalidArgumentException On an invalid given enumerator
96
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
97 5
    public function addIterable(iterable $map): void
98
    {
99 5
        $innerMap = $this->map;
100 5
        foreach ($map as $enumerator => $value) {
101 5
            $ord = ($this->enumeration)::get($enumerator)->getOrdinal();
102 5
            $innerMap[$ord] = $value;
103
        }
104 5
        $this->map = $innerMap;
105 5
    }
106
107
    /**
108
     * Removes the given enumerator (object or value) mapping.
109
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
0 ignored issues
show
Missing parameter comment
Loading history...
110
     * @throws InvalidArgumentException On an invalid given enumerator
111
     * @see offsetUnset()
112
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
113 5
    public function remove($enumerator): void
114
    {
115 5
        $ord = ($this->enumeration)::get($enumerator)->getOrdinal();
116 5
        unset($this->map[$ord]);
117 5
    }
118
119
    /**
120
     * Removes the given iterable enumerator (object or value) mappings.
121
     * @param iterable<T|null|bool|int|float|string|array<mixed>> $enumerators
0 ignored issues
show
Missing parameter comment
Loading history...
122
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
@throws tag comment must end with a full stop
Loading history...
123
     */
0 ignored issues
show
Missing @return tag in function comment
Loading history...
124 3
    public function removeIterable(iterable $enumerators): void
125
    {
126 3
        $map = $this->map;
127 3
        foreach ($enumerators as $enumerator) {
128 3
            $ord = ($this->enumeration)::get($enumerator)->getOrdinal();
129 3
            unset($map[$ord]);
130
        }
131
132 3
        $this->map = $map;
133 3
    }
134
135
    /* write access (immutable) */
136
137
    /**
138
     * Creates a new map with the given enumerator (object or value) mapping to the specified data value added.
139
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
0 ignored issues
show
Missing parameter comment
Loading history...
140
     * @param mixed                                                 $value
0 ignored issues
show
Missing parameter comment
Loading history...
Expected 37 spaces after parameter type; 49 found
Loading history...
141
     * @return static
142
     * @throws InvalidArgumentException On an invalid given enumerator
143
     */
144 1
    public function with($enumerator, $value): self
145
    {
146 1
        $clone = clone $this;
147 1
        $clone->add($enumerator, $value);
148 1
        return $clone;
149
    }
150
151
    /**
152
     * Creates a new map with the given iterable mapping enumerators (objects or values) to data values added.
153
     * @param iterable<T|null|bool|int|float|string|array<mixed>, mixed> $map
0 ignored issues
show
Missing parameter comment
Loading history...
154
     * @return static
155
     * @throws InvalidArgumentException On an invalid given enumerator
156
     */
157 1
    public function withIterable(iterable $map): self
158
    {
159 1
        $clone = clone $this;
160 1
        $clone->addIterable($map);
161 1
        return $clone;
162
    }
163
164
    /**
165
     * Create a new map with the given enumerator mapping removed.
166
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
0 ignored issues
show
Missing parameter comment
Loading history...
167
     * @return static
168
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
@throws tag comment must end with a full stop
Loading history...
169
     */
170 1
    public function without($enumerator): self
171
    {
172 1
        $clone = clone $this;
173 1
        $clone->remove($enumerator);
174 1
        return $clone;
175
    }
176
177
    /**
178
     * Creates a new map with the given iterable enumerator (object or value) mappings removed.
179
     * @param iterable<T|null|bool|int|float|string|array<mixed>> $enumerators
0 ignored issues
show
Missing parameter comment
Loading history...
180
     * @return static
181
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
@throws tag comment must end with a full stop
Loading history...
182
     */
183 1
    public function withoutIterable(iterable $enumerators): self
184
    {
185 1
        $clone = clone $this;
186 1
        $clone->removeIterable($enumerators);
187 1
        return $clone;
188
    }
189
190
    /* read access */
191
192
    /**
193
     * Get the classname of the enumeration type.
194
     * @return class-string<T>
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<T> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<T>.
Loading history...
195
     */
196 1
    public function getEnumeration(): string
197
    {
198 1
        return $this->enumeration;
199
    }
200
201
    /**
202
     * Get the mapped data value of the given enumerator (object or value).
203
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
0 ignored issues
show
Missing parameter comment
Loading history...
204
     * @return mixed
205
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
@throws tag comment must end with a full stop
Loading history...
206
     * @throws UnexpectedValueException If the given enumerator does not exist in this map
0 ignored issues
show
@throws tag comment must end with a full stop
Loading history...
207
     * @see offsetGet()
208
     */
209 10
    public function get($enumerator)
210
    {
211 10
        $enumerator = ($this->enumeration)::get($enumerator);
212 10
        $ord = $enumerator->getOrdinal();
213 10
        if (!\array_key_exists($ord, $this->map)) {
214 2
            throw new UnexpectedValueException(sprintf(
215 2
                'Enumerator %s could not be found',
216 2
                \var_export($enumerator->getValue(), true)
217
            ));
218
        }
219
220 8
        return $this->map[$ord];
221
    }
222
223
    /**
224
     * Get a list of enumerator keys.
225
     * @return T[]
226
     *
227
     * @phpstan-return array<int, T>
228
     * @psalm-return list<T>
229
     */
230 8
    public function getKeys(): array
231
    {
232
        /** @var callable $byOrdinalFn */
233 8
        $byOrdinalFn = [$this->enumeration, 'byOrdinal'];
234
235 8
        return \array_map($byOrdinalFn, \array_keys($this->map));
236
    }
237
238
    /**
239
     * Get a list of mapped data values.
240
     * @return mixed[]
241
     *
242
     * @phpstan-return array<int, mixed>
243
     * @psalm-return list<mixed>
244
     */
245 8
    public function getValues(): array
246
    {
247 8
        return \array_values($this->map);
248
    }
249
250
    /**
251
     * Search for the given data value.
252
     * @param mixed $value
0 ignored issues
show
Missing parameter comment
Loading history...
253
     * @param bool $strict Use strict type comparison
0 ignored issues
show
Expected 2 spaces after parameter type; 1 found
Loading history...
254
     * @return T|null The enumerator object of the first matching data value or NULL
255
     */
256 2
    public function search($value, bool $strict = false)
257
    {
258
        /** @var false|int $ord */
259 2
        $ord = \array_search($value, $this->map, $strict);
260 2
        if ($ord !== false) {
261 2
            return ($this->enumeration)::byOrdinal($ord);
262
        }
263
264 2
        return null;
265
    }
266
267
    /**
268
     * Test if the given enumerator key (object or value) exists.
269
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
0 ignored issues
show
Missing parameter comment
Loading history...
270
     * @return bool
271
     * @see offsetExists()
272
     */
273 8
    public function has($enumerator): bool
274
    {
275
        try {
276 8
            $ord = ($this->enumeration)::get($enumerator)->getOrdinal();
277 7
            return \array_key_exists($ord, $this->map);
278 1
        } catch (InvalidArgumentException $e) {
279
            // An invalid enumerator can't be contained in this map
280 1
            return false;
281
        }
282
    }
283
284
    /**
285
     * Test if the given enumerator key (object or value) exists.
286
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
0 ignored issues
show
Missing parameter comment
Loading history...
287
     * @return bool
288
     * @see offsetExists()
289
     * @see has()
290
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
291
     */
292 1
    public function contains($enumerator): bool
293
    {
294 1
        return $this->has($enumerator);
295
    }
296
297
    /* ArrayAccess */
298
299
    /**
300
     * Test if the given enumerator key (object or value) exists and is not NULL
301
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
0 ignored issues
show
Missing parameter comment
Loading history...
302
     * @return bool
303
     * @see contains()
304
     */
305 5
    public function offsetExists($enumerator): bool
306
    {
307
        try {
308 5
            return isset($this->map[($this->enumeration)::get($enumerator)->getOrdinal()]);
309 1
        } catch (InvalidArgumentException $e) {
310
            // An invalid enumerator can't be an offset of this map
311 1
            return false;
312
        }
313
    }
314
315
    /**
316
     * Get the mapped data value of the given enumerator (object or value).
317
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
0 ignored issues
show
Missing parameter comment
Loading history...
318
     * @return mixed The mapped date value of the given enumerator or NULL
319
     * @throws InvalidArgumentException On an invalid given enumerator
320
     * @see get()
321
     */
322 4
    public function offsetGet($enumerator)
323
    {
324
        try {
325 4
            return $this->get($enumerator);
326 1
        } catch (UnexpectedValueException $e) {
327 1
            return null;
328
        }
329
    }
330
331
    /**
332
     * Adds the given enumerator (object or value) mapping to the specified data value.
333
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
0 ignored issues
show
Missing parameter comment
Loading history...
334
     * @param mixed                                     $value
335
     * @return void
336
     * @throws InvalidArgumentException On an invalid given enumerator
337
     * @see add()
338
     */
339 7
    public function offsetSet($enumerator, $value = null): void
340
    {
341 7
        $this->add($enumerator, $value);
342 6
    }
343
344
    /**
345
     * Removes the given enumerator (object or value) mapping.
346
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
0 ignored issues
show
Missing parameter comment
Loading history...
347
     * @return void
348
     * @throws InvalidArgumentException On an invalid given enumerator
0 ignored issues
show
@throws tag comment must end with a full stop
Loading history...
349
     * @see remove()
350
     */
351 2
    public function offsetUnset($enumerator): void
352
    {
353 2
        $this->remove($enumerator);
354 2
    }
355
356
    /* IteratorAggregate */
357
358
    /**
359
     * Get a new Iterator.
360
     *
361
     * @return Iterator<T, mixed> Iterator<K extends Enum, V>
362
     */
363 2
    public function getIterator(): Iterator
364
    {
365 2
        $map = $this->map;
366 2
        foreach ($map as $ordinal => $value) {
367 2
            yield ($this->enumeration)::byOrdinal($ordinal) => $value;
368
        }
369 2
    }
370
371
    /* Countable */
372
373
    /**
374
     * Count the number of elements
375
     *
376
     * @return int
377
     */
378 3
    public function count(): int
379
    {
380 3
        return \count($this->map);
381
    }
382
383
    /**
384
     * Tests if the map is empty
385
     *
386
     * @return bool
387
     */
388 1
    public function isEmpty(): bool
389
    {
390 1
        return empty($this->map);
391
    }
392
}
393