1: | <?php declare(strict_types=1); |
2: | |
3: | namespace Salient\Utility; |
4: | |
5: | use Salient\Utility\Internal\NamedType; |
6: | use Closure; |
7: | use InvalidArgumentException; |
8: | use ReflectionAttribute; |
9: | use ReflectionClass; |
10: | use ReflectionClassConstant; |
11: | use ReflectionException; |
12: | use ReflectionExtension; |
13: | use ReflectionFunction; |
14: | use ReflectionFunctionAbstract; |
15: | use ReflectionIntersectionType; |
16: | use ReflectionMethod; |
17: | use ReflectionNamedType; |
18: | use ReflectionParameter; |
19: | use ReflectionProperty; |
20: | use ReflectionType; |
21: | use ReflectionUnionType; |
22: | use ReflectionZendExtension; |
23: | |
24: | |
25: | |
26: | |
27: | |
28: | |
29: | final class Reflect extends AbstractUtility |
30: | { |
31: | |
32: | private static array $Constants = []; |
33: | |
34: | private static array $ConstantsByValue = []; |
35: | |
36: | |
37: | |
38: | |
39: | |
40: | |
41: | |
42: | public static function getNames(array $reflectors): array |
43: | { |
44: | foreach ($reflectors as $reflector) { |
45: | $names[] = $reflector->getName(); |
46: | } |
47: | return $names ?? []; |
48: | } |
49: | |
50: | |
51: | |
52: | |
53: | |
54: | |
55: | |
56: | public static function getBaseClass(ReflectionClass $class): ReflectionClass |
57: | { |
58: | while ($parent = $class->getParentClass()) { |
59: | $class = $parent; |
60: | } |
61: | return $class; |
62: | } |
63: | |
64: | |
65: | |
66: | |
67: | |
68: | |
69: | |
70: | public static function getPrototypeClass(ReflectionMethod $method): ReflectionClass |
71: | { |
72: | try { |
73: | return $method->getPrototype()->getDeclaringClass(); |
74: | } catch (ReflectionException $ex) { |
75: | return $method->getDeclaringClass(); |
76: | } |
77: | } |
78: | |
79: | |
80: | |
81: | |
82: | |
83: | |
84: | |
85: | public static function getAllProperties(ReflectionClass $class): array |
86: | { |
87: | do { |
88: | foreach ($class->getProperties() as $property) { |
89: | $name = $property->getName(); |
90: | if (isset($seen[$name])) { |
91: | continue; |
92: | } |
93: | $properties[] = $property; |
94: | $seen[$name] = true; |
95: | } |
96: | } while ($class = $class->getParentClass()); |
97: | |
98: | return $properties ?? []; |
99: | } |
100: | |
101: | |
102: | |
103: | |
104: | |
105: | |
106: | |
107: | |
108: | |
109: | |
110: | public static function getAcceptedTypes( |
111: | $function, |
112: | bool $skipBuiltins = false, |
113: | int $param = 0 |
114: | ): array { |
115: | if (!$function instanceof ReflectionFunctionAbstract) { |
116: | if (!$function instanceof Closure) { |
117: | $function = Closure::fromCallable($function); |
118: | } |
119: | $function = new ReflectionFunction($function); |
120: | } |
121: | |
122: | $params = $function->getParameters(); |
123: | if (!isset($params[$param])) { |
124: | throw new InvalidArgumentException(sprintf( |
125: | '$function has no parameter at position %d', |
126: | $param, |
127: | )); |
128: | } |
129: | |
130: | $types = self::normaliseType($params[$param]->getType()); |
131: | foreach ($types as $type) { |
132: | $intersection = []; |
133: | foreach (Arr::wrap($type) as $type) { |
134: | if ($skipBuiltins && $type->isBuiltin()) { |
135: | continue 2; |
136: | } |
137: | $intersection[] = $type->getName(); |
138: | } |
139: | $union[] = Arr::unwrap($intersection); |
140: | } |
141: | |
142: | |
143: | return $union ?? []; |
144: | } |
145: | |
146: | |
147: | |
148: | |
149: | |
150: | |
151: | |
152: | |
153: | |
154: | |
155: | |
156: | |
157: | |
158: | |
159: | |
160: | |
161: | |
162: | |
163: | |
164: | |
165: | |
166: | |
167: | |
168: | |
169: | |
170: | public static function normaliseType(?ReflectionType $type): array |
171: | { |
172: | if ($type === null) { |
173: | return []; |
174: | } |
175: | |
176: | return self::doNormaliseType($type); |
177: | } |
178: | |
179: | |
180: | |
181: | |
182: | |
183: | |
184: | public static function getTypes(?ReflectionType $type): array |
185: | { |
186: | return self::doGetTypes($type, false); |
187: | } |
188: | |
189: | |
190: | |
191: | |
192: | |
193: | |
194: | public static function getTypeNames(?ReflectionType $type): array |
195: | { |
196: | return self::doGetTypes($type, true); |
197: | } |
198: | |
199: | |
200: | |
201: | |
202: | private static function doGetTypes(?ReflectionType $type, bool $names): array |
203: | { |
204: | if ($type === null) { |
205: | return []; |
206: | } |
207: | |
208: | foreach (Arr::flatten(self::doNormaliseType($type)) as $type) { |
209: | |
210: | $name = $type->getName(); |
211: | if (isset($seen[$name])) { |
212: | continue; |
213: | } |
214: | $types[] = $names ? $name : $type; |
215: | $seen[$name] = true; |
216: | } |
217: | |
218: | return $types ?? []; |
219: | } |
220: | |
221: | |
222: | |
223: | |
224: | private static function doNormaliseType(ReflectionType $type): array |
225: | { |
226: | if ($type instanceof ReflectionUnionType) { |
227: | foreach ($type->getTypes() as $type) { |
228: | if ($type instanceof ReflectionIntersectionType) { |
229: | $types[] = $type->getTypes(); |
230: | continue; |
231: | } |
232: | $types[] = $type; |
233: | } |
234: | |
235: | return $types ?? []; |
236: | } |
237: | |
238: | if ($type instanceof ReflectionIntersectionType) { |
239: | $types = [$type->getTypes()]; |
240: | |
241: | return $types; |
242: | } |
243: | |
244: | |
245: | return self::expandNullableType($type); |
246: | } |
247: | |
248: | |
249: | |
250: | |
251: | |
252: | private static function expandNullableType(ReflectionType $type): array |
253: | { |
254: | if ($type->allowsNull() && ( |
255: | !$type->isBuiltin() |
256: | || strcasecmp($type->getName(), 'null') |
257: | )) { |
258: | return [ |
259: | new NamedType($type->getName(), $type->isBuiltin(), false), |
260: | new NamedType('null', true, true), |
261: | ]; |
262: | } |
263: | |
264: | return [$type]; |
265: | } |
266: | |
267: | |
268: | |
269: | |
270: | |
271: | |
272: | |
273: | public static function getConstants($class): array |
274: | { |
275: | return self::$Constants[self::getClassName($class)] |
276: | ??= self::doGetConstants($class); |
277: | } |
278: | |
279: | |
280: | |
281: | |
282: | |
283: | private static function doGetConstants($class): array |
284: | { |
285: | $class = self::getClass($class); |
286: | foreach ($class->getReflectionConstants() as $constant) { |
287: | if ($constant->isPublic()) { |
288: | $constants[$constant->getName()] = $constant->getValue(); |
289: | } |
290: | } |
291: | |
292: | return $constants ?? []; |
293: | } |
294: | |
295: | |
296: | |
297: | |
298: | |
299: | |
300: | |
301: | |
302: | |
303: | |
304: | public static function getConstantsByValue($class): array |
305: | { |
306: | return self::$ConstantsByValue[self::getClassName($class)] |
307: | ??= self::doGetConstantsByValue($class); |
308: | } |
309: | |
310: | |
311: | |
312: | |
313: | |
314: | private static function doGetConstantsByValue($class): array |
315: | { |
316: | foreach (self::getConstants($class) as $name => $value) { |
317: | if (!is_int($value) && !is_string($value)) { |
318: | continue; |
319: | } |
320: | if (!isset($constants[$value])) { |
321: | $constants[$value] = $name; |
322: | continue; |
323: | } |
324: | if (!is_array($constants[$value])) { |
325: | $constants[$value] = (array) $constants[$value]; |
326: | } |
327: | $constants[$value][] = $name; |
328: | } |
329: | |
330: | return $constants ?? []; |
331: | } |
332: | |
333: | |
334: | |
335: | |
336: | |
337: | |
338: | |
339: | public static function hasConstantWithValue($class, $value): bool |
340: | { |
341: | return in_array($value, self::getConstants($class), true); |
342: | } |
343: | |
344: | |
345: | |
346: | |
347: | |
348: | |
349: | |
350: | |
351: | |
352: | |
353: | public static function getConstantName($class, $value): string |
354: | { |
355: | foreach (self::getConstants($class) as $name => $_value) { |
356: | if ($_value === $value) { |
357: | $names[] = $name; |
358: | } |
359: | } |
360: | |
361: | if (!isset($names)) { |
362: | throw new InvalidArgumentException(sprintf( |
363: | 'Invalid value: %s', |
364: | Format::value($value), |
365: | )); |
366: | } |
367: | |
368: | if (count($names) > 1) { |
369: | throw new InvalidArgumentException(sprintf( |
370: | 'Value matches multiple constants: %s', |
371: | Format::value($value), |
372: | )); |
373: | } |
374: | |
375: | return $names[0]; |
376: | } |
377: | |
378: | |
379: | |
380: | |
381: | |
382: | |
383: | |
384: | private static function getClass($class): ReflectionClass |
385: | { |
386: | if ($class instanceof ReflectionClass) { |
387: | return $class; |
388: | } |
389: | return new ReflectionClass($class); |
390: | } |
391: | |
392: | |
393: | |
394: | |
395: | |
396: | |
397: | |
398: | private static function getClassName($class): string |
399: | { |
400: | if ($class instanceof ReflectionClass) { |
401: | return $class->getName(); |
402: | } |
403: | return $class; |
404: | } |
405: | } |
406: | |