Passed
Push — master ( 8a3c76...b28e47 )
by
unknown
13:58
created

Enum   F

Complexity

Total Complexity 83

Size/Duplication

Total Lines 408
Duplicated Lines 0 %

Importance

Changes 32
Bugs 5 Features 3
Metric Value
eloc 156
c 32
b 5
f 3
dl 0
loc 408
rs 2
wmc 83

31 Methods

Rating   Name   Duplication   Size   Complexity  
A resolveByStaticCall() 0 15 3
A resolveByValue() 0 6 1
A getIndices() 0 3 1
A resolveByIndex() 0 6 1
A isValidIndex() 0 3 1
A resolveByString() 0 11 3
A getNames() 0 3 1
A startsWith() 0 3 2
A resolveFromDocBlocks() 0 17 3
A getValue() 0 3 1
A __callStatic() 0 15 5
B make() 0 24 8
A isValidValue() 0 3 1
A getIndex() 0 3 1
A isAny() 0 9 3
A getName() 0 3 1
A isEqual() 0 17 6
A clearCache() 0 3 1
A jsonSerialize() 0 3 1
A getValues() 0 3 1
A getMappedValue() 0 13 3
A isValidName() 0 3 1
A resolveByName() 0 7 1
A __toString() 0 3 1
A getMappedIndex() 0 13 3
A resolveFromStaticMethods() 0 21 6
A toArray() 0 3 1
A getAll() 0 5 1
A __call() 0 11 3
B resolve() 0 56 8
B __construct() 0 21 10

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
namespace Spatie\Enum;
4
5
use ArgumentCountError;
6
use BadMethodCallException;
7
use JsonSerializable;
8
use ReflectionClass;
9
use ReflectionMethod;
10
use Spatie\Enum\Exceptions\DuplicatedIndexException;
11
use Spatie\Enum\Exceptions\DuplicatedValueException;
12
use Spatie\Enum\Exceptions\InvalidIndexException;
13
use Spatie\Enum\Exceptions\InvalidNameException;
14
use Spatie\Enum\Exceptions\InvalidValueException;
15
use TypeError;
16
17
abstract class Enum implements Enumerable, JsonSerializable
18
{
19
    /** @var array[] */
20
    protected static $cache = [];
21
22
    /** @var int */
23
    protected $index;
24
25
    /** @var string */
26
    protected $value;
27
28
    /** @var string */
29
    protected $name;
30
31
    /**
32
     * This construct is not part of the public API and COULD change in a minor release.
33
     * You SHOULD NOT use it by your own - instead you SHOULD use the make() method.
34
     *
35
     * @internal
36
     * @see \Spatie\Enum\Enum::make()
37
     *
38
     * @param string|null $name
39
     * @param string|null $value
40
     * @param int|null $index
41
     */
42
    public function __construct(?string $name = null, ?string $value = null, ?int $index = null)
43
    {
44
        if (is_null($name) && is_null($value) && is_null($index)) {
45
            ['name' => $name, 'value' => $value, 'index' => $index] = $this->resolveByStaticCall();
46
        }
47
48
        if (is_null($name) || ! static::isValidName($name)) {
49
            throw new InvalidNameException($name, static::class);
50
        }
51
52
        if (is_null($value) || ! static::isValidValue($value)) {
53
            throw new InvalidValueException($value, static::class);
54
        }
55
56
        if (is_null($index) || ! static::isValidIndex($index)) {
57
            throw new InvalidIndexException($index, static::class);
58
        }
59
60
        $this->name = $name;
61
        $this->value = $value;
62
        $this->index = $index;
63
    }
64
65
    public function __call(string $name, array $arguments)
66
    {
67
        if (static::startsWith($name, 'is')) {
68
            return $this->isEqual(substr($name, 2));
69
        }
70
71
        if (static::isValidName($name)) {
72
            return static::make($name);
73
        }
74
75
        throw new BadMethodCallException('Call to undefined method '.static::class.'->'.$name.'()');
76
    }
77
78
    /**
79
     * @param string $name
80
     * @param mixed[] $arguments
81
     *
82
     * @return \Spatie\Enum\Enumerable|bool
83
     */
84
    public static function __callStatic(string $name, array $arguments)
85
    {
86
        if (static::startsWith($name, 'is')) {
87
            if (! isset($arguments[0])) {
88
                throw new ArgumentCountError('Calling '.static::class.'::'.$name.'() in static context requires one argument');
89
            }
90
91
            return static::make($arguments[0])->$name();
92
        }
93
94
        if (static::isValidName($name) || static::isValidValue($name)) {
95
            return static::make($name);
96
        }
97
98
        throw new BadMethodCallException('Call to undefined method '.static::class.'::'.$name.'()');
99
    }
100
101
    public function __toString(): string
102
    {
103
        return $this->getValue();
104
    }
105
106
    public function getIndex(): int
107
    {
108
        return $this->index;
109
    }
110
111
    public static function getIndices(): array
112
    {
113
        return array_column(static::resolve(), 'index');
114
    }
115
116
    public function getValue(): string
117
    {
118
        return $this->value;
119
    }
120
121
    public static function getValues(): array
122
    {
123
        return array_column(static::resolve(), 'value');
124
    }
125
126
    public function getName(): string
127
    {
128
        return $this->name;
129
    }
130
131
    public static function getNames(): array
132
    {
133
        return array_keys(static::resolve());
134
    }
135
136
    public function isAny(array $values): bool
137
    {
138
        foreach ($values as $value) {
139
            if ($this->isEqual($value)) {
140
                return true;
141
            }
142
        }
143
144
        return false;
145
    }
146
147
    public function isEqual($value): bool
148
    {
149
        if (is_int($value) || is_string($value)) {
150
            try {
151
                $value = static::make($value);
152
            } catch (InvalidValueException $error) {
153
                return false;
154
            } catch (InvalidIndexException $error) {
155
                return false;
156
            }
157
        }
158
159
        if ($value instanceof $this) {
160
            return $value->getValue() === $this->getValue();
161
        }
162
163
        return false;
164
    }
165
166
    public function jsonSerialize(): string
167
    {
168
        return $this->getValue();
169
    }
170
171
    /**
172
     * @param int|string $value
173
     *
174
     * @return static
175
     */
176
    public static function make($value): Enumerable
177
    {
178
        if (! is_int($value) && ! is_string($value)) {
0 ignored issues
show
introduced by
The condition is_string($value) is always true.
Loading history...
179
            throw new TypeError(static::class.'::make() expects string|int as argument but '.gettype($value).' given');
180
        }
181
182
        $name = null;
183
        $index = null;
184
185
        if (is_int($value)) {
186
            if (! static::isValidIndex($value)) {
187
                throw new InvalidIndexException($value, static::class);
188
            }
189
190
            [$name, $index, $value] = static::resolveByIndex($value);
191
        } elseif (is_string($value)) {
0 ignored issues
show
introduced by
The condition is_string($value) is always true.
Loading history...
192
            [$name, $index, $value] = static::resolveByString($value);
193
        }
194
195
        if (is_string($name) && method_exists(static::class, $name)) {
196
            return forward_static_call(static::class.'::'.$name);
197
        }
198
199
        return new static($name, $value, $index);
200
    }
201
202
    public static function toArray(): array
203
    {
204
        return array_combine(static::getValues(), static::getIndices());
205
    }
206
207
    /**
208
     * @return \Spatie\Enum\Enumerable[]
209
     */
210
    public static function getAll(): array
211
    {
212
        return array_map(static function (int $index): Enumerable {
213
            return static::make($index);
214
        }, static::getIndices());
215
    }
216
217
    public static function isValidIndex(int $index): bool
218
    {
219
        return in_array($index, static::getIndices(), true);
220
    }
221
222
    public static function isValidName(string $value): bool
223
    {
224
        return in_array(strtoupper($value), static::getNames(), true);
225
    }
226
227
    public static function isValidValue(string $value): bool
228
    {
229
        return in_array($value, static::getValues(), true);
230
    }
231
232
    protected static function resolve(): array
233
    {
234
        $values = [];
235
236
        $class = static::class;
237
238
        if (isset(self::$cache[$class])) {
239
            return self::$cache[$class];
240
        }
241
242
        self::$cache[$class] = [];
243
244
        $reflection = new ReflectionClass(static::class);
245
246
        foreach (static::resolveFromDocBlocks($reflection) as $value) {
247
            $values[] = $value;
248
        }
249
250
        foreach (static::resolveFromStaticMethods($reflection) as $value) {
251
            $values[] = $value;
252
        }
253
254
        foreach ($values as $index => $value) {
255
            $name = strtoupper($value);
256
257
            self::$cache[$class][$name] = [
258
                'name' => $name,
259
                'index' => static::getMappedIndex($name) ?? $index,
260
                'value' => static::getMappedValue($name) ?? $value,
261
            ];
262
        }
263
264
        foreach (array_keys(self::$cache[$class]) as $name) {
265
            self::$cache[$class][$name]['value'] = static::make($name)->getValue();
266
            self::$cache[$class][$name]['index'] = static::make($name)->getIndex();
267
        }
268
269
        $duplicatedValues = array_filter(array_count_values(static::getValues()), static function (int $count): bool {
270
            return $count > 1;
271
        });
272
273
        if (! empty($duplicatedValues)) {
274
            self::clearCache();
275
            throw new DuplicatedValueException(array_keys($duplicatedValues), static::class);
276
        }
277
278
        $duplicatedIndices = array_filter(array_count_values(static::getIndices()), static function (int $count): bool {
279
            return $count > 1;
280
        });
281
282
        if (! empty($duplicatedIndices)) {
283
            self::clearCache();
284
            throw new DuplicatedIndexException(array_keys($duplicatedIndices), static::class);
285
        }
286
287
        return self::$cache[$class];
288
    }
289
290
    protected static function resolveFromDocBlocks(ReflectionClass $reflection): array
291
    {
292
        $values = [];
293
294
        $docComment = $reflection->getDocComment();
295
296
        if (! $docComment) {
297
            return $values;
298
        }
299
300
        preg_match_all('/\@method static self ([\w]+)\(\)/', $docComment, $matches);
301
302
        foreach ($matches[1] ?? [] as $value) {
303
            $values[] = $value;
304
        }
305
306
        return $values;
307
    }
308
309
    protected static function resolveFromStaticMethods(ReflectionClass $reflection): array
310
    {
311
        $selfReflection = new ReflectionClass(self::class);
312
        $selfMethods = array_map(static function (ReflectionMethod $method): string {
313
            return $method->getName();
314
        }, $selfReflection->getMethods(ReflectionMethod::IS_STATIC | ReflectionMethod::IS_PUBLIC));
315
316
        $values = [];
317
        foreach ($reflection->getMethods(ReflectionMethod::IS_STATIC | ReflectionMethod::IS_PUBLIC) as $method) {
318
            if (
319
                $method->getDeclaringClass()->getName() === self::class
320
                || ! ($method->isPublic() && $method->isStatic())
321
                || in_array($method->getName(), $selfMethods)
322
            ) {
323
                continue;
324
            }
325
326
            $values[] = $method->getName();
327
        }
328
329
        return $values;
330
    }
331
332
    protected function resolveByStaticCall(): array
333
    {
334
        if (strpos(static::class, 'class@anonymous') !== 0) {
335
            throw new InvalidValueException(null, static::class);
336
        }
337
338
        $backtrace = debug_backtrace();
339
340
        $name = $backtrace[2]['function'];
341
342
        if (! static::isValidName($name)) {
343
            throw new InvalidValueException($name, static::class);
344
        }
345
346
        return static::resolve()[strtoupper($name)];
347
    }
348
349
    protected static function resolveByIndex(int $index): array
350
    {
351
        $name = array_combine(static::getIndices(), static::getNames())[$index];
352
        $value = array_search($index, static::toArray());
353
354
        return [$name, $index, $value];
355
    }
356
357
    protected static function resolveByString(string $string): array
358
    {
359
        if (static::isValidValue($string)) {
360
            return static::resolveByValue($string);
361
        }
362
363
        if (static::isValidName($string)) {
364
            return static::resolveByName($string);
365
        }
366
367
        throw new InvalidValueException($string, static::class);
368
    }
369
370
    protected static function resolveByValue(string $value): array
371
    {
372
        $index = static::toArray()[$value];
373
        $name = array_combine(static::getValues(), static::getNames())[$value];
374
375
        return [$name, $index, $value];
376
    }
377
378
    protected static function resolveByName(string $name): array
379
    {
380
        $name = strtoupper($name);
381
382
        ['value' => $value, 'index' => $index] = static::resolve()[$name];
383
384
        return [$name, $index, $value];
385
    }
386
387
    protected static function startsWith(string $haystack, string $needle): bool
388
    {
389
        return strlen($haystack) > 2 && strpos($haystack, $needle) === 0;
390
    }
391
392
    protected static function clearCache(): void
393
    {
394
        unset(self::$cache[static::class]);
395
    }
396
397
    protected static function getMappedIndex(string $name): ?int
398
    {
399
        if (! defined(static::class.'::MAP_INDEX')) {
400
            return null;
401
        }
402
403
        $map = [];
404
405
        foreach (constant(static::class.'::MAP_INDEX') as $key => $index) {
406
            $map[strtoupper($key)] = $index;
407
        }
408
409
        return $map[$name] ?? null;
410
    }
411
412
    protected static function getMappedValue(string $name): ?string
413
    {
414
        if (! defined(static::class.'::MAP_VALUE')) {
415
            return null;
416
        }
417
418
        $map = [];
419
420
        foreach (constant(static::class.'::MAP_VALUE') as $key => $index) {
421
            $map[strtoupper($key)] = $index;
422
        }
423
424
        return $map[$name] ?? null;
425
    }
426
}
427