La PHP Reflection API è una funzionalità integrata di PHP che permette di ottenere e ispezionare a runtime le meta-informazioni di classi, metodi, proprietà, funzioni e parametri. Puoi scoprire quali argomenti riceve un costruttore o quali attributi sono applicati a un metodo, senza modificare il codice sorgente.Laravel usa massicciamente la Reflection API dentro Illuminate/Container/Container.php per realizzare la risoluzione automatica del DI container, la lettura degli attributi PHP, la method injection e altro ancora.
$ref = new ReflectionClass(UserController::class);$ref->getName(); // nome fully qualified della classe$ref->getShortName(); // solo il nome della classe$ref->isInstantiable(); // se è istanziabile$ref->getConstructor(); // restituisce il costruttore come ReflectionMethod$ref->getMethods(); // restituisce tutti i metodi come ReflectionMethod[]$ref->getProperties(); // restituisce tutte le proprietà come ReflectionProperty[]$ref->getAttributes(); // restituisce gli attributi come ReflectionAttribute[]
ReflectionParameter — ispezionare gli argomenti del costruttore
$ref = new ReflectionClass(UserController::class);$constructor = $ref->getConstructor();if ($constructor) { foreach ($constructor->getParameters() as $param) { $param->getName(); // nome del parametro $param->getType(); // type hint (ReflectionType) $param->isOptional(); // se è opzionale $param->isVariadic(); // se è variadic $param->getDefaultValue(); // valore di default (se presente) }}
L’IoC container di Laravel realizza la constructor injection (risoluzione automatica delle dipendenze) usando la Reflection API. Vediamo il meccanismo alla base di app()->make(SomeClass::class) e della dependency injection.
Il metodo build() reale di Container.php è più o meno così.
// Illuminate\Container\Container::build() semplificatopublic function build($concrete){ // 1. Ispeziona la classe con ReflectionClass $reflector = new ReflectionClass($concrete); // Le classi non istanziabili (interface, abstract, ecc.) sollevano errore if (! $reflector->isInstantiable()) { throw new BindingResolutionException("[$concrete] is not instantiable."); } // 2. Ottiene il costruttore $constructor = $reflector->getConstructor(); // Nessun costruttore → istanzia direttamente if (is_null($constructor)) { return new $concrete; } // 3. Ottiene tutti i parametri del costruttore $dependencies = $constructor->getParameters(); // 4. Risolve ricorsivamente ciascun parametro $instances = $this->resolveDependencies($dependencies); return new $concrete(...$instances);}protected function resolveDependencies(array $dependencies): array{ $results = []; foreach ($dependencies as $dependency) { // Se c'è un type hint, risolve ricorsivamente col container $className = Util::getParameterClassName($dependency); $results[] = is_null($className) ? $this->resolvePrimitive($dependency) // tipo primitivo : $this->resolveClass($dependency, $className); // tipo classe } return $results;}
Util::getParameterClassName() è una utility che estrae la stringa del tipo dal risultato di $parameter->getType(). Fa da wrapper conveniente al ReflectionNamedType restituito da ReflectionParameter::getType().
Da PHP 8.0, tramite la Reflection API puoi ottenere gli attributi applicati a classi, metodi e proprietà. Laravel usa questo meccanismo per gestire gli attributi delle code e di Eloquent.
Per gli attributi PHP e la loro integrazione in Laravel, consulta anche Attributi PHP.
use ReflectionClass;// 1. ottieni gli attributi applicati alla classe$ref = new ReflectionClass(ProcessOrder::class);$attrs = $ref->getAttributes(Queue::class); // ottieni solo un attributo specificoforeach ($attrs as $attr) { $instance = $attr->newInstance(); // istanzia la classe attributo echo $instance->queue; // leggi le proprietà dell'attributo}// 2. ottieni tutti gli attributi (senza filtro)$allAttrs = $ref->getAttributes();foreach ($allAttrs as $attr) { echo $attr->getName(); // nome classe attributo (FQCN) print_r($attr->getArguments()); // argomenti del costruttore}
Ottenere metadati — registrazione automatica delle rotte via attributi
Pattern per raccogliere e registrare rotte in modo automatico combinando attributi e Reflection.
// definizione dell'attributo Route custom#[\Attribute(\Attribute::TARGET_METHOD)]class Route{ public function __construct( public string $method, public string $path, ) {}}// uso nel controllerclass UserController{ #[Route('GET', '/users')] public function index() { /* ... */ } #[Route('POST', '/users')] public function store() { /* ... */ }}// service provider che registra automaticamente le rotte dagli attributiclass AttributeRouteServiceProvider extends ServiceProvider{ public function boot(Router $router): void { $ref = new ReflectionClass(UserController::class); foreach ($ref->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { foreach ($method->getAttributes(Route::class) as $attr) { $route = $attr->newInstance(); $router->addRoute( $route->method, $route->path, [UserController::class, $method->getName()], ); } } }}
Il metodo call() di Laravel risolve gli argomenti automaticamente con la Reflection. Esempio di come implementare lo stesso meccanismo in un pacchetto.
use ReflectionFunction;use ReflectionMethod;function callWithDependencies(callable $callable, Container $container): mixed{ if (is_array($callable)) { [$object, $method] = $callable; $ref = new ReflectionMethod($object, $method); $params = $ref->getParameters(); } else { $ref = new ReflectionFunction($callable); $params = $ref->getParameters(); } $args = []; foreach ($params as $param) { $type = $param->getType()?->getName(); $args[] = $type ? $container->make($type) : null; } return $callable(...$args);}// esempio d'usocallWithDependencies([new UserController(), 'index'], app());
La Reflection API riparsa ogni volta le info di classe, quindi ha un costo. Nella produzione è best practice mettere in cache i risultati.
class ReflectionCache{ private static array $cache = []; public static function getClass(string $class): \ReflectionClass { return self::$cache[$class] ??= new \ReflectionClass($class); }}// esempio d'uso$ref = ReflectionCache::getClass(UserController::class);
L’OPcache di PHP non memorizza in cache i risultati della Reflection. Se ispezioni molte classi in loop, valuta una cache tua. Nota però che il container di Laravel già riutilizza le istanze di ReflectionClass all’interno della stessa richiesta.