Skip to content

Commit 209a524

Browse files
authored
Create Reflection.php
1 parent bfdccab commit 209a524

File tree

1 file changed

+181
-0
lines changed

1 file changed

+181
-0
lines changed

src/Reflection.php

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
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

Comments
 (0)