Passed
Branch master (5a719b)
by Marc
03:59
created

Enum   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 389
Duplicated Lines 3.08 %

Test Coverage

Coverage 97.69%

Importance

Changes 0
Metric Value
dl 12
loc 389
ccs 127
cts 130
cp 0.9769
rs 7.4757
c 0
b 0
f 0
wmc 53

21 Methods

Rating   Name   Duplication   Size   Complexity  
A __toString() 0 3 1
A __sleep() 0 3 1
A __construct() 0 4 1
A get() 0 7 3
A getValue() 0 3 1
A __clone() 0 3 1
A __wakeup() 0 3 1
B is() 0 8 5
A getName() 0 4 2
A getOrdinal() 0 16 4
B byValue() 6 16 5
A getNames() 0 6 2
A getEnumerators() 0 6 2
C detectConstants() 0 46 9
B byOrdinal() 3 22 4
A __callStatic() 0 3 1
A getConstants() 0 3 1
A byName() 3 13 3
A getOrdinals() 0 4 2
A getValues() 0 3 1
A has() 0 8 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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
namespace MabeEnum;
4
5
use ReflectionClass;
6
use InvalidArgumentException;
7
use LogicException;
8
9
/**
10
 * Class to implement enumerations for PHP 5 (without SplEnum)
11
 *
12
 * @link http://github.com/marc-mabe/php-enum for the canonical source repository
13
 * @copyright Copyright (c) 2017 Marc Bennewitz
14
 * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
15
 */
