L’API PHP Reflection est une fonctionnalité intégrée à PHP permettant d’obtenir et d’inspecter à l’exécution les méta-informations des classes, méthodes, propriétés, fonctions ou paramètres. Vous pouvez ainsi vérifier les arguments du constructeur d’une classe, ou les attributs posés sur une méthode, sans modifier directement le code source.Laravel utilise abondamment l’API Reflection à l’intérieur de Illuminate/Container/Container.php pour la résolution automatique du conteneur DI, la lecture des attributs PHP ou l’injection dans les méthodes.
ReflectionClass — obtenir des informations sur une classe
$ref = new ReflectionClass(UserController::class);$ref->getName(); // Nom complètement qualifié de la classe$ref->getShortName(); // Nom court de la classe$ref->isInstantiable(); // Peut-elle être instanciée ?$ref->getConstructor(); // Retourne le constructeur en tant que ReflectionMethod$ref->getMethods(); // Retourne toutes les méthodes en ReflectionMethod[]$ref->getProperties(); // Retourne toutes les propriétés en ReflectionProperty[]$ref->getAttributes(); // Retourne les attributs de la classe en ReflectionAttribute[]
ReflectionParameter — inspecter les arguments d’un constructeur
$ref = new ReflectionClass(UserController::class);$constructor = $ref->getConstructor();if ($constructor) { foreach ($constructor->getParameters() as $param) { $param->getName(); // Nom de l'argument $param->getType(); // Hint de type (ReflectionType) $param->isOptional(); // Optionnel ? $param->isVariadic(); // Variadique ? $param->getDefaultValue(); // Valeur par défaut (si présente) }}
Le conteneur IoC de Laravel s’appuie sur l’API Reflection pour l’injection par constructeur (résolution automatique des dépendances). Comprenons le mécanisme derrière app()->make(SomeClass::class) et l’injection de dépendances.
La méthode build() du conteneur (version simplifiée)
Le vrai build() de Container.php ressemble à peu près à ceci.
// Simplification de Illuminate\Container\Container::build()public function build($concrete){ // 1. Inspection de la classe via ReflectionClass $reflector = new ReflectionClass($concrete); // Les classes non instanciables (interface, abstract, etc.) provoquent une erreur if (! $reflector->isInstantiable()) { throw new BindingResolutionException("[$concrete] is not instantiable."); } // 2. Récupérer le constructeur $constructor = $reflector->getConstructor(); // Pas de constructeur → instancier tel quel if (is_null($constructor)) { return new $concrete; } // 3. Récupérer tous les paramètres du constructeur $dependencies = $constructor->getParameters(); // 4. Résoudre chaque paramètre de manière récursive $instances = $this->resolveDependencies($dependencies); return new $concrete(...$instances);}protected function resolveDependencies(array $dependencies): array{ $results = []; foreach ($dependencies as $dependency) { // Si un hint de type est disponible, résolution récursive via le conteneur $className = Util::getParameterClassName($dependency); $results[] = is_null($className) ? $this->resolvePrimitive($dependency) // type primitif : $this->resolveClass($dependency, $className); // type classe } return $results;}
Util::getParameterClassName() est un utilitaire qui extrait le nom du type sous forme de chaîne à partir du résultat de $parameter->getType(). Il enveloppe le ReflectionNamedType renvoyé par ReflectionParameter::getType() de manière plus commode.
Depuis PHP 8.0, l’API Reflection permet de récupérer les attributs posés sur des classes, méthodes ou propriétés. Laravel s’en sert pour traiter les attributs de queue ou d’Eloquent.
Pour l’intégration des attributs PHP dans Laravel, voir aussi Attributs PHP.
use ReflectionClass;// 1. Récupérer les attributs posés sur la classe$ref = new ReflectionClass(ProcessOrder::class);$attrs = $ref->getAttributes(Queue::class); // Ne récupère que l'attribut donnéforeach ($attrs as $attr) { $instance = $attr->newInstance(); // Instancie la classe d'attribut echo $instance->queue; // Lit une propriété de l'attribut}// 2. Récupérer tous les attributs (sans filtre)$allAttrs = $ref->getAttributes();foreach ($allAttrs as $attr) { echo $attr->getName(); // Nom (FQCN) de la classe d'attribut print_r($attr->getArguments()); // Arguments du constructeur}
Récupération de métadonnées — enregistrer automatiquement des routes via attributs
Un schéma combinant attributs et Reflection pour collecter automatiquement les routes.
// Définition d'un attribut Route maison#[\Attribute(\Attribute::TARGET_METHOD)]class Route{ public function __construct( public string $method, public string $path, ) {}}// Utilisation dans un contrôleurclass UserController{ #[Route('GET', '/users')] public function index() { /* ... */ } #[Route('POST', '/users')] public function store() { /* ... */ }}// Service provider qui enregistre les routes à partir des attributsclass 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()], ); } } }}
La méthode call() de Laravel utilise Reflection pour résoudre automatiquement les arguments. Voici comment implémenter le même mécanisme dans un package.
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);}// Exemple d'utilisationcallWithDependencies([new UserController(), 'index'], app());
L’API Reflection parse les informations de classe à chaque appel : cela a un coût. En production, la bonne pratique est de mettre les résultats en cache.
class ReflectionCache{ private static array $cache = []; public static function getClass(string $class): \ReflectionClass { return self::$cache[$class] ??= new \ReflectionClass($class); }}// Exemple d'utilisation$ref = ReflectionCache::getClass(UserController::class);
L’OPcache de PHP ne met pas en cache les résultats de Reflection. Si vous inspectez de nombreuses classes en boucle, envisagez un cache personnalisé. Notez toutefois que le conteneur Laravel réutilise déjà l’instance ReflectionClass dans la même requête.