marc-mabe /
php-enum
| 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 |
||
|
0 ignored issues
–
show
Coding Style
introduced
by
Loading history...
|
|||
| 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
|
|||
| 30 | */ |
||
| 31 | private $enumeration; |
||
|
0 ignored issues
–
show
|
|||
| 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
|
|||
| 42 | * @param null|iterable<T|null|bool|int|float|string|array<mixed>, mixed> $map Initialize map |
||
|
0 ignored issues
–
show
|
|||
| 43 | * @throws InvalidArgumentException |
||
|
0 ignored issues
–
show
|
|||
| 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
|
|||
| 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
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. 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
|
|||
| 83 | * @throws InvalidArgumentException On an invalid given enumerator |
||
|
0 ignored issues
–
show
|
|||
| 84 | * @see offsetSet() |
||
| 85 | */ |
||
|
0 ignored issues
–
show
|
|||
| 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
|
|||
| 95 | * @throws InvalidArgumentException On an invalid given enumerator |
||
|
0 ignored issues
–
show
|
|||
| 96 | */ |
||
|
0 ignored issues
–
show
|
|||
| 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
|
|||
| 110 | * @throws InvalidArgumentException On an invalid given enumerator |
||
|
0 ignored issues
–
show
|
|||
| 111 | * @see offsetUnset() |
||
| 112 | */ |
||
|
0 ignored issues
–
show
|
|||
| 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
|
|||
| 122 | * @throws InvalidArgumentException On an invalid given enumerator |
||
|
0 ignored issues
–
show
|
|||
| 123 | */ |
||
|
0 ignored issues
–
show
|
|||
| 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
|
|||
| 140 | * @param mixed $value |
||
|
0 ignored issues
–
show
|
|||
| 141 | * @return static |
||
| 142 | * @throws InvalidArgumentException On an invalid given enumerator |
||
|
0 ignored issues
–
show
|
|||
| 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
|
|||
| 154 | * @return static |
||
| 155 | * @throws InvalidArgumentException On an invalid given enumerator |
||
|
0 ignored issues
–
show
|
|||
| 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
|
|||
| 167 | * @return static |
||
| 168 | * @throws InvalidArgumentException On an invalid given enumerator |
||
|
0 ignored issues
–
show
|
|||
| 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
|
|||
| 180 | * @return static |
||
| 181 | * @throws InvalidArgumentException On an invalid given enumerator |
||
|
0 ignored issues
–
show
|
|||
| 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
|
|||
| 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
|
|||
| 204 | * @return mixed |
||
| 205 | * @throws InvalidArgumentException On an invalid given enumerator |
||
|
0 ignored issues
–
show
|
|||
| 206 | * @throws UnexpectedValueException If the given enumerator does not exist in this map |
||
|
0 ignored issues
–
show
|
|||
| 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
|
|||
| 253 | * @param bool $strict Use strict type comparison |
||
|
0 ignored issues
–
show
|
|||
| 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
|
|||
| 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
|
|||
| 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
|
|||
| 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
|
|||
| 318 | * @return mixed The mapped date value of the given enumerator or NULL |
||
| 319 | * @throws InvalidArgumentException On an invalid given enumerator |
||
|
0 ignored issues
–
show
|
|||
| 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
|
|||
| 334 | * @param mixed $value |
||
|
0 ignored issues
–
show
|
|||
| 335 | * @return void |
||
| 336 | * @throws InvalidArgumentException On an invalid given enumerator |
||
|
0 ignored issues
–
show
|
|||
| 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
|
|||
| 347 | * @return void |
||
| 348 | * @throws InvalidArgumentException On an invalid given enumerator |
||
|
0 ignored issues
–
show
|
|||
| 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 |