Completed
Pull Request — master (#123)
by Marc
73:34 queued 71:19
created

Enum   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 409
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 97
dl 0
loc 409
ccs 115
cts 115
cp 1
rs 5.5199
c 0
b 0
f 0
wmc 56

24 Methods

Rating   Name   Duplication   Size   Complexity  
A getValues() 0 3 1
A getConstants() 0 3 1
A getOrdinals() 0 4 2
A __toString() 0 3 1
A __sleep() 0 3 1
A __construct() 0 4 1
A byValue() 0 22 6
A get() 0 7 3
A getValue() 0 3 1
A __clone() 0 3 1
A __wakeup() 0 3 1
A byName() 0 12 3
A is() 0 8 5
A getName() 0 3 2
A getOrdinal() 0 16 4
A hasName() 0 3 1
A getNames() 0 6 2
A getEnumerators() 0 6 2
A hasValue() 0 4 1
A byOrdinal() 0 20 4
A __callStatic() 0 3 1
A noAmbiguousValues() 0 10 3
A has() 0 4 3
A detectConstants() 0 28 6

How to fix   Complexity   

Complex Class

Complex classes like Enum often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Enum, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace MabeEnum;
6
7
use ReflectionClass;
8
use InvalidArgumentException;
9
use LogicException;
10
11
/**
12
 * Abstract base enumeration class.
13
 *
14
 * @copyright 2019 Marc Bennewitz
15
 * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
16
 * @link http://github.com/marc-mabe/php-enum for the canonical source repository
17
 */
3 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @package tag in class comment
Loading history...
Coding Style introduced by
Missing @author tag in class comment
Loading history...
18
abstract class Enum
19
{
20
    /**
21
     * The selected enumerator value
22
     *
23
     * @var null|bool|int|float|string|array
24
     */
25
    private $value;
1 ignored issue
show
Coding Style introduced by
Expected 1 blank line before member var; 0 found
Loading history...
Coding Style introduced by
Private member variable "value" must contain a leading underscore
Loading history...
26
27
    /**
28
     * The ordinal number of the enumerator
29
     *
30
     * @var null|int
31
     */
32
    private $ordinal;
1 ignored issue
show
Coding Style introduced by
Private member variable "ordinal" must contain a leading underscore
Loading history...
33
34
    /**
35
     * A map of enumerator names and values by enumeration class
36
     *
37
     * @var array ["$class" => ["$name" => $value, ...], ...]
38
     */
39
    private static $constants = [];
1 ignored issue
show
Coding Style introduced by
Private member variable "constants" must contain a leading underscore
Loading history...
40
41
    /**
42
     * A List of available enumerator names by enumeration class
43
     *
44
     * @var array ["$class" => ["$name0", ...], ...]
45
     */
46
    private static $names = [];
1 ignored issue
show
Coding Style introduced by
Private member variable "names" must contain a leading underscore
Loading history...
47
48
    /**
49
     * Already instantiated enumerators
50
     *
51
     * @var array ["$class" => ["$name" => $instance, ...], ...]
52
     */
53
    private static $instances = [];
1 ignored issue
show
Coding Style introduced by
Private member variable "instances" must contain a leading underscore
Loading history...
54
55
    /**
56
     * Constructor
57
     *
58
     * @param null|bool|int|float|string|array $value   The value of the enumerator
2 ignored issues
show
Coding Style introduced by
Expected "null|boolean|integer|float|string|array" but found "null|bool|int|float|string|array" for parameter type
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
59
     * @param int|null                         $ordinal The ordinal number of the enumerator
2 ignored issues
show
Coding Style introduced by
Expected "integer|null" but found "int|null" for parameter type
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
60
     */
61 43
    final private function __construct($value, $ordinal = null)
2 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$ordinal" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$ordinal"; expected 0 but found 1
Loading history...
62
    {
63 43
        $this->value   = $value;
64 43
        $this->ordinal = $ordinal;
65 43
    }
66
67
    /**
68
     * Get the name of the enumerator
69
     *
70
     * @return string
71
     * @see getName()
72
     */
73 1
    public function __toString(): string
74
    {
75 1
        return $this->getName();
76
    }
77
78
    /**
79
     * @throws LogicException Enums are not cloneable
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
80
     *                        because instances are implemented as singletons
81
     */
0 ignored issues
show
introduced by
Missing @return tag in function comment
Loading history...
82 1
    final private function __clone()
83
    {
84 1
        throw new LogicException('Enums are not cloneable');
85
    }
86
87
    /**
88
     * @throws LogicException Enums are not serializable
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
89
     *                        because instances are implemented as singletons
90
     */
0 ignored issues
show
introduced by
Missing @return tag in function comment
Loading history...
91 1
    final public function __sleep()
92
    {
93 1
        throw new LogicException('Enums are not serializable');
94
    }
95
96
    /**
97
     * @throws LogicException Enums are not serializable
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
98
     *                        because instances are implemented as singletons
99
     */
0 ignored issues
show
introduced by
Missing @return tag in function comment
Loading history...
100 1
    final public function __wakeup()
101
    {
102 1
        throw new LogicException('Enums are not serializable');
103
    }
104
105
    /**
106
     * Get the value of the enumerator
107
     *
108
     * @return null|bool|int|float|string|array
1 ignored issue
show
Coding Style introduced by
Expected "null|boolean|integer|float|string|array" but found "null|bool|int|float|string|array" for function return type
Loading history...
109
     */
110 29
    final public function getValue()
111
    {
112 29
        return $this->value;
113
    }
114
115
    /**
116
     * Get the name of the enumerator
117
     *
118
     * @return string
119
     */
120 8
    final public function getName()
121
    {
122 8
        return self::$names[static::class][$this->ordinal ?: $this->getOrdinal()];
123
    }
124
125
    /**
126
     * Get the ordinal number of the enumerator
127
     *
128
     * @return int
1 ignored issue
show
Coding Style introduced by
Expected "integer" but found "int" for function return type
Loading history...
129
     */
130 102
    final public function getOrdinal()
131
    {
132 102
        if ($this->ordinal === null) {
133 21
            $ordinal = 0;
134 21
            $value   = $this->value;
135 21
            foreach (self::detectConstants(static::class) as $constValue) {
136 21
                if ($value === $constValue) {
137 21
                    break;
138
                }
139 16
                ++$ordinal;
140
            }
141
142 21
            $this->ordinal = $ordinal;
143
        }
144
145 102
        return $this->ordinal;
146
    }
147
148
    /**
149
     * Compare this enumerator against another and check if it's the same.
150
     *
151
     * @param static|null|bool|int|float|string|array $enumerator An enumerator object or value
2 ignored issues
show
Coding Style introduced by
Expected "static|null|boolean|integer|float|string|array" but found "static|null|bool|int|float|string|array" for parameter type
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
152
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
153
     */
154 2
    final public function is($enumerator)
155
    {
156 2
        return $this === $enumerator || $this->value === $enumerator
157
158
            // The following additional conditions are required only because of the issue of serializable singletons
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 116 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
159 2
            || ($enumerator instanceof static
160 2
                && \get_class($enumerator) === static::class
161 2
                && $enumerator->value === $this->value
162
            );
163
    }
164
165
    /**
166
     * Get an enumerator instance of the given enumerator value or instance
167
     *
168
     * @param static|null|bool|int|float|string|array $enumerator An enumerator object or value
2 ignored issues
show
Coding Style introduced by
Expected "static|null|boolean|integer|float|string|array" but found "static|null|bool|int|float|string|array" for parameter type
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
169
     * @return static
170
     * @throws InvalidArgumentException On an unknwon or invalid value
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
171
     * @throws LogicException           On ambiguous constant values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
172
     */
173 113
    final public static function get($enumerator)
174
    {
175 113
        if ($enumerator instanceof static && \get_class($enumerator) === static::class) {
176 34
            return $enumerator;
177
        }
178
179 88
        return static::byValue($enumerator);
180
    }
181
182
    /**
183
     * Get an enumerator instance by the given value
184
     *
185
     * @param null|bool|int|float|string|array $value Enumerator value
2 ignored issues
show
Coding Style introduced by
Expected "null|boolean|integer|float|string|array" but found "null|bool|int|float|string|array" for parameter type
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
186
     * @return static
187
     * @throws InvalidArgumentException On an unknwon or invalid value
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
188
     * @throws LogicException           On ambiguous constant values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
189
     */
190 88
    final public static function byValue($value)
191
    {
192 88
        if (!isset(self::$constants[static::class])) {
193 16
            self::detectConstants(static::class);
194
        }
195
196 86
        $name = \array_search($value, self::$constants[static::class], true);
197 86
        if ($name === false) {
198 12
            throw new InvalidArgumentException(sprintf(
199 12
                'Unknown value %s for enumeration %s',
200 12
                \is_scalar($value)
201 7
                    ? \var_export($value, true)
1 ignored issue
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
202 12
                    : 'of type ' . (\is_object($value) ? \get_class($value) : \gettype($value)),
1 ignored issue
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
203 12
                static::class
204
            ));
205
        }
206
207 77
        if (!isset(self::$instances[static::class][$name])) {
208 21
            self::$instances[static::class][$name] = new static(self::$constants[static::class][$name]);
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 104 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
209
        }
210
211 77
        return self::$instances[static::class][$name];
212
    }
213
214
    /**
215
     * Get an enumerator instance by the given name
216
     *
217
     * @param string $name The name of the enumerator
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
218
     * @return static
219
     * @throws InvalidArgumentException On an invalid or unknown name
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
220
     * @throws LogicException           On ambiguous values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
221
     */
222 54
    final public static function byName(string $name)
223
    {
224 54
        if (isset(self::$instances[static::class][$name])) {
225 41
            return self::$instances[static::class][$name];
226
        }
227
228 17
        $const = static::class . "::{$name}";
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $name instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
229 17
        if (!\defined($const)) {
230 1
            throw new InvalidArgumentException("{$const} not defined");
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $const instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
231
        }
232
233 16
        return self::$instances[static::class][$name] = new static(\constant($const));
234
    }
235
236
    /**
237
     * Get an enumeration instance by the given ordinal number
238
     *
239
     * @param int $ordinal The ordinal number of the enumerator
2 ignored issues
show
Coding Style introduced by
Expected "integer" but found "int" for parameter type
Loading history...
introduced by
Parameter comment must end with a full stop
Loading history...
240
     * @return static
241
     * @throws InvalidArgumentException On an invalid ordinal number
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
242
     * @throws LogicException           On ambiguous values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
243
     */
244 39
    final public static function byOrdinal(int $ordinal)
245
    {
246 39
        if (!isset(self::$names[static::class])) {
247 2
            self::detectConstants(static::class);
248
        }
249
250 39
        if (!isset(self::$names[static::class][$ordinal])) {
251 1
            throw new InvalidArgumentException(\sprintf(
252 1
                'Invalid ordinal number %s, must between 0 and %s',
253 1
                $ordinal,
254 1
                \count(self::$names[static::class]) - 1
255
            ));
256
        }
257
258 38
        $name = self::$names[static::class][$ordinal];
259 38
        if (isset(self::$instances[static::class][$name])) {
260 37
            return self::$instances[static::class][$name];
261
        }
262
263 6
        return self::$instances[static::class][$name] = new static(self::$constants[static::class][$name], $ordinal);
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 117 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
264
    }
265
266
    /**
267
     * Get a list of enumerator instances ordered by ordinal number
268
     *
269
     * @return static[]
270
     */
271 16
    final public static function getEnumerators()
272
    {
273 16
        if (!isset(self::$names[static::class])) {
274 1
            self::detectConstants(static::class);
275
        }
276 16
        return \array_map([static::class, 'byName'], self::$names[static::class]);
277
    }
278
279
    /**
280
     * Get a list of enumerator values ordered by ordinal number
281
     *
282
     * @return mixed[]
283
     */
284 1
    final public static function getValues()
285
    {
286 1
        return \array_values(self::detectConstants(static::class));
287
    }
288
289
    /**
290
     * Get a list of enumerator names ordered by ordinal number
291
     *
292
     * @return string[]
293
     */
294 2
    final public static function getNames()
295
    {
296 2
        if (!isset(self::$names[static::class])) {
297 1
            self::detectConstants(static::class);
298
        }
299 2
        return self::$names[static::class];
300
    }
301
    
302
    /**
303
     * Get a list of enumerator ordinal numbers
304
     *
305
     * @return int[]
306
     */
307 1
    final public static function getOrdinals()
308
    {
309 1
        $count = \count(self::detectConstants(static::class));
310 1
        return $count ? \range(0, $count - 1) : [];
311
    }
312
313
    /**
314
     * Get all available constants of the called class
315
     *
316
     * @return array
317
     * @throws LogicException On ambiguous constant values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
318
     */
319 114
    final public static function getConstants()
320
    {
321 114
        return self::detectConstants(static::class);
322
    }
323
324
    /**
325
     * Test if the given enumerator is part of this enumeration
326
     * 
327
     * @param static|null|bool|int|float|string|array $enumerator
1 ignored issue
show
Documentation introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected "static|null|boolean|integer|float|string|array" but found "static|null|bool|int|float|string|array" for parameter type
Loading history...
328
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
329
     */
330 1
    final public static function has($enumerator)
331
    {
332 1
        return ($enumerator instanceof static && \get_class($enumerator) === static::class)
333 1
            || static::hasValue($enumerator);
334
    }
335
336
    /**
337
     * Test if the given enumerator value is part of this enumeration
338
     *
339
     * @param null|bool|int|float|string|array $value
1 ignored issue
show
Documentation introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected "null|boolean|integer|float|string|array" but found "null|bool|int|float|string|array" for parameter type
Loading history...
340
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
341
     */
342 2
    final public static function hasValue($value)
343
    {
344 2
        $constants = self::detectConstants(static::class);
345 2
        return \in_array($value, $constants, true);
346
    }
347
348
    /**
349
     * Test if the given enumerator name is part of this enumeration
350
     *
351
     * @param string $name
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
352
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
353
     */
354 1
    final public static function hasName(string $name)
355
    {
356 1
        return \defined("static::{$name}");
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $name instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
357
    }
358
359
    /**
360
     * Detect all public available constants of given enumeration class
361
     *
362
     * @param string $class
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
363
     * @return array
364
     */
365 136
    private static function detectConstants($class)
0 ignored issues
show
Coding Style introduced by
Type hint "string" missing for $class
Loading history...
366
    {
367 136
        if (!isset(self::$constants[$class])) {
368 42
            $reflection = new ReflectionClass($class);
369 42
            $constants  = [];
370
371
            do {
372 42
                $scopeConstants = [];
373
                // Enumerators must be defined as public class constants
374 42
                foreach ($reflection->getReflectionConstants() as $reflConstant) {
375 41
                    if ($reflConstant->isPublic()) {
376 41
                        $scopeConstants[ $reflConstant->getName() ] = $reflConstant->getValue();
377
                    }
378
                }
379
380 42
                $constants = $scopeConstants + $constants;
381 42
            } while (($reflection = $reflection->getParentClass()) && $reflection->name !== __CLASS__);
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 103 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
382
383 42
            assert(
384 42
                self::noAmbiguousValues($constants),
385 42
                "Ambiguous enumerator values detected for {$class}"
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $class instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
386
            );
387
388 40
            self::$constants[$class] = $constants;
389 40
            self::$names[$class] = \array_keys($constants);
390
        }
391
392 134
        return self::$constants[$class];
393
    }
394
395
    /**
396
     * Test that the given constants does not contain ambiguous values
397
     * @param array $constants
0 ignored issues
show
Documentation introduced by
Missing parameter comment
Loading history...
398
     * @return bool
1 ignored issue
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
399
     */
400 42
    private static function noAmbiguousValues($constants)
0 ignored issues
show
introduced by
Type hint "array" missing for $constants
Loading history...
401
    {
402 42
        foreach ($constants as $value) {
403 41
            $names = \array_keys($constants, $value, true);
404 41
            if (\count($names) > 1) {
405 4
                return false;
406
            }
407
        }
408
409 38
        return true;
410
    }
411
412
    /**
413
     * Get an enumerator instance by the given name.
414
     *
415
     * This will be called automatically on calling a method
416
     * with the same name of a defined enumerator.
417
     *
418
     * @param string $method The name of the enumerator (called as method)
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
419
     * @param array  $args   There should be no arguments
1 ignored issue
show
introduced by
Parameter comment must end with a full stop
Loading history...
420
     * @return static
421
     * @throws InvalidArgumentException On an invalid or unknown name
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
422
     * @throws LogicException           On ambiguous constant values
0 ignored issues
show
introduced by
@throws tag comment must end with a full stop
Loading history...
423
     */
424 35
    final public static function __callStatic(string $method, array $args)
425
    {
426 35
        return self::byName($method);
427
    }
428
}
429