1
|
|
|
<?php |
2
|
|
|
declare(strict_types=1); |
3
|
|
|
namespace Thunder\Platenum\Enum; |
4
|
|
|
|
5
|
|
|
use Thunder\Platenum\Exception\PlatenumException; |
6
|
|
|
|
7
|
|
|
/** |
8
|
|
|
* @author Tomasz Kowalczyk <[email protected]> |
9
|
|
|
* @psalm-template T |
10
|
|
|
* @psalm-immutable |
11
|
|
|
*/ |
12
|
|
|
trait EnumTrait |
13
|
|
|
{ |
14
|
|
|
/** @var string */ |
15
|
|
|
private $member; |
16
|
|
|
/** @psalm-var T */ |
17
|
|
|
private $value; |
18
|
|
|
|
19
|
|
|
/** @psalm-var non-empty-array<string,non-empty-array<string,int|string>> */ |
20
|
|
|
protected static $members = []; |
21
|
|
|
/** @var array<string,array<string,static>> */ |
22
|
|
|
protected static $instances = []; |
23
|
|
|
|
24
|
|
|
/** @psalm-param T $value */ |
25
|
26 |
|
final private function __construct(string $member, $value) |
26
|
|
|
{ |
27
|
26 |
|
$this->member = $member; |
28
|
26 |
|
$this->value = $value; |
29
|
26 |
|
} |
30
|
|
|
|
31
|
|
|
/* --- CREATE --- */ |
32
|
|
|
|
33
|
38 |
|
final public static function __callStatic(string $member, array $arguments) |
34
|
|
|
{ |
35
|
38 |
|
$class = static::class; |
36
|
38 |
|
if($arguments) { |
|
|
|
|
37
|
1 |
|
throw PlatenumException::fromConstantArguments($class); |
38
|
|
|
} |
39
|
|
|
|
40
|
37 |
|
return static::fromMember($member); |
41
|
|
|
} |
42
|
|
|
|
43
|
45 |
|
final public static function fromMember(string $member): self |
44
|
|
|
{ |
45
|
45 |
|
$class = static::class; |
46
|
45 |
|
if(isset(static::$instances[$class][$member])) { |
47
|
9 |
|
return static::$instances[$class][$member]; |
48
|
|
|
} |
49
|
|
|
|
50
|
44 |
|
static::resolveMembers(); |
51
|
36 |
|
if(false === array_key_exists($member, static::$members[$class])) { |
52
|
10 |
|
static::throwInvalidMemberException($member); |
53
|
6 |
|
static::throwDefaultInvalidMemberException($member); |
54
|
|
|
} |
55
|
|
|
|
56
|
26 |
|
return static::$instances[$class][$member] = static::fromMemberAndValue($member, static::$members[$class][$member]); |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* @psalm-param int|string $value |
61
|
|
|
* @psalm-return static |
62
|
|
|
*/ |
63
|
26 |
|
final private static function fromMemberAndValue(string $member, $value): self |
64
|
|
|
{ |
65
|
|
|
/** @psalm-suppress UnsafeInstantiation */ |
66
|
26 |
|
return new static($member, $value); |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* @param mixed $value |
71
|
|
|
* @return static |
72
|
|
|
*/ |
73
|
14 |
|
final public static function fromValue($value): self |
74
|
|
|
{ |
75
|
14 |
|
$class = static::class; |
76
|
14 |
|
if(false === is_scalar($value)) { |
77
|
1 |
|
throw PlatenumException::fromIllegalValue($class, $value); |
78
|
|
|
} |
79
|
|
|
|
80
|
13 |
|
static::resolveMembers(); |
81
|
11 |
|
$member = array_search($value, static::$members[$class], true); |
82
|
11 |
|
if(false === $member) { |
83
|
5 |
|
static::throwInvalidValueException($value); |
84
|
3 |
|
static::throwDefaultInvalidValueException($value); |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** @var string $member */ |
88
|
6 |
|
return static::fromMember($member); |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* @param static $enum |
93
|
|
|
* @return static |
94
|
|
|
*/ |
95
|
4 |
|
final public static function fromEnum($enum): self |
96
|
|
|
{ |
97
|
4 |
|
if(false === $enum instanceof static) { |
98
|
2 |
|
throw PlatenumException::fromMismatchedClass(static::class, \get_class($enum)); |
99
|
|
|
} |
100
|
|
|
|
101
|
2 |
|
return static::fromValue($enum->value); |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* @param static &$enum |
106
|
|
|
* @param-out static $enum |
107
|
|
|
*/ |
108
|
2 |
|
final public function fromInstance(&$enum): void |
109
|
|
|
{ |
110
|
2 |
|
$enum = static::fromEnum($enum); |
111
|
1 |
|
} |
112
|
|
|
|
113
|
|
|
/* --- EXCEPTIONS --- */ |
114
|
|
|
|
115
|
3 |
|
private static function throwInvalidMemberException(string $member): void |
|
|
|
|
116
|
|
|
{ |
117
|
3 |
|
} |
118
|
|
|
|
119
|
9 |
|
private static function throwDefaultInvalidMemberException(string $member): void |
120
|
|
|
{ |
121
|
9 |
|
throw PlatenumException::fromInvalidMember(static::class, $member, static::$members[static::class]); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/** @param mixed $value */ |
125
|
2 |
|
private static function throwInvalidValueException($value): void |
|
|
|
|
126
|
|
|
{ |
127
|
2 |
|
} |
128
|
|
|
|
129
|
|
|
/** @param mixed $value */ |
130
|
6 |
|
private static function throwDefaultInvalidValueException($value): void |
131
|
|
|
{ |
132
|
6 |
|
throw PlatenumException::fromInvalidValue(static::class, $value); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/* --- COMPARE --- */ |
136
|
|
|
|
137
|
|
|
/** @param static $other */ |
138
|
3 |
|
final public function equals($other): bool |
139
|
|
|
{ |
140
|
3 |
|
return $other instanceof $this && $this->value === $other->value; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/* --- TRANSFORM --- */ |
144
|
|
|
|
145
|
6 |
|
final public function getMember(): string |
146
|
|
|
{ |
147
|
6 |
|
return $this->member; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** @psalm-return T */ |
151
|
9 |
|
final public function getValue() |
152
|
|
|
{ |
153
|
9 |
|
return $this->value; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** @psalm-return T */ |
157
|
1 |
|
final public function jsonSerialize() |
158
|
|
|
{ |
159
|
1 |
|
return $this->getValue(); |
160
|
|
|
} |
161
|
|
|
|
162
|
1 |
|
final public function __toString(): string |
163
|
|
|
{ |
164
|
1 |
|
return (string)$this->getValue(); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
/* --- CHECK --- */ |
168
|
|
|
|
169
|
8 |
|
final public static function memberExists(string $member): bool |
170
|
|
|
{ |
171
|
8 |
|
static::resolveMembers(); |
172
|
|
|
|
173
|
7 |
|
return array_key_exists($member, static::$members[static::class]); |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** @param int|string $value */ |
177
|
8 |
|
final public static function valueExists($value): bool |
178
|
|
|
{ |
179
|
8 |
|
static::resolveMembers(); |
180
|
|
|
|
181
|
8 |
|
return \in_array($value, static::$members[static::class], true); |
182
|
|
|
} |
183
|
|
|
|
184
|
1 |
|
final public function hasMember(string $members): bool |
185
|
|
|
{ |
186
|
1 |
|
return $members === $this->member; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** @psalm-param T $value */ |
190
|
1 |
|
final public function hasValue($value): bool |
191
|
|
|
{ |
192
|
1 |
|
return $value === $this->value; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
/* --- INFO --- */ |
196
|
|
|
|
197
|
|
|
/** @return int|string */ |
198
|
6 |
|
final public static function memberToValue(string $member) |
199
|
|
|
{ |
200
|
6 |
|
if(false === static::memberExists($member)) { |
201
|
5 |
|
static::throwInvalidMemberException($member); |
202
|
3 |
|
static::throwDefaultInvalidMemberException($member); |
203
|
|
|
} |
204
|
|
|
|
205
|
1 |
|
return static::$members[static::class][$member]; |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
/** @param int|string $value */ |
209
|
7 |
|
final public static function valueToMember($value): string |
210
|
|
|
{ |
211
|
7 |
|
if(false === static::valueExists($value)) { |
212
|
5 |
|
static::throwInvalidValueException($value); |
213
|
3 |
|
static::throwDefaultInvalidValueException($value); |
214
|
|
|
} |
215
|
|
|
|
216
|
2 |
|
return (string)array_search($value, static::$members[static::class], true); |
217
|
|
|
} |
218
|
|
|
|
219
|
1 |
|
final public static function getMembers(): array |
220
|
|
|
{ |
221
|
1 |
|
static::resolveMembers(); |
222
|
|
|
|
223
|
1 |
|
return array_keys(static::$members[static::class]); |
224
|
|
|
} |
225
|
|
|
|
226
|
1 |
|
final public static function getValues(): array |
227
|
|
|
{ |
228
|
1 |
|
static::resolveMembers(); |
229
|
|
|
|
230
|
1 |
|
return array_values(static::$members[static::class]); |
231
|
|
|
} |
232
|
|
|
|
233
|
5 |
|
final public static function getMembersAndValues(): array |
234
|
|
|
{ |
235
|
5 |
|
static::resolveMembers(); |
236
|
|
|
|
237
|
5 |
|
return static::$members[static::class]; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
/* --- SOURCE --- */ |
241
|
|
|
|
242
|
74 |
|
final private static function resolveMembers(): void |
243
|
|
|
{ |
244
|
74 |
|
$class = static::class; |
245
|
74 |
|
if(isset(static::$members[$class])) { |
246
|
15 |
|
return; |
247
|
|
|
} |
248
|
|
|
|
249
|
73 |
|
$throwMissingResolve = function(string $class): void { |
250
|
1 |
|
throw PlatenumException::fromMissingResolve($class); |
251
|
73 |
|
}; |
252
|
|
|
// reflection instead of method_exists because of PHP 7.4 bug #78632 |
253
|
|
|
// @see https://bugs.php.net/bug.php?id=78632 |
254
|
73 |
|
$hasResolve = (new \ReflectionClass($class))->hasMethod('resolve'); |
255
|
|
|
/** @psalm-var array<string,T> $members */ |
256
|
73 |
|
$members = $hasResolve ? static::resolve() : $throwMissingResolve($class); |
|
|
|
|
257
|
66 |
|
if(empty($members)) { |
258
|
1 |
|
throw PlatenumException::fromEmptyMembers($class); |
259
|
|
|
} |
260
|
65 |
|
if(\count($members) !== \count(\array_unique($members))) { |
|
|
|
|
261
|
1 |
|
throw PlatenumException::fromNonUniqueMembers($class); |
262
|
|
|
} |
263
|
|
|
/** @psalm-suppress RedundantCondition */ |
264
|
64 |
|
if(['string'] !== \array_unique(\array_map('gettype', array_keys($members)))) { |
|
|
|
|
265
|
1 |
|
throw PlatenumException::fromNonStringMembers($class); |
266
|
|
|
} |
267
|
63 |
|
if(1 !== \count(\array_unique(\array_map('gettype', $members)))) { |
|
|
|
|
268
|
1 |
|
throw PlatenumException::fromNonUniformMemberValues($class, $members); |
|
|
|
|
269
|
|
|
} |
270
|
|
|
|
271
|
62 |
|
static::$members[$class] = $members; |
272
|
62 |
|
} |
273
|
|
|
|
274
|
|
|
/* --- MAGIC --- */ |
275
|
|
|
|
276
|
1 |
|
final public function __clone() |
277
|
|
|
{ |
278
|
1 |
|
throw PlatenumException::fromMagicMethod(static::class, __FUNCTION__); |
279
|
|
|
} |
280
|
|
|
|
281
|
1 |
|
final public function __call(string $name, array $arguments) |
282
|
|
|
{ |
283
|
1 |
|
throw PlatenumException::fromMagicMethod(static::class, __FUNCTION__); |
284
|
|
|
} |
285
|
|
|
|
286
|
1 |
|
final public function __invoke() |
287
|
|
|
{ |
288
|
1 |
|
throw PlatenumException::fromMagicMethod(static::class, __FUNCTION__); |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
/** @param mixed $name */ |
292
|
1 |
|
final public function __isset($name) |
293
|
|
|
{ |
294
|
1 |
|
throw PlatenumException::fromMagicMethod(static::class, __FUNCTION__); |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
/** @param mixed $name */ |
298
|
1 |
|
final public function __unset($name) |
299
|
|
|
{ |
300
|
1 |
|
throw PlatenumException::fromMagicMethod(static::class, __FUNCTION__); |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
/** @param mixed $value */ |
304
|
1 |
|
final public function __set(string $name, $value) |
305
|
|
|
{ |
306
|
1 |
|
throw PlatenumException::fromMagicMethod(static::class, __FUNCTION__); |
307
|
|
|
} |
308
|
|
|
|
309
|
1 |
|
final public function __get(string $name) |
310
|
|
|
{ |
311
|
1 |
|
throw PlatenumException::fromMagicMethod(static::class, __FUNCTION__); |
312
|
|
|
} |
313
|
|
|
} |
314
|
|
|
|
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.