Completed
Push — refactor_EnumMap ( 1d0ac6...de90b7 )
by Marc
01:25
created

Enum   B

Complexity

Total Complexity 54

Size/Duplication

Total Lines 394
Duplicated Lines 1.52 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 95.49%

Importance

Changes 0
Metric Value
wmc 54
lcom 1
cbo 0
dl 6
loc 394
ccs 127
cts 133
cp 0.9549
rs 7.0642
c 0
b 0
f 0

21 Methods

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

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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 = array();
38
39
    /**
40
     * A List of available enumerator names by enumeration class
41
     *
42
     * @var array ["$class" => ["$name0", ...], ...]
43
     */
44
    private static $names = array();
45
46
    /**
47
     * Already instantiated enumerators
48
     *
49
     * @var array ["$class" => ["$name" => $instance, ...], ...]
50
     */
51
    private static $instances = array();
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 72
    final private function __construct($value, $ordinal = null)
60
    {
61 72
        $this->value   = $value;
62 72
        $this->ordinal = $ordinal;
63 72
    }
64
65
    /**
66
     * Get the name of the enumerator
67
     *
68
     * @return string
69
     * @see getName()
70
     */
71 2
    public function __toString()
72
    {
73 2
        return $this->getName();
74
    }
75
76
    /**
77
     * @throws LogicException Enums are not cloneable
78
     *                        because instances are implemented as singletons
79
     */
80 2
    final private function __clone()
