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