Passed
Push — master ( 25b5c8...2d14f5 )
by Константин
01:50
created

Enum   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 297
Duplicated Lines 2.02 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 88.1%

Importance

Changes 0
Metric Value
wmc 39
lcom 1
cbo 0
dl 6
loc 297
ccs 74
cts 84
cp 0.881
rs 9.28
c 0
b 0
f 0

How to fix   Duplicated Code   

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:

1
<?php
2
3
namespace Grachevko\Enum;
4
5
use function array_keys;
6
use function array_values;
7
use BadMethodCallException;
8
use function in_array;
9
use InvalidArgumentException;
10
use function is_int;
11
use LogicException;
12
use ReflectionClass;
13
use ReflectionException;
14
use ReflectionProperty;
15
use Serializable;
16
use function sprintf;
17
18
/**
19
 * @author Konstantin Grachev <[email protected]>
20
 */
21
abstract class Enum implements Serializable
22
{
23
    /**
24
     * @var int
25
     */
26
    private $id;
27
28
    /**
29
     * @var ReflectionClass[]
30
     */
31
    private static $reflections = [];
32
33
    /**
34
     * @var ReflectionProperty[][]
35
     */
36
    private static $properties = [];
37
38
    /**
39
     * @var Enum[][]
40
     */
41
    private static $instances = [];
42
43
    /**
44
     * @param int $id
45
     *
46
     * @throws InvalidArgumentException
47
     * @throws ReflectionException
48
     */
49 8
    private function __construct(int $id)
50
    {
51 8
        if (!in_array($id, self::getReflection()->getConstants(), true)) {
52
            throw new InvalidArgumentException(
53
                sprintf('Undefined enum "%s" of class "%s"', $id, static::class)
54
            );
55
        }
56
57 6
        $this->id = $id;
58 6
    }
59
60
    /**
61
     * @return string
62
     */
63
    final public function __toString(): string
64
    {
65
        return (string) $this->getId();
66
    }
67
68
    /**
69
     * @param string $name
70
     * @param array  $arguments
71
     *
72
     * @throws ReflectionException
73
     * @throws InvalidArgumentException
74
     * @throws BadMethodCallException
75
     *
76
     * @return bool|string
77
     */
78 5
    final public function __call(string $name, array $arguments)
79
    {
80 5
        $reflection = self::getReflection();
81
82 5
        if (0 === strpos($name, 'is') && ctype_upper($name[2])) {
83 3
            return $this->eq(static::create($reflection->getConstant(self::stringToConstant(substr($name, 2)))));
84
        }
85
86 2
        if (0 === strpos($name, 'get') && ctype_upper($name[3])) {
87 2
            return $this->get(lcfirst(substr($name, 3)));
88
        }
89
90
        throw new BadMethodCallException(
91
            sprintf('Undefined method "%s" in class "%s"', $name, static::class)
92
        );
93
    }
94
95
    /**
96
     * @param string $name
97
     * @param array  $arguments
98
     *
99
     * @throws ReflectionException
100
     * @throws BadMethodCallException
101
     * @throws LogicException
102
     *
103
     * @return static
104
     */
105 14
    final public static function __callStatic(string $name, array $arguments)
106
    {
107 14
        $reflectionClass = self::getReflection();
108
109 14
        $const = self::stringToConstant($name);
110 14
        $constants = $reflectionClass->getConstants();
111
112 14
        if (array_key_exists($const, $constants)) {
113 12
            return static::create($constants[$const]);
114
        }
115
116 2
        if (0 === strpos($name, 'from') && ctype_upper($name[4])) {
117 1
            return static::from(lcfirst(substr($name, 4)), $arguments[0]);
118
        }
119
120 1
        throw new BadMethodCallException(
121 1
            sprintf('Undefined method "%s" in class "%s"', $name, static::class)
122
        );
123
    }
124
125
    /**
126
     * @param int $id
127
     *
128
     * @throws ReflectionException
129
     *
130
     * @return static
131
     */
132 16
    final public static function create(int $id): self
133
    {
134 16
        return self::$instances[static::class][$id] ?? self::$instances[static::class][$id] = new static($id);
135
    }
136
137
    /**
138
     * @param string $property
139
     * @param mixed  $value
140
     *
141
     * @throws ReflectionException
142
     *
143
     * @return static
144
     */
145 1
    final public static function from(string $property, $value): self
146
    {
147 1
        return static::create(array_flip(self::$properties[static::class][$property]->getValue())[$value]);
148
    }
149
150
    /**
151
     * @return int
152
     */
153 13
    final public function getId(): int
154
    {
155 13
        return $this->id;
156
    }
157
158
    /**
159
     * @throws ReflectionException
160
     *
161
     * @return string
162
     */
163 5
    final public function getName(): string
164
    {
165 5
        if (self::getReflection()->hasProperty('name')) {
166 2
            return $this->get('name');
167
        }
168
169 4
        return strtolower(array_flip(self::getReflection()->getConstants())[$this->getId()]);
170
    }
171
172
    /**
173
     * @param string $property
174
     *
175
     * @throws InvalidArgumentException
176
     *
177
     * @return mixed
178
     */
179 5
    final public function get(string $property)
180
    {
181 5
        if (!array_key_exists($property, self::$properties[static::class])) {
182 1
            throw new InvalidArgumentException(
183 1
                sprintf('Property "%s" not exist at class "%s"', $property, static::class)
184
            );
185
        }
186
187 5
        return self::$properties[static::class][$property]->getValue()[$this->getId()];
188
    }
189
190
    /**
191
     * @param Enum[]|int[]|string[] $ids
192
     * @param bool                  $reverse
193
     *
194
     * @throws ReflectionException
195
     *
196
     * @return static[]
197
     */
198 1
    final public static function all(array $ids = [], bool $reverse = false): array
199
    {
200
        $ids = array_map(static function ($id) {
201 1
            return $id instanceof Enum ? $id->getId() : (int) $id;
202 1
        }, $ids);
203
204 1
        $all = array_values(self::getReflection()->getConstants());
205
206 1
        if ([] === $ids) {
207 1
            $ids = $all;
208
        } else {
209 1
            $ids = $reverse ? array_diff($all, $ids) : $ids;
210
        }
211
212
        return array_map(static function (int $id) {
213 1
            return static::create($id);
214 1
        }, $ids);
215
    }
216
217
    /**
218
     * @param Enum $enum
219
     *
220
     * @return bool
221
     */
222 5
    final public function eq(Enum $enum): bool
223
    {
224 5
        return static::class === get_class($enum) && $enum->getId() === $this->getId();
225
    }
226
227
    /**
228
     * @return string
229
     */
230 1
    final public function serialize(): string
231
    {
232 1
        return (string) $this->getId();
233
    }
234
235
    /**
236
     * @param string $serialized
237
     */
238 1
    final public function unserialize($serialized): void
239
    {
240 1
        $this->id = (int) $serialized;
241 1
    }
242
243
    /**
244
     * @param string $string
245
     *
246
     * @throws InvalidArgumentException
247
     *
248
     * @return string
249
     */
250 14
    private static function stringToConstant(string $string): string
251
    {
252 14
        $constant = preg_replace('/\B([A-Z])/', '_$1', $string);
253
254 14
        if (null === $constant) {
255
            throw new InvalidArgumentException(sprintf('preg_replace return null for string "%s"', $string));
256
        }
257
258 14
        return strtoupper($constant);
259
    }
260
261
    /**
262
     * @throws ReflectionException
263
     * @throws LogicException
264
     *
265
     * @return ReflectionClass
266
     */
267 17
    private static function getReflection(): ReflectionClass
268
    {
269 17
        $class = static::class;
270
271 17
        return self::$reflections[$class] ?? self::$reflections[$class] = self::validate(new ReflectionClass($class));
272
    }
273
274
    /**
275
     * @param ReflectionClass $reflection
276
     *
277
     * @throws LogicException
278
     *
279
     * @return ReflectionClass
280
     */
281 5
    private static function validate(ReflectionClass $reflection): ReflectionClass
282
    {
283 5
        $constants = $reflection->getConstants();
284
285 5
        if ([] === $constants) {
286 1
            throw new LogicException(sprintf('Class %s must define Constants', static::class));
287
        }
288
289 4
        foreach ($reflection->getReflectionConstants() as $reflectionConstant) {
290 4
            if (false === $reflectionConstant->isPrivate()) {
291
                throw new LogicException(sprintf(
292
                    'Constant "%s" of class "%s" must be private by design.',
293
                    $reflectionConstant->getName(),
294
                    static::class,
295
                    ));
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected ')'
Loading history...
296
            }
297
298 4
            if (!is_int($reflectionConstant->getValue())) {
299 1
                throw new LogicException(sprintf(
300 1
                    'Constants "%s" of class "%s" must be type of integer by design.',
301 1
                    $reflectionConstant->getName(),
302 4
                    static::class,
303
                    ));
304
            }
305
        }
306
307 3
        foreach ($reflection->getProperties() as $property) {
308 1
            $property->setAccessible(true);
309
310 1
            self::$properties[static::class][$property->getName()] = $property;
311
312 1
            if ($property->isPublic()) {
313
                throw new LogicException(sprintf(
314
                    'Property "%s" of class "%s" must be private or protected by design.',
315
                    $property->getName(),
316
                    static::class,
317
                    ));
318
            }
319
320 1
            if (!$property->isStatic()) {
321
                throw new LogicException(sprintf(
322
                    'Property "%s" of class "%s" must be static by design.',
323
                    $property->getName(),
324
                    static::class,
325
                    ));
326
            }
327
328 1
            if (array_values($constants) !== array_keys($property->getValue())) {
329
                throw new LogicException(sprintf(
330
                    'Property "%s" of class "%s" must have values for all constants by design.',
331
                    $property->getName(),
332 1
                    static::class
333
                ));
334
            }
335
        }
336
337 3
        return $reflection;
338
    }
339
}
340