1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Spatie\Enum; |
6
|
|
|
|
7
|
|
|
use TypeError; |
8
|
|
|
use ReflectionClass; |
9
|
|
|
use JsonSerializable; |
10
|
|
|
use ReflectionMethod; |
11
|
|
|
|
12
|
|
|
abstract class Enum implements JsonSerializable |
13
|
|
|
{ |
14
|
|
|
/** @var array */ |
15
|
|
|
protected static $cache = []; |
16
|
|
|
|
17
|
|
|
/** @var array */ |
18
|
|
|
protected static $map = []; |
19
|
|
|
|
20
|
|
|
/** @var string */ |
21
|
|
|
protected $value; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* @param string $value |
25
|
|
|
* |
26
|
|
|
* @return static |
27
|
|
|
*/ |
28
|
|
|
public static function from(string $value): Enum |
29
|
|
|
{ |
30
|
|
|
if (method_exists(static::class, $value)) { |
31
|
|
|
return forward_static_call(static::class.'::'.$value); |
32
|
|
|
} |
33
|
|
|
|
34
|
|
|
return new static($value); |
35
|
|
|
} |
36
|
|
|
|
37
|
|
|
public function __construct(string $value = null) |
38
|
|
|
{ |
39
|
|
|
if ($value === null) { |
40
|
|
|
$value = $this->resolveValueFromStaticCall(); |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
$enumValues = self::resolve(); |
44
|
|
|
|
45
|
|
|
if (isset($enumValues[strtolower($value)])) { |
46
|
|
|
$value = $enumValues[strtolower($value)]; |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
if (! in_array($value, $enumValues)) { |
50
|
|
|
throw new TypeError("Value {$value} not available in enum ".static::class); |
|
|
|
|
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
if ($value === null) { |
54
|
|
|
throw new TypeError("Value of enum can't be null"); |
|
|
|
|
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
$this->value = $value; |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
public static function __callStatic($name, $arguments) |
61
|
|
|
{ |
62
|
|
|
if (strlen($name) > 2 && strpos($name, 'is') === 0) { |
63
|
|
|
if (! isset($arguments[0])) { |
64
|
|
|
throw new \ArgumentCountError(sprintf('Calling %s::%s() in static context requires one argument', static::class, $name)); |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
return static::from($arguments[0])->$name(); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
if (! isset(self::resolve()[strtolower($name)])) { |
71
|
|
|
throw new TypeError("Method {$name} not available in enum ".static::class); |
|
|
|
|
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
return new static($name); |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
public function __call($name, $arguments) |
78
|
|
|
{ |
79
|
|
|
if (strlen($name) > 2 && strpos($name, 'is') === 0) { |
80
|
|
|
return $this->equals(substr($name, 2)); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
if (isset(self::resolve()[strtolower($name)])) { |
84
|
|
|
return static::__callStatic($name, $arguments); |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
throw new \BadMethodCallException(sprintf('Call to undefined method %s->%s()', static::class, $name)); |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* @param string|\Spatie\Enum\Enum $enum |
92
|
|
|
* |
93
|
|
|
* @return bool |
94
|
|
|
*/ |
95
|
|
|
public function equals($enum): bool |
96
|
|
|
{ |
97
|
|
|
if (is_string($enum)) { |
98
|
|
|
$enum = static::from($enum); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
if (! $enum instanceof $this) { |
102
|
|
|
return false; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
if ($enum->value !== $this->value) { |
106
|
|
|
return false; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
return true; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* @param string[]|\Spatie\Enum\Enum[] $enums |
114
|
|
|
* |
115
|
|
|
* @return bool |
116
|
|
|
*/ |
117
|
|
|
public function isOneOf(array $enums): bool |
118
|
|
|
{ |
119
|
|
|
foreach ($enums as $enum) { |
120
|
|
|
if ($this->equals($enum)) { |
121
|
|
|
return true; |
122
|
|
|
} |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
return false; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
public function __toString(): string |
129
|
|
|
{ |
130
|
|
|
return $this->value; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
public function jsonSerialize() |
134
|
|
|
{ |
135
|
|
|
return $this->value; |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
public static function toArray(): array |
139
|
|
|
{ |
140
|
|
|
return self::resolve(); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
public static function getKeys(): array |
144
|
|
|
{ |
145
|
|
|
return array_keys(self::resolve()); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
public static function getValues(): array |
149
|
|
|
{ |
150
|
|
|
return array_values(self::resolve()); |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
protected static function resolve(): array |
154
|
|
|
{ |
155
|
|
|
$enumValues = []; |
156
|
|
|
|
157
|
|
|
$class = static::class; |
158
|
|
|
|
159
|
|
|
if (isset(self::$cache[$class])) { |
160
|
|
|
return self::$cache[$class]; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
$staticReflection = new ReflectionClass(static::class); |
164
|
|
|
|
165
|
|
|
foreach (self::resolveValuesFromStaticMethods($staticReflection) as $value => $name) { |
166
|
|
|
$enumValues[$value] = $name; |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
foreach (self::resolveFromDocblocks($staticReflection) as $value => $name) { |
170
|
|
|
$enumValues[$value] = $name; |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
return self::$cache[$class] = $enumValues; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
protected static function resolveValuesFromStaticMethods(ReflectionClass $staticReflection): array |
177
|
|
|
{ |
178
|
|
|
$enumValues = []; |
179
|
|
|
foreach ($staticReflection->getMethods(ReflectionMethod::IS_STATIC) as $method) { |
180
|
|
|
if ($method->getDeclaringClass()->getName() === self::class) { |
181
|
|
|
continue; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
$methodName = $method->getName(); |
185
|
|
|
$enumValues[strtolower($methodName)] = static::$map[$methodName] ?? $methodName; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
return $enumValues; |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
protected static function resolveFromDocblocks(ReflectionClass $staticReflection): array |
192
|
|
|
{ |
193
|
|
|
$enumValues = []; |
194
|
|
|
|
195
|
|
|
$docComment = $staticReflection->getDocComment(); |
196
|
|
|
|
197
|
|
|
if (! $docComment) { |
198
|
|
|
return $enumValues; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
preg_match_all('/\@method static self ([\w]+)\(\)/', $docComment, $matches); |
202
|
|
|
|
203
|
|
|
foreach ($matches[1] ?? [] as $valueName) { |
204
|
|
|
$enumValues[strtolower($valueName)] = static::$map[$valueName] ?? $valueName; |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
return $enumValues; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
protected function resolveValueFromStaticCall(): ?string |
211
|
|
|
{ |
212
|
|
|
if (strpos(get_class($this), 'class@anonymous') === 0) { |
213
|
|
|
$backtrace = debug_backtrace(); |
214
|
|
|
|
215
|
|
|
return $backtrace[2]['function']; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
return null; |
219
|
|
|
} |
220
|
|
|
} |
221
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.