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