The PHP Reflection API is a built-in PHP feature that lets you inspect and retrieve metadata about classes, methods, properties, functions, and parameters at runtime. You can discover what arguments a constructor expects, which attributes are attached to a method, and much more — all without modifying the source code.Laravel relies heavily on the Reflection API inside Illuminate/Container/Container.php to power automatic dependency resolution, PHP attribute reading, and method injection.
$ref = new ReflectionClass(UserController::class);$ref->getName(); // Fully-qualified class name$ref->getShortName(); // Class name only$ref->isInstantiable(); // Whether the class can be instantiated$ref->getConstructor(); // Returns the constructor as a ReflectionMethod$ref->getMethods(); // Returns all methods as ReflectionMethod[]$ref->getProperties(); // Returns all properties as ReflectionProperty[]$ref->getAttributes(); // Returns attributes on the class as ReflectionAttribute[]
$ref = new ReflectionClass(UserController::class);$constructor = $ref->getConstructor();if ($constructor) { foreach ($constructor->getParameters() as $param) { $param->getName(); // Parameter name $param->getType(); // Type hint (ReflectionType) $param->isOptional(); // Whether the parameter is optional $param->isVariadic(); // Whether the parameter is variadic $param->getDefaultValue(); // Default value (if present) }}
Laravel’s IoC container uses the Reflection API to implement constructor injection — the automatic resolution of dependencies. Here is how app()->make(SomeClass::class) and dependency injection work under the hood.
The actual build() method in Container.php looks approximately like this.
// Simplified version of Illuminate\Container\Container::build()public function build($concrete){ // 1. Inspect the class with ReflectionClass $reflector = new ReflectionClass($concrete); // Non-instantiable classes (interfaces, abstract classes) throw an error if (! $reflector->isInstantiable()) { throw new BindingResolutionException("[$concrete] is not instantiable."); } // 2. Get the constructor $constructor = $reflector->getConstructor(); // No constructor means no dependencies — instantiate directly if (is_null($constructor)) { return new $concrete; } // 3. Fetch all constructor parameters $dependencies = $constructor->getParameters(); // 4. Resolve each parameter recursively $instances = $this->resolveDependencies($dependencies); return new $concrete(...$instances);}protected function resolveDependencies(array $dependencies): array{ $results = []; foreach ($dependencies as $dependency) { // Resolve class-typed parameters via the container $className = Util::getParameterClassName($dependency); $results[] = is_null($className) ? $this->resolvePrimitive($dependency) // Primitive types : $this->resolveClass($dependency, $className); // Class types } return $results;}
Util::getParameterClassName() extracts the type name string from the result of $parameter->getType(). It wraps the ReflectionNamedType returned by ReflectionParameter::getType() into a convenient helper.
Since PHP 8.0, the Reflection API lets you retrieve attributes attached to classes, methods, and properties. Laravel uses this mechanism to process queue attributes and Eloquent attributes.
See PHP Attributes for a detailed explanation of PHP attributes and how Laravel integrates them.
use ReflectionClass;// 1. Get attributes on a class$ref = new ReflectionClass(ProcessOrder::class);$attrs = $ref->getAttributes(Queue::class); // Fetch only a specific attributeforeach ($attrs as $attr) { $instance = $attr->newInstance(); // Instantiate the attribute class echo $instance->queue; // Read the attribute's properties}// 2. Get all attributes (no filter)$allAttrs = $ref->getAttributes();foreach ($allAttrs as $attr) { echo $attr->getName(); // Attribute class name (FQCN) print_r($attr->getArguments()); // Constructor arguments}
A pattern that combines attributes with Reflection to collect routes automatically.
// Custom Route attribute definition#[\Attribute(\Attribute::TARGET_METHOD)]class Route{ public function __construct( public string $method, public string $path, ) {}}// Used in a controllerclass UserController{ #[Route('GET', '/users')] public function index() { /* ... */ } #[Route('POST', '/users')] public function store() { /* ... */ }}// Service provider that auto-registers routes from attributesclass 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()], ); } } }}
The Reflection API parses class metadata every time it runs, so it carries a cost. Caching results is a best practice for production code.
class ReflectionCache{ private static array $cache = []; public static function getClass(string $class): \ReflectionClass { return self::$cache[$class] ??= new \ReflectionClass($class); }}// Usage$ref = ReflectionCache::getClass(UserController::class);
PHP’s OPcache does not cache Reflection results. Consider building your own cache when inspecting a large number of classes in a loop. Note that Laravel’s container itself reuses ReflectionClass instances within the same request.