|
| 1 | +<?php |
| 2 | + |
| 3 | +namespace Bermuda\Reflection; |
| 4 | + |
| 5 | +use Closure; |
| 6 | +use Generator; |
| 7 | +use ReflectionClass; |
| 8 | +use ReflectionConstant; |
| 9 | +use ReflectionFunction; |
| 10 | +use ReflectionFunctionAbstract; |
| 11 | +use ReflectionMethod; |
| 12 | +use ReflectionObject; |
| 13 | +use ReflectionParameter; |
| 14 | +use ReflectionProperty; |
| 15 | +use Reflector; |
| 16 | + |
| 17 | +/** |
| 18 | + * Class Reflection |
| 19 | + * |
| 20 | + * This static helper class provides utility methods for retrieving reflection information and |
| 21 | + * metadata (attributes) from various reflection objects such as functions, classes, parameters, |
| 22 | + * constants, and properties. It also implements caching of created reflectors to improve performance. |
| 23 | + */ |
| 24 | +final class Reflection |
| 25 | +{ |
| 26 | + /** |
| 27 | + * @var array<string, Reflector> Cache mapping of unique identifiers to Reflector instances. |
| 28 | + */ |
| 29 | + private static array $reflectors = []; |
| 30 | + |
| 31 | + /** |
| 32 | + * Adds a custom reflector to the static cache. |
| 33 | + * |
| 34 | + * @param string $id The unique identifier for caching the reflector. |
| 35 | + * @param Reflector $reflector The reflector instance to cache. |
| 36 | + */ |
| 37 | + public static function addReflector(string $id, Reflector $reflector): void |
| 38 | + { |
| 39 | + self::$reflectors[$id] = $reflector; |
| 40 | + } |
| 41 | + |
| 42 | + /** |
| 43 | + * Retrieves all metadata attributes for the provided reflection object. |
| 44 | + * |
| 45 | + * If a name is supplied, only attributes matching the given class name are returned. |
| 46 | + * The metadata is yielded as a generator producing Attribute instances. |
| 47 | + * |
| 48 | + * @template T of object |
| 49 | + * @param ReflectionFunctionAbstract|ReflectionClass|ReflectionParameter|ReflectionConstant $reflector The reflection object to inspect. |
| 50 | + * @param class-string<T>|null $name Optional Attribute class name to filter by. |
| 51 | + * @return Generator<T>|null Yields instances of the Attribute, or null if none are found. |
| 52 | + */ |
| 53 | + public static function getMetadata(ReflectionFunctionAbstract|ReflectionClass|ReflectionParameter|ReflectionConstant $reflector, ?string $name = null): ?Generator |
| 54 | + { |
| 55 | + if (empty($attributes = $reflector?->getAttributes($name) ?? [])) return null; |
| 56 | + foreach ($attributes as $attribute) yield $attribute->newInstance(); |
| 57 | + } |
| 58 | + |
| 59 | + /** |
| 60 | + * Retrieves the first metadata Attribute instance for the provided reflection object that matches the given Attribute name. |
| 61 | + * |
| 62 | + * This method supports ReflectionFunctionAbstract, ReflectionClass, ReflectionParameter, |
| 63 | + * ReflectionConstant, and ReflectionProperty. |
| 64 | + * |
| 65 | + * @template T of object |
| 66 | + * @param ReflectionFunctionAbstract|ReflectionClass|ReflectionParameter|ReflectionConstant|ReflectionProperty $reflector The reflection object to inspect. |
| 67 | + * @param class-string<T> $name The Attribute class name to look for. |
| 68 | + * @return T|null Returns the Attribute instance if found, or null if not present. |
| 69 | + */ |
| 70 | + public static function getFirstMetadata(ReflectionFunctionAbstract|ReflectionClass|ReflectionParameter|ReflectionConstant|ReflectionProperty $reflector, string $name): ?object |
| 71 | + { |
| 72 | + $attributes = $reflector?->getAttributes($name) ?? []; |
| 73 | + return isset($attributes[0]) ? $attributes[0]->newInstance() : null; |
| 74 | + } |
| 75 | + |
| 76 | + /** |
| 77 | + * Checks whether the provided reflection object has any metadata attributes matching the specified Attribute name. |
| 78 | + * |
| 79 | + * This method supports ReflectionFunctionAbstract, ReflectionClass, ReflectionParameter, |
| 80 | + * ReflectionConstant, and ReflectionProperty. |
| 81 | + * |
| 82 | + * @param ReflectionFunctionAbstract|ReflectionClass|ReflectionParameter|ReflectionConstant|ReflectionProperty $reflector The reflection object to check. |
| 83 | + * @param string $name The Attribute class name to look for. |
| 84 | + * @return bool Returns true if at least one matching Attribute is found; otherwise, false. |
| 85 | + */ |
| 86 | + public static function hasMetadata(ReflectionFunctionAbstract|ReflectionClass|ReflectionParameter|ReflectionConstant|ReflectionProperty $reflector, string $name): bool |
| 87 | + { |
| 88 | + return !empty($reflector->getAttributes($name)); |
| 89 | + } |
| 90 | + |
| 91 | + /** |
| 92 | + * Reflects a given variable and returns an appropriate reflection object. |
| 93 | + * |
| 94 | + * Supported types: |
| 95 | + * - Callables: Returns a reflection of the callable. |
| 96 | + * - Objects: Returns a ReflectionObject for the instance. |
| 97 | + * - Strings: Treated as a class name; returns a ReflectionClass if the class exists. |
| 98 | + * |
| 99 | + * @param mixed $var The variable to reflect. |
| 100 | + * @return null|ReflectionFunctionAbstract|ReflectionClass|ReflectionObject Returns a reflection object if supported, or null. |
| 101 | + */ |
| 102 | + public static function reflect(mixed $var): null|ReflectionFunctionAbstract|ReflectionClass|ReflectionObject |
| 103 | + { |
| 104 | + return match (true) { |
| 105 | + is_callable($var) => self::callable($var), |
| 106 | + is_object($var) => self::object($var), |
| 107 | + is_string($var) => self::class($var), |
| 108 | + default => null |
| 109 | + }; |
| 110 | + } |
| 111 | + |
| 112 | + /** |
| 113 | + * Returns a Reflection instance for the given callable. |
| 114 | + * |
| 115 | + * Depending on the type of callable: |
| 116 | + * - For closures, uses spl_object_hash() for caching. |
| 117 | + * - For string callables with "::" (e.g., "Class::method"), returns a ReflectionMethod. |
| 118 | + * - For simple function names, returns a ReflectionFunction. |
| 119 | + * - For array-style callables, returns a ReflectionMethod. |
| 120 | + * |
| 121 | + * @param callable $callable The callable to reflect. |
| 122 | + * @return ReflectionFunctionAbstract Returns the reflection instance corresponding to the callable. |
| 123 | + */ |
| 124 | + public static function callable(callable $callable): ReflectionFunctionAbstract |
| 125 | + { |
| 126 | + if ($callable instanceof Closure) { |
| 127 | + if (isset(self::$reflectors[$cacheKey = spl_object_hash($callable)])) { |
| 128 | + return self::$reflectors[$cacheKey]; |
| 129 | + } |
| 130 | + |
| 131 | + return self::$reflectors[$cacheKey] = new ReflectionFunction($callable); |
| 132 | + } |
| 133 | + |
| 134 | + if (is_string($callable)) { |
| 135 | + if (isset(self::$reflectors[$callable])) return self::$reflectors[$callable]; |
| 136 | + if (str_contains($callable, '::')) { |
| 137 | + [$class, $method] = explode('::', $callable, 2); |
| 138 | + return self::$reflectors[$callable] = new ReflectionMethod($class, $method); |
| 139 | + } else return self::$reflectors[$callable] = new ReflectionFunction($callable); |
| 140 | + } |
| 141 | + |
| 142 | + if (is_array($callable)) { |
| 143 | + list ($objectOrClass, $method) = $callable; |
| 144 | + if (is_object($objectOrClass)) $objectOrClass = $objectOrClass::class; |
| 145 | + return self::$reflectors["$objectOrClass::$method"] = new ReflectionMethod($objectOrClass, $method); |
| 146 | + } |
| 147 | + |
| 148 | + $cacheKey = spl_object_hash($callable); |
| 149 | + if (isset(self::$reflectors[$cacheKey])) return self::$reflectors[$cacheKey]; |
| 150 | + return self::$reflectors[$callable::class] = new ReflectionMethod($callable, '__invoke'); |
| 151 | + } |
| 152 | + |
| 153 | + /** |
| 154 | + * Returns a ReflectionObject for the given object instance. |
| 155 | + * |
| 156 | + * The created ReflectionObject is cached based on the object’s spl_object_hash. |
| 157 | + * |
| 158 | + * @param object $object The object to reflect. |
| 159 | + * @return ReflectionObject Returns the ReflectionObject representing the given object. |
| 160 | + */ |
| 161 | + public static function object(object $object): ReflectionObject |
| 162 | + { |
| 163 | + return self::$reflectors[$id = spl_object_hash($object)] ?? self::$reflectors[$id] = new ReflectionObject($object); |
| 164 | + } |
| 165 | + |
| 166 | + /** |
| 167 | + * Returns a ReflectionClass for the given class name. |
| 168 | + * |
| 169 | + * If the class exists, its reflection is cached and returned. If the class does not exist, null is returned. |
| 170 | + * |
| 171 | + * @param string $class The class name to reflect. |
| 172 | + * @return null|ReflectionClass Returns the ReflectionClass instance if the class exists; otherwise, null. |
| 173 | + */ |
| 174 | + public static function class(string $class): ?ReflectionClass |
| 175 | + { |
| 176 | + if (isset(self::$reflectors[$class])) return self::$reflectors[$class]; |
| 177 | + if (class_exists($class)) return self::$reflectors[$class] = new ReflectionClass($class); |
| 178 | + |
| 179 | + return null; |
| 180 | + } |
| 181 | +} |
0 commit comments