0

Reflection — inspecting code at runtime without reading source files

Intermediate5 min read·eng-12-015
interviewlaravel-src

Concept

Reflection — the ability of code to inspect and manipulate itself at runtime. PHP's Reflection API lets you examine classes, methods, properties, parameters, and attributes without reading source code.

What you can inspect:

  • Class name, parent class, interfaces implemented, traits used.
  • Methods: name, visibility, parameters, return type, docblock.
  • Properties: name, visibility, default value, type.
  • Parameters: name, type, default value, whether optional.
  • Attributes (PHP 8.0+): structured metadata attached to code.

Key Reflection classes:

  • ReflectionClass($className): Inspect a class.
  • ReflectionMethod($class, $method): Inspect a method.
  • ReflectionProperty($class, $property): Inspect a property.
  • ReflectionParameter: From ReflectionMethod::getParameters().
  • ReflectionFunction($callable): Inspect a function or closure.

PHP 8.0 Attributes: Metadata attached to classes, methods, properties using #[AttributeName]. Reflection is how you READ these attributes at runtime. Frameworks use Attributes for routing (#[Route('/users')]), validation, serialization.

Performance: Reflection is significantly slower than direct calls. Cache reflected data in production. PHPStan, Psalm, and IDE tooling use reflection at build-time, not runtime.

Common uses in PHP:

  • Service containers: Inspect constructor parameters to resolve dependencies (auto-wiring).
  • ORM: Read property types and names to map database columns.
  • Testing: PHPUnit uses reflection to invoke private methods, read private properties.
  • Serialization: Map class properties to JSON/XML.
  • Routing: NelmioApiDocBundle, Symfony routing via attributes.

Code Example

php
<?php
// Inspect a class with Reflection
class OrderService
{
    private readonly OrderRepository $orders;
    private readonly LoggerInterface $logger;

    public function __construct(OrderRepository $orders, LoggerInterface $logger)
    {
        $this->orders = $orders;
        $this->logger = $logger;
    }

    public function createOrder(array $data): Order { /* ... */ }
    private function validate(array $data): void     { /* ... */ }
}

$reflection = new \ReflectionClass(OrderService::class);

echo $reflection->getName();         // 'OrderService'
echo $reflection->getParentClass();  // false (no parent)

// Inspect constructor parameters (how the container does auto-wiring)
$constructor = $reflection->getConstructor();
foreach ($constructor->getParameters() as $param) {
    $type = $param->getType()?->getName();
    echo "{$param->getName()}: {$type}\n";
    // orders: OrderRepository
    // logger: LoggerInterface
}

// Inspect methods
foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
    echo "public: {$method->getName()}\n"; // createOrder
}
// Only public methods — private 'validate' not shown

// Access private property value (useful in tests)
$service = new OrderService(new OrderRepository(), new NullLogger());
$prop    = $reflection->getProperty('orders');
$prop->setAccessible(true);
$ordersInstance = $prop->getValue($service); // bypasses private!

// PHP 8.0 Attributes — metadata on code
#[\Attribute(\Attribute::TARGET_METHOD)]
class Route
{
    public function __construct(public readonly string $path, public readonly string $method = 'GET') {}
}

class UserController
{
    #[Route('/users', 'GET')]
    public function index(): array { return []; }

    #[Route('/users', 'POST')]
    public function store(): void {}
}

// Read attributes at runtime
$reflection = new \ReflectionClass(UserController::class);
foreach ($reflection->getMethods() as $method) {
    $attrs = $method->getAttributes(Route::class);
    foreach ($attrs as $attr) {
        $route = $attr->newInstance(); // creates a Route instance
        echo "{$route->method} {$route->path} → {$method->getName()}\n";
        // GET /users → index
        // POST /users → store
    }
}