81
    {
82 2
        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 2
    final public function __sleep()
90
    {
91 2
        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 2
    final public function __wakeup()
99
    {
100 2
        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 46
    final public function getValue()
109
    {
110 46
        return $this->value;
111
    }
112
113
    /**
114
     * Get the name of the enumerator
115
     *
116
     * @return string
117
     */
118 16
    final public function getName()
119
    {
120 16
        $ordinal = $this->ordinal !== null ? $this->ordinal : $this->getOrdinal();
121 16
        return self::$names[static::class][$ordinal];
122
    }
123
124
    /**
125
     * Get the ordinal number of the enumerator
126
     *
127
     * @return int
128
     */
129 124
    final public function getOrdinal()
130
    {
131 124
        if ($this->ordinal === null) {
132 38
            $ordinal = 0;
133 38
            $value   = $this->value;
134 38
            foreach (self::detectConstants(static::class) as $constValue) {
135 38
                if ($value === $constValue) {
136 38
                    break;
137
                }
138 32
                ++$ordinal;
139 19
            }
140
141 38
            $this->ordinal = $ordinal;
142 19
        }
143
144 124
        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 4
    final public function is($enumerator)
154
    {
155 4
        return $this === $enumerator || $this->value === $enumerator
156
157
            // The following additional conditions are required only because of the issue of serializable singletons
158 4
            || ($enumerator instanceof static
159 4
                && \get_class($enumerator) === static::class
160 4
                && $enumerator->value === $this->value
161 2
            );
162
    }
163
164
    /**
165
     * Get an enumerator instance of the given value or instance
166
     *
167
     * @param static|null|bool|int|float|string $value
168
     * @return static
169
     * @throws InvalidArgumentException On an unknwon or invalid value
170
     * @throws LogicException           On ambiguous constant values
171
     */
172 144
    final public static function get($value)
173
    {
174 144
        if ($value instanceof static && \get_class($value) === static::class) {
175 56
            return $value;
176
        }
177
178 102
        return static::byValue($value);
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 102
    final public static function byValue($value)
190
    {
191 102
        $class     = static::class;
192 102
        $constants = self::detectConstants($class);
193 98
        $name      = \array_search($value, $constants, true);
194 98 View Code Duplication
        if ($name === false) {
195 14
            $message = \is_scalar($value)
196 10
                ? 'Unknown value ' . \var_export($value, true)
197 14
                : 'Invalid value of type ' . (\is_object($value) ? \get_class($value) : \gettype($value));
198 14
            throw new InvalidArgumentException($message);
199
        }
200
201 86
        if (!isset(self::$instances[$class][$name])) {
202 34
            self::$instances[$class][$name] = new $class($constants[$name]);
203 17
        }
204
205 86
        return self::$instances[$class][$name];
206
    }
207
208
    /**
209
     * Get an enumerator instance by the given name
210
     *
211
     * @param string $name The name of the enumerator
212
     * @return static
213
     * @throws InvalidArgumentException On an invalid or unknown name
214
     * @throws LogicException           On ambiguous values
215
     */
216 88
    final public static function byName($name)
217
    {
218 88
        $name  = (string) $name;
219 88
        $class = static::class;
220 88
        if (isset(self::$instances[$class][$name])) {
221 64
            return self::$instances[$class][$name];
222
        }
223
224 34
        $const = $class . '::' . $name;
225 34
        if (!\defined($const)) {
226 2
            throw new InvalidArgumentException($const . ' not defined');
227
        }
228
229 32
        return self::$instances[$class][$name] = new $class(\constant($const));
230
    }
231
232
    /**
233
     * Get an enumeration instance by the given ordinal number
234
     *
235
     * @param int $ordinal The ordinal number or the enumerator
236
     * @return static
237
     * @throws InvalidArgumentException On an invalid ordinal number
238
     * @throws LogicException           On ambiguous values
239
     */
240 60
    final public static function byOrdinal($ordinal)
241
    {
242 60
        $ordinal   = (int) $ordinal;
243 60
        $class     = static::class;
244
245 60
        if (!isset(self::$names[$class])) {
246 4
            self::detectConstants($class);
247 2
        }
248
249 60
        if (!isset(self::$names[$class][$ordinal])) {
250 2
            throw new InvalidArgumentException(\sprintf(
251 2
                'Invalid ordinal number, must between 0 and %s',
252 2
                \count(self::$names[$class]) - 1
253 1
            ));
254
        }
255
256 58
        $name = self::$names[$class][$ordinal];
257 58
        if (isset(self::$instances[$class][$name])) {
258 54
            return self::$instances[$class][$name];
259
        }
260
261 6
        $const = $class . '::' . $name;
262 6
        return self::$instances[$class][$name] = new $class(\constant($const), $ordinal);
263
    }
264
265
    /**
266
     * Get a list of enumerator instances ordered by ordinal number
267
     *
268
     * @return static[]
269
     */
270 32
    final public static function getEnumerators()
271
    {
272 32
        if (!isset(self::$names[static::class])) {
273 2
            self::detectConstants(static::class);
274 1
        }
275 32
        return \array_map([static::class, 'byName'], self::$names[static::class]);
276
    }
277
278
    /**
279
     * Get a list of enumerator values ordered by ordinal number
280
     *
281
     * @return mixed[]
282
     */
283 2
    final public static function getValues()
284
    {
285 2
        return \array_values(self::detectConstants(static::class));
286
    }
287
288
    /**
289
     * Get a list of enumerator names ordered by ordinal number
290
     *
291
     * @return string[]
292
     */
293 4
    final public static function getNames()
294
    {
295 4
        if (!isset(self::$names[static::class])) {
296 2
            self::detectConstants(static::class);
297 1
        }
298 4
        return self::$names[static::class];
299
    }
300
    /*
301
     * Get a list of enumerator ordinal numbers
302
     *
303
     * @return int[]
304
     */
305 2
    final public static function getOrdinals()
306
    {
307 2
        $count = \count(self::detectConstants(static::class));
308 2
        return $count === 0 ? array() : \range(0, $count - 1);
309
    }
310
311
    /**
312
     * Get all available constants of the called class
313
     *
314
     * @return array
315
     * @throws LogicException On ambiguous constant values
316
     */
317 156
    final public static function getConstants()
318
    {
319 156
        return self::detectConstants(static::class);
320
    }
321
322
    /**
323
     * Is the given enumerator part of this enumeration
324
     * 
325
     * @param static|null|bool|int|float|string $value
326
     * @return bool
327
     */
328 2
    final public static function has($value)
329
    {
330 2
        if ($value instanceof static && \get_class($value) === static::class) {
331 2
            return true;
332
        }
333
334 2
        $constants = self::detectConstants(static::class);
335 2
        return \in_array($value, $constants, true);
336
    }
337
338
    /**
339
     * Detect all public available constants of given enumeration class
340
     *
341
     * @param string $class
342
     * @return array
343
     * @throws LogicException On ambiguous constant values
344
     */
345 202
    private static function detectConstants($class)
346
    {
347 202
        if (!isset(self::$constants[$class])) {
348 70
            $reflection = new ReflectionClass($class);
349 70
            $constants  = array();
350
351
            do {
352 70
                $scopeConstants = array();
353 70
                if (PHP_VERSION_ID >= 70100) {
354
                    // Since PHP-7.1 visibility modifiers are allowed for class constants
355
                    // for enumerations we are only interested in public once.
356
                    foreach ($reflection->getReflectionConstants() as $reflConstant) {
357
                        if ($reflConstant->isPublic()) {
358
                            $scopeConstants[ $reflConstant->getName() ] = $reflConstant->getValue();
359
                        }
360
                    }
361
                } else {
362
                    // In PHP < 7.1 all class constants were public by definition
363 70
                    $scopeConstants = $reflection->getConstants();
364
                }
365
366 70
                $constants = $scopeConstants + $constants;
367 70
            } while (($reflection = $reflection->getParentClass()) && $reflection->name !== __CLASS__);
368
369
            // Detect ambiguous values and report names
370 70
            $ambiguous = array();
371 70
            foreach ($constants as $value) {
372 68
                $names = \array_keys($constants, $value, true);
373 68
                if (\count($names) > 1) {
374 36
                    $ambiguous[\var_export($value, true)] = $names;
375 2
                }
376 35
            }
377 70
            if (!empty($ambiguous)) {
378 4
                throw new LogicException(
379
                    'All possible values needs to be unique. The following are ambiguous: '
380 4
                    . \implode(', ', \array_map(function ($names) use ($constants) {
381 4
                        return \implode('/', $names) . '=' . \var_export($constants[$names[0]], true);
382 4
                    }, $ambiguous))
383 2
                );
384
            }
385
386 66
            self::$constants[$class] = $constants;
387 66
            self::$names[$class] = \array_keys($constants);
388 33
        }
389
390 198
        return self::$constants[$class];
391
    }
392
393
    /**
394
     * Get an enumerator instance by the given name.
395
     *
396
     * This will be called automatically on calling a method
397
     * with the same name of a defined enumerator.
398
     *
399
     * @param string $method The name of the enumeraotr (called as method)
400
     * @param array  $args   There should be no arguments
401
     * @return static
402
     * @throws InvalidArgumentException On an invalid or unknown name
403
     * @throws LogicException           On ambiguous constant values
404
     */
405 52
    final public static function __callStatic($method, array $args)
406
    {
407 52
        return self::byName($method);
408
    }
409
}
410