16
abstract class Enum
17
{
18
    /**
19
     * The selected enumerator value
20
     *
21
     * @var null|bool|int|float|string
22
     */
23
    private $value;
24
25
    /**
26
     * The ordinal number of the enumerator
27
     *
28
     * @var null|int
29
     */
30
    private $ordinal;
31
32
    /**
33
     * A map of enumerator names and values by enumeration class
34
     *
35
     * @var array ["$class" => ["$name" => $value, ...], ...]
36
     */
37
    private static $constants = [];
38
39
    /**
40
     * A List of available enumerator names by enumeration class
41
     *
42
     * @var array ["$class" => ["$name0", ...], ...]
43
     */
44
    private static $names = [];
45
46
    /**
47
     * Already instantiated enumerators
48
     *
49
     * @var array ["$class" => ["$name" => $instance, ...], ...]
50
     */
51
    private static $instances = [];
52
53
    /**
54
     * Constructor
55
     *
56
     * @param null|bool|int|float|string $value   The value of the enumerator
57
     * @param int|null                   $ordinal The ordinal number of the enumerator
58
     */
59 108
    final private function __construct($value, $ordinal = null)
60
    {
61 108
        $this->value   = $value;
62 108
        $this->ordinal = $ordinal;
63 108
    }
64
65
    /**
66
     * Get the name of the enumerator
67
     *
68
     * @return string
69
     * @see getName()
70
     */
71 3
    public function __toString()
72
    {
73 3
        return $this->getName();
74
    }
75
76
    /**
77
     * @throws LogicException Enums are not cloneable
78
     *                        because instances are implemented as singletons
79
     */
80 3
    final private function __clone()
81
    {
82 3
        throw new LogicException('Enums are not cloneable');
83
    }
84
85
    /**
86
     * @throws LogicException Enums are not serializable
87
     *                        because instances are implemented as singletons
88
     */
89 3
    final public function __sleep()
90
    {
91 3
        throw new LogicException('Enums are not serializable');
92
    }
93
94
    /**
95
     * @throws LogicException Enums are not serializable
96
     *                        because instances are implemented as singletons
97
     */
98 3
    final public function __wakeup()
99
    {
100 3
        throw new LogicException('Enums are not serializable');
101
    }
102
103
    /**
104
     * Get the value of the enumerator
105
     *
106
     * @return null|bool|int|float|string
107
     */
108 69
    final public function getValue()
109
    {
110 69
        return $this->value;
111
    }
112
113
    /**
114
     * Get the name of the enumerator
115
     *
116
     * @return string
117
     */
118 24
    final public function getName()
119
    {
120 24
        $ordinal = $this->ordinal !== null ? $this->ordinal : $this->getOrdinal();
121 24
        return self::$names[static::class][$ordinal];
122
    }
123
124
    /**
125
     * Get the ordinal number of the enumerator
126
     *
127
     * @return int
128
     */
129 189
    final public function getOrdinal()
130
    {
131 189
        if ($this->ordinal === null) {
132 57
            $ordinal = 0;
133 57
            $value   = $this->value;
134 57
            foreach (self::detectConstants(static::class) as $constValue) {
135 57
                if ($value === $constValue) {
136 57
                    break;
137
                }
138 48
                ++$ordinal;
139 19
            }
140
141 57
            $this->ordinal = $ordinal;
142 19
        }
143
144 189
        return $this->ordinal;
145
    }
146
147
    /**
148
     * Compare this enumerator against another and check if it's the same.
149
     *
150
     * @param mixed $enumerator
151
     * @return bool
152
     */
153 6
    final public function is($enumerator)
154
    {
155 6
        return $this === $enumerator || $this->value === $enumerator
156
157
            // The following additional conditions are required only because of the issue of serializable singletons
158 6
            || ($enumerator instanceof static
159 6
                && \get_class($enumerator) === static::class
160 6
                && $enumerator->value === $this->value
161 2
            );
162
    }
163
164
    /**
165
     * Get an enumerator instance of the given enumerator value or instance
166
     *
167
     * @param static|null|bool|int|float|string $enumerator
168
     * @return static
169
     * @throws InvalidArgumentException On an unknwon or invalid value
170
     * @throws LogicException           On ambiguous constant values
171
     */
172 219
    final public static function get($enumerator)
173
    {
174 219
        if ($enumerator instanceof static && \get_class($enumerator) === static::class) {
175 84
            return $enumerator;
176
        }
177
178 156
        return static::byValue($enumerator);
179
    }
180
181
    /**
182
     * Get an enumerator instance by the given value
183
     *
184
     * @param mixed $value
185
     * @return static
186
     * @throws InvalidArgumentException On an unknwon or invalid value
187
     * @throws LogicException           On ambiguous constant values
188
     */
189 156
    final public static function byValue($value)
190
    {
191 156
        $constants = self::detectConstants(static::class);
192 150
        $name      = \array_search($value, $constants, true);
193 150 View Code Duplication
        if ($name === false) {
194 21
            $message = \is_scalar($value)
195 13
                ? 'Unknown value ' . \var_export($value, true)
196 21
                : 'Invalid value of type ' . (\is_object($value) ? \get_class($value) : \gettype($value));
197 21
            throw new InvalidArgumentException($message);
198
        }
199
200 132
        if (!isset(self::$instances[static::class][$name])) {
201 51
            self::$instances[static::class][$name] = new static($constants[$name]);
202 17
        }
203
204 132
        return self::$instances[static::class][$name];
205
    }
206
207
    /**
208
     * Get an enumerator instance by the given name
209
     *
210
     * @param string $name The name of the enumerator
211
     * @return static
212
     * @throws InvalidArgumentException On an invalid or unknown name
213
     * @throws LogicException           On ambiguous values
214
     */
215 132
    final public static function byName($name)
216
    {
217 132
        $name = (string) $name;
218 132 View Code Duplication
        if (isset(self::$instances[static::class][$name])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
219 96
            return self::$instances[static::class][$name];
220
        }
221
222 51
        $const = static::class . '::' . $name;
223 51
        if (!\defined($const)) {
224 3
            throw new InvalidArgumentException($const . ' not defined');
225
        }
226
227 48
        return self::$instances[static::class][$name] = new static(\constant($const));
228
    }
229
230
    /**
231
     * Get an enumeration instance by the given ordinal number
232
     *
233
     * @param int $ordinal The ordinal number or the enumerator
234
     * @return static
235
     * @throws InvalidArgumentException On an invalid ordinal number
236
     * @throws LogicException           On ambiguous values
237
     */
238 90
    final public static function byOrdinal($ordinal)
239
    {
240 90
        $ordinal = (int) $ordinal;
241
242 90
        if (!isset(self::$names[static::class])) {
243 6
            self::detectConstants(static::class);
244 2
        }
245
246 90
        if (!isset(self::$names[static::class][$ordinal])) {
247 3
            throw new InvalidArgumentException(\sprintf(
248 3
                'Invalid ordinal number, must between 0 and %s',
249 3
                \count(self::$names[static::class]) - 1
250 1
            ));
251
        }
252
253 87
        $name = self::$names[static::class][$ordinal];
254 87 View Code Duplication
        if (isset(self::$instances[static::class][$name])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
255 81
            return self::$instances[static::class][$name];
256
        }
257
258 9
        $const = static::class . '::' . $name;
259 9
        return self::$instances[static::class][$name] = new static(\constant($const), $ordinal);
260
    }
261
262
    /**
263
     * Get a list of enumerator instances ordered by ordinal number
264
     *
265
     * @return static[]
266
     */
267 48
    final public static function getEnumerators()
268
    {
269 48
        if (!isset(self::$names[static::class])) {
270 3
            self::detectConstants(static::class);
271 1
        }
272 48
        return \array_map([static::class, 'byName'], self::$names[static::class]);
273
    }
274
275
    /**
276
     * Get a list of enumerator values ordered by ordinal number
277
     *
278
     * @return mixed[]
279
     */
280 3
    final public static function getValues()
281
    {
282 3
        return \array_values(self::detectConstants(static::class));
283
    }
284
285
    /**
286
     * Get a list of enumerator names ordered by ordinal number
287
     *
288
     * @return string[]
289
     */
290 6
    final public static function getNames()
291
    {
292 6
        if (!isset(self::$names[static::class])) {
293 3
            self::detectConstants(static::class);
294 1
        }
295 6
        return self::$names[static::class];
296
    }
297
    /*
298
     * Get a list of enumerator ordinal numbers
299
     *
300
     * @return int[]
301
     */
302 3
    final public static function getOrdinals()
303
    {
304 3
        $count = \count(self::detectConstants(static::class));
305 3
        return $count === 0 ? [] : \range(0, $count - 1);
306
    }
307
308
    /**
309
     * Get all available constants of the called class
310
     *
311
     * @return array
312
     * @throws LogicException On ambiguous constant values
313
     */
314 236
    final public static function getConstants()
315
    {
316 236
        return self::detectConstants(static::class);
317
    }
318
319
    /**
320
     * Is the given enumerator part of this enumeration
321
     * 
322
     * @param static|null|bool|int|float|string $value
323
     * @return bool
324
     */
325 3
    final public static function has($value)
326
    {
327 3
        if ($value instanceof static && \get_class($value) === static::class) {
328 3
            return true;
329
        }
330
331 3
        $constants = self::detectConstants(static::class);
332 3
        return \in_array($value, $constants, true);
333
    }
334
335
    /**
336
     * Detect all public available constants of given enumeration class
337
     *
338
     * @param string $class
339
     * @return array
340
     * @throws LogicException On ambiguous constant values
341
     */
342 308
    private static function detectConstants($class)
343
    {
344 308
        if (!isset(self::$constants[$class])) {
345 107
            $reflection = new ReflectionClass($class);
346 107
            $constants  = [];
347
348
            do {
349 107
                $scopeConstants = [];
350 107
                if (PHP_VERSION_ID >= 70100) {
351
                    // Since PHP-7.1 visibility modifiers are allowed for class constants
352
                    // for enumerations we are only interested in public once.
353 37
                    foreach ($reflection->getReflectionConstants() as $reflConstant) {
0 ignored issues
show
Bug introduced by
The method getReflectionConstants() does not exist on ReflectionClass. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

353
                    foreach ($reflection->/** @scrutinizer ignore-call */ getReflectionConstants() as $reflConstant) {

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...
354 36
                        if ($reflConstant->isPublic()) {
355 37
                            $scopeConstants[ $reflConstant->getName() ] = $reflConstant->getValue();
356
                        }
357
                    }
358
                } else {
359
                    // In PHP < 7.1 all class constants were public by definition
360 70
                    $scopeConstants = $reflection->getConstants();
361
                }
362
363 107
                $constants = $scopeConstants + $constants;
364 107
            } while (($reflection = $reflection->getParentClass()) && $reflection->name !== __CLASS__);
365
366
            // Detect ambiguous values and report names
367 107
            $ambiguous = [];
368 107
            foreach ($constants as $value) {
369 104
                $names = \array_keys($constants, $value, true);
370 104
                if (\count($names) > 1) {
371 72
                    $ambiguous[\var_export($value, true)] = $names;
372 2
                }
373 35
            }
374 107
            if (!empty($ambiguous)) {
375 6
                throw new LogicException(
376
                    'All possible values needs to be unique. The following are ambiguous: '
377 6
                    . \implode(', ', \array_map(function ($names) use ($constants) {
378 6
                        return \implode('/', $names) . '=' . \var_export($constants[$names[0]], true);
379 6
                    }, $ambiguous))
380 2
                );
381
            }
382
383 101
            self::$constants[$class] = $constants;
384 101
            self::$names[$class] = \array_keys($constants);
385 33
        }
386
387 302
        return self::$constants[$class];
388
    }
389
390
    /**
391
     * Get an enumerator instance by the given name.
392
     *
393
     * This will be called automatically on calling a method
394
     * with the same name of a defined enumerator.
395
     *
396
     * @param string $method The name of the enumeraotr (called as method)
397
     * @param array  $args   There should be no arguments
398
     * @return static
399
     * @throws InvalidArgumentException On an invalid or unknown name
400
     * @throws LogicException           On ambiguous constant values
401
     */
402 78
    final public static function __callStatic($method, array $args)
403
    {
404 78
        return self::byName($method);
405
    }
406
}
407