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: | public static function getPrototype(ReflectionMethod $method): ?ReflectionMethod |
68: | { |
69: | try { |
70: | return $method->getPrototype(); |
71: | } catch (ReflectionException $ex) { |
72: | if (\PHP_VERSION_ID >= 80200 || $method->isPrivate()) { |
73: | return null; |
74: | } |
75: | |
76: | |
77: | $class = $method->getDeclaringClass(); |
78: | $name = $method->getName(); |
79: | if ($method->isPublic()) { |
80: | foreach ($class->getInterfaces() as $interface) { |
81: | if ($interface->hasMethod($name)) { |
82: | return $interface->getMethod($name); |
83: | } |
84: | } |
85: | } |
86: | $class = $class->getParentClass(); |
87: | if ($class && $class->hasMethod($name)) { |
88: | return $class->getMethod($name); |
89: | } |
90: | return null; |
91: | } |
92: | } |
93: | |
94: | |
95: | |
96: | |
97: | |
98: | |
99: | |
100: | public static function getPrototypeClass(ReflectionMethod $method): ReflectionClass |
101: | { |
102: | return (self::getPrototype($method) ?? $method)->getDeclaringClass(); |
103: | } |
104: | |
105: | |
106: | |
107: | |
108: | |
109: | |
110: | public static function getTraitMethod( |
111: | ReflectionClass $class, |
112: | string $methodName |
113: | ): ?ReflectionMethod { |
114: | if ($inserted = self::getTraitAliases($class)[$methodName] ?? null) { |
115: | return new ReflectionMethod(...$inserted); |
116: | } |
117: | |
118: | foreach ($class->getTraits() as $trait) { |
119: | if ($trait->hasMethod($methodName)) { |
120: | return $trait->getMethod($methodName); |
121: | } |
122: | } |
123: | |
124: | return null; |
125: | } |
126: | |
127: | |
128: | |
129: | |
130: | |
131: | |
132: | |
133: | |
134: | public static function getTraitAliases(ReflectionClass $class): array |
135: | { |
136: | foreach ($class->getTraitAliases() as $alias => $original) { |
137: | |
138: | $original = explode('::', $original, 2); |
139: | $aliases[$alias] = $original; |
140: | } |
141: | |
142: | return $aliases ?? []; |
143: | } |
144: | |
145: | |
146: | |
147: | |
148: | |
149: | |
150: | public static function getTraitProperty( |
151: | ReflectionClass $class, |
152: | string $propertyName |
153: | ): ?ReflectionProperty { |
154: | foreach ($class->getTraits() as $trait) { |
155: | if ($trait->hasProperty($propertyName)) { |
156: | return $trait->getProperty($propertyName); |
157: | } |
158: | } |
159: | |
160: | return null; |
161: | } |
162: | |
163: | |
164: | |
165: | |
166: | |
167: | |
168: | public static function getTraitConstant( |
169: | ReflectionClass $class, |
170: | string $constantName |
171: | ): ?ReflectionClassConstant { |
172: | if (\PHP_VERSION_ID < 80200) { |
173: | return null; |
174: | } |
175: | |
176: | foreach ($class->getTraits() as $trait) { |
177: | if ( |
178: | $trait->hasConstant($constantName) |
179: | && ($constant = $trait->getReflectionConstant($constantName)) |
180: | ) { |
181: | return $constant; |
182: | } |
183: | } |
184: | |
185: | return null; |
186: | } |
187: | |
188: | |
189: | |
190: | |
191: | |
192: | |
193: | |
194: | public static function getAllProperties(ReflectionClass $class): array |
195: | { |
196: | do { |
197: | foreach ($class->getProperties() as $property) { |
198: | $name = $property->getName(); |
199: | if (isset($seen[$name])) { |
200: | continue; |
201: | } |
202: | $properties[] = $property; |
203: | $seen[$name] = true; |
204: | } |
205: | } while ($class = $class->getParentClass()); |
206: | |
207: | return $properties ?? []; |
208: | } |
209: | |
210: | |
211: | |
212: | |
213: | |
214: | |
215: | |
216: | |
217: | |
218: | |
219: | public static function getAcceptedTypes( |
220: | $function, |
221: | bool $skipBuiltins = false, |
222: | int $param = 0 |
223: | ): array { |
224: | if (!$function instanceof ReflectionFunctionAbstract) { |
225: | if (!$function instanceof Closure) { |
226: | $function = Closure::fromCallable($function); |
227: | } |
228: | $function = new ReflectionFunction($function); |
229: | } |
230: | |
231: | $params = $function->getParameters(); |
232: | if (!isset($params[$param])) { |
233: | throw new InvalidArgumentException(sprintf( |
234: | '$function has no parameter at position %d', |
235: | $param, |
236: | )); |
237: | } |
238: | |
239: | $types = self::normaliseType($params[$param]->getType()); |
240: | foreach ($types as $type) { |
241: | $intersection = []; |
242: | foreach (Arr::wrap($type) as $type) { |
243: | if ($skipBuiltins && $type->isBuiltin()) { |
244: | continue 2; |
245: | } |
246: | $intersection[] = $type->getName(); |
247: | } |
248: | $union[] = Arr::unwrap($intersection); |
249: | } |
250: | |
251: | |
252: | return $union ?? []; |
253: | } |
254: | |
255: | |
256: | |
257: | |
258: | |
259: | |
260: | |
261: | |
262: | |
263: | |
264: | |
265: | |
266: | |
267: | |
268: | |
269: | |
270: | |
271: | |
272: | |
273: | |
274: | |
275: | |
276: | |
277: | |
278: | |
279: | public static function normaliseType(?ReflectionType $type): array |
280: | { |
281: | if ($type === null) { |
282: | return []; |
283: | } |
284: | |
285: | return self::doNormaliseType($type); |
286: | } |
287: | |
288: | |
289: | |
290: | |
291: | |
292: | |
293: | public static function getTypes(?ReflectionType $type): array |
294: | { |
295: | return self::doGetTypes($type, false); |
296: | } |
297: | |
298: | |
299: | |
300: | |
301: | |
302: | |
303: | public static function getTypeNames(?ReflectionType $type): array |
304: | { |
305: | return self::doGetTypes($type, true); |
306: | } |
307: | |
308: | |
309: | |
310: | |
311: | private static function doGetTypes(?ReflectionType $type, bool $names): array |
312: | { |
313: | if ($type === null) { |
314: | return []; |
315: | } |
316: | |
317: | foreach (Arr::flatten(self::doNormaliseType($type)) as $type) { |
318: | |
319: | $name = $type->getName(); |
320: | if (isset($seen[$name])) { |
321: | continue; |
322: | } |
323: | $types[] = $names ? $name : $type; |
324: | $seen[$name] = true; |
325: | } |
326: | |
327: | return $types ?? []; |
328: | } |
329: | |
330: | |
331: | |
332: | |
333: | private static function doNormaliseType(ReflectionType $type): array |
334: | { |
335: | if ($type instanceof ReflectionUnionType) { |
336: | foreach ($type->getTypes() as $type) { |
337: | if ($type instanceof ReflectionIntersectionType) { |
338: | $types[] = $type->getTypes(); |
339: | continue; |
340: | } |
341: | $types[] = $type; |
342: | } |
343: | |
344: | return $types ?? []; |
345: | } |
346: | |
347: | if ($type instanceof ReflectionIntersectionType) { |
348: | $types = [$type->getTypes()]; |
349: | |
350: | return $types; |
351: | } |
352: | |
353: | |
354: | return self::expandNullableType($type); |
355: | } |
356: | |
357: | |
358: | |
359: | |
360: | |
361: | private static function expandNullableType(ReflectionType $type): array |
362: | { |
363: | if ($type->allowsNull() && ( |
364: | !$type->isBuiltin() |
365: | || strcasecmp($type->getName(), 'null') |
366: | )) { |
367: | return [ |
368: | new NamedType($type->getName(), $type->isBuiltin(), false), |
369: | new NamedType('null', true, true), |
370: | ]; |
371: | } |
372: | |
373: | return [$type]; |
374: | } |
375: | |
376: | |
377: | |
378: | |
379: | |
380: | |
381: | |
382: | public static function getConstants($class): array |
383: | { |
384: | return self::$Constants[self::getClassName($class)] |
385: | ??= self::doGetConstants($class); |
386: | } |
387: | |
388: | |
389: | |
390: | |
391: | |
392: | private static function doGetConstants($class): array |
393: | { |
394: | $class = self::getClass($class); |
395: | foreach ($class->getReflectionConstants() as $constant) { |
396: | if ($constant->isPublic()) { |
397: | $constants[$constant->getName()] = $constant->getValue(); |
398: | } |
399: | } |
400: | |
401: | return $constants ?? []; |
402: | } |
403: | |
404: | |
405: | |
406: | |
407: | |
408: | |
409: | |
410: | |
411: | |
412: | |
413: | public static function getConstantsByValue($class): array |
414: | { |
415: | return self::$ConstantsByValue[self::getClassName($class)] |
416: | ??= self::doGetConstantsByValue($class); |
417: | } |
418: | |
419: | |
420: | |
421: | |
422: | |
423: | private static function doGetConstantsByValue($class): array |
424: | { |
425: | foreach (self::getConstants($class) as $name => $value) { |
426: | if (!is_int($value) && !is_string($value)) { |
427: | continue; |
428: | } |
429: | if (!isset($constants[$value])) { |
430: | $constants[$value] = $name; |
431: | continue; |
432: | } |
433: | if (!is_array($constants[$value])) { |
434: | $constants[$value] = (array) $constants[$value]; |
435: | } |
436: | $constants[$value][] = $name; |
437: | } |
438: | |
439: | return $constants ?? []; |
440: | } |
441: | |
442: | |
443: | |
444: | |
445: | |
446: | |
447: | |
448: | public static function hasConstantWithValue($class, $value): bool |
449: | { |
450: | return in_array($value, self::getConstants($class), true); |
451: | } |
452: | |
453: | |
454: | |
455: | |
456: | |
457: | |
458: | |
459: | |
460: | |
461: | |
462: | public static function getConstantName($class, $value): string |
463: | { |
464: | foreach (self::getConstants($class) as $name => $_value) { |
465: | if ($_value === $value) { |
466: | $names[] = $name; |
467: | } |
468: | } |
469: | |
470: | if (!isset($names)) { |
471: | throw new InvalidArgumentException(sprintf( |
472: | 'Invalid value: %s', |
473: | Format::value($value), |
474: | )); |
475: | } |
476: | |
477: | if (count($names) > 1) { |
478: | throw new InvalidArgumentException(sprintf( |
479: | 'Value matches multiple constants: %s', |
480: | Format::value($value), |
481: | )); |
482: | } |
483: | |
484: | return $names[0]; |
485: | } |
486: | |
487: | |
488: | |
489: | |
490: | |
491: | |
492: | |
493: | |
494: | |
495: | public static function getConstantValue($class, string $name, bool $ignoreCase = false) |
496: | { |
497: | $constants = self::getConstants($class); |
498: | if (array_key_exists($name, $constants)) { |
499: | return $constants[$name]; |
500: | } |
501: | |
502: | if ($ignoreCase) { |
503: | $constants = array_change_key_case($constants, \CASE_UPPER); |
504: | if (array_key_exists($upper = Str::upper($name), $constants)) { |
505: | return $constants[$upper]; |
506: | } |
507: | } |
508: | |
509: | throw new InvalidArgumentException(sprintf('Invalid name: %s', $name)); |
510: | } |
511: | |
512: | |
513: | |
514: | |
515: | |
516: | |
517: | |
518: | private static function getClass($class): ReflectionClass |
519: | { |
520: | if ($class instanceof ReflectionClass) { |
521: | return $class; |
522: | } |
523: | return new ReflectionClass($class); |
524: | } |
525: | |
526: | |
527: | |
528: | |
529: | |
530: | |
531: | |
532: | private static function getClassName($class): string |
533: | { |
534: | if ($class instanceof ReflectionClass) { |
535: | return $class->getName(); |
536: | } |
537: | return $class; |
538: | } |
539: | } |
540: | |