Completed
Push — enum_get_by ( 5b9d27 )
by Marc
01:47
created

Enum   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 384
Duplicated Lines 1.56 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 94.87%

Importance

Changes 0
Metric Value
wmc 46
lcom 1
cbo 0
dl 6
loc 384
ccs 111
cts 117
cp 0.9487
rs 8.3999
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 4 1
A getOrdinal() 0 19 4
A is() 0 4 2
A get() 0 8 3
B byValue() 6 18 5
A byName() 0 15 3
A byOrdinal() 0 20 3
A getByName() 0 4 1
A getByOrdinal() 0 4 1
A clear() 0 5 1
A getEnumerators() 0 4 1
A getConstants() 0 4 1
A has() 0 11 3
D detectConstants() 0 46 10
A __callStatic() 0 4 1

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