1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace Cerbero\Enum; |
||
6 | |||
7 | use Closure; |
||
8 | use Generator; |
||
9 | use GlobIterator; |
||
10 | use UnitEnum; |
||
11 | |||
12 | /** |
||
13 | * The enums manager. |
||
14 | */ |
||
15 | class Enums |
||
16 | { |
||
17 | /** |
||
18 | * The application base path. |
||
19 | * |
||
20 | * @var string |
||
21 | */ |
||
22 | protected static ?string $basePath = null; |
||
23 | |||
24 | /** |
||
25 | * The glob paths to find enums in. |
||
26 | * |
||
27 | * @var string[] |
||
28 | */ |
||
29 | protected static array $paths = []; |
||
30 | |||
31 | /** |
||
32 | * The TypeScript path to sync enums in. |
||
33 | * |
||
34 | * @var Closure(class-string<UnitEnum>|string $enum): string|string |
||
35 | */ |
||
36 | protected static Closure|string $typeScript = 'resources/js/enums/index.ts'; |
||
37 | |||
38 | /** |
||
39 | * The logic to run when an inaccessible enum method is called. |
||
40 | * |
||
41 | * @var ?Closure(class-string<UnitEnum> $enum, string $name, array<array-key, mixed> $arguments): mixed |
||
42 | */ |
||
43 | protected static ?Closure $onStaticCall = null; |
||
44 | |||
45 | /** |
||
46 | * The logic to run when an inaccessible case method is called. |
||
47 | * |
||
48 | * @var ?Closure(UnitEnum $case, string $name, array<array-key, mixed> $arguments): mixed |
||
49 | */ |
||
50 | protected static ?Closure $onCall = null; |
||
51 | |||
52 | /** |
||
53 | * The logic to run when a case is invoked. |
||
54 | * |
||
55 | * @var ?Closure(UnitEnum $case, mixed ...$arguments): mixed |
||
56 | */ |
||
57 | protected static ?Closure $onInvoke = null; |
||
58 | |||
59 | /** |
||
60 | * Set the application base path. |
||
61 | */ |
||
62 | 11 | public static function setBasePath(string $path): void |
|
63 | { |
||
64 | 11 | static::$basePath = path($path); |
|
65 | } |
||
66 | |||
67 | /** |
||
68 | * Retrieve the application base path, optionally appending the given path. |
||
69 | */ |
||
70 | 21 | public static function basePath(?string $path = null): string |
|
71 | { |
||
72 | 21 | $basePath = static::$basePath ?: dirname(__DIR__, 4); |
|
73 | |||
74 | 21 | return $path === null ? $basePath : $basePath . DIRECTORY_SEPARATOR . ltrim(path($path), '\/'); |
|
75 | } |
||
76 | |||
77 | /** |
||
78 | * Set the glob paths to find all the application enums. |
||
79 | */ |
||
80 | 5 | public static function setPaths(string ...$paths): void |
|
81 | { |
||
82 | 5 | static::$paths = array_map(path(...), $paths); |
|
83 | } |
||
84 | |||
85 | /** |
||
86 | * Retrieve the paths to find all the application enums. |
||
87 | * |
||
88 | * @return string[] |
||
89 | */ |
||
90 | 11 | public static function paths(): array |
|
91 | { |
||
92 | 11 | return static::$paths; |
|
93 | } |
||
94 | |||
95 | /** |
||
96 | * Set the TypeScript path to sync enums in. |
||
97 | * |
||
98 | * @param callable(class-string<UnitEnum>|string $enum): string|string $path |
||
99 | */ |
||
100 | 4 | public static function setTypeScript(callable|string $path): void |
|
101 | { |
||
102 | /** @phpstan-ignore assign.propertyType */ |
||
103 | 4 | static::$typeScript = is_callable($path) ? $path(...) : $path; |
|
0 ignored issues
–
show
|
|||
104 | } |
||
105 | |||
106 | /** |
||
107 | * Retrieve the TypeScript path, optionally for the given enum. |
||
108 | * |
||
109 | * @param class-string<UnitEnum>|string $enum |
||
0 ignored issues
–
show
|
|||
110 | * @return string |
||
111 | */ |
||
112 | 7 | public static function typeScript(string $enum = ''): string |
|
113 | { |
||
114 | 7 | return static::$typeScript instanceof Closure ? (static::$typeScript)($enum) : static::$typeScript; |
|
115 | } |
||
116 | |||
117 | /** |
||
118 | * Yield the namespaces of all the application enums. |
||
119 | * |
||
120 | * @return Generator<int, class-string<UnitEnum>> |
||
121 | */ |
||
122 | 2 | public static function namespaces(): Generator |
|
123 | { |
||
124 | 2 | $psr4 = psr4(); |
|
125 | |||
126 | 2 | foreach (static::paths() as $path) { |
|
127 | 2 | $pattern = static::basePath($path) . DIRECTORY_SEPARATOR . '*.php'; |
|
128 | |||
129 | 2 | foreach (new GlobIterator($pattern) as $fileInfo) { |
|
130 | /** @var \SplFileInfo $fileInfo */ |
||
131 | 2 | $enumPath = (string) $fileInfo->getRealPath(); |
|
132 | |||
133 | 2 | foreach ($psr4 as $root => $relative) { |
|
134 | 2 | $absolute = static::basePath($relative) . DIRECTORY_SEPARATOR; |
|
135 | |||
136 | 2 | if (str_starts_with($enumPath, $absolute)) { |
|
137 | 2 | $enum = strtr($enumPath, [$absolute => $root, '/' => '\\', '.php' => '']); |
|
138 | |||
139 | 2 | if (enum_exists($enum)) { |
|
140 | 2 | yield $enum; |
|
141 | } |
||
142 | } |
||
143 | } |
||
144 | } |
||
145 | } |
||
146 | } |
||
147 | |||
148 | /** |
||
149 | * Set the logic to run when an inaccessible enum method is called. |
||
150 | * |
||
151 | * @param callable(class-string<UnitEnum> $enum, string $name, array<array-key, mixed> $arguments): mixed $callback |
||
152 | */ |
||
153 | 2 | public static function onStaticCall(callable $callback): void |
|
154 | { |
||
155 | 2 | static::$onStaticCall = $callback(...); |
|
156 | } |
||
157 | |||
158 | /** |
||
159 | * Handle the call to an inaccessible enum method. |
||
160 | * |
||
161 | * @param class-string<UnitEnum> $enum |
||
0 ignored issues
–
show
|
|||
162 | * @param array<array-key, mixed> $arguments |
||
163 | */ |
||
164 | 4 | public static function handleStaticCall(string $enum, string $name, array $arguments): mixed |
|
165 | { |
||
166 | 4 | return static::$onStaticCall |
|
167 | 2 | ? (static::$onStaticCall)($enum, $name, $arguments) |
|
168 | 4 | : $enum::fromName($name)->value(); /** @phpstan-ignore method.nonObject */ |
|
169 | } |
||
170 | |||
171 | /** |
||
172 | * Set the logic to run when an inaccessible case method is called. |
||
173 | * |
||
174 | * @param callable(UnitEnum $case, string $name, array<array-key, mixed> $arguments): mixed $callback |
||
175 | */ |
||
176 | 2 | public static function onCall(callable $callback): void |
|
177 | { |
||
178 | 2 | static::$onCall = $callback(...); |
|
179 | } |
||
180 | |||
181 | /** |
||
182 | * Handle the call to an inaccessible case method. |
||
183 | * |
||
184 | * @param array<array-key, mixed> $arguments |
||
0 ignored issues
–
show
|
|||
185 | */ |
||
186 | 14 | public static function handleCall(UnitEnum $case, string $name, array $arguments): mixed |
|
187 | { |
||
188 | 14 | return static::$onCall ? (static::$onCall)($case, $name, $arguments) : $case->resolveMetaAttribute($name); |
|
189 | } |
||
190 | |||
191 | /** |
||
192 | * Set the logic to run when a case is invoked. |
||
193 | * |
||
194 | * @param callable(UnitEnum $case, mixed ...$arguments): mixed $callback |
||
195 | */ |
||
196 | 2 | public static function onInvoke(callable $callback): void |
|
197 | { |
||
198 | 2 | static::$onInvoke = $callback(...); |
|
199 | } |
||
200 | |||
201 | /** |
||
202 | * Handle the invocation of a case. |
||
203 | */ |
||
204 | 2 | public static function handleInvoke(UnitEnum $case, mixed ...$arguments): mixed |
|
205 | { |
||
206 | 2 | return static::$onInvoke ? (static::$onInvoke)($case, ...$arguments) : $case->value(); |
|
207 | } |
||
208 | } |
||
209 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.