advanced php language

Reflection

Reflection lets PHP code inspect other PHP code at runtime. You can inspect classes, methods, properties, parameters, attributes, types, visibility, and more.

Reflection is used by frameworks, service containers, serializers, routers, ORMs, test tools, static-analysis helpers, and developer tooling. It is powerful, but it should not be the first choice for ordinary business logic. Explicit code is usually easier to read and refactor.

Inspecting A Class

ReflectionClass gives information about a class.

PHP example
<?php

declare(strict_types=1);

final class RegisterUser
{
    public function __construct(
        private Mailer $mailer,
    ) {
    }
}

interface Mailer
{
    public function send(string $email, string $message): void;
}

$class = new ReflectionClass(RegisterUser::class);

echo $class->getName() . PHP_EOL;
echo $class->isFinal() ? 'final' : 'not final';
echo PHP_EOL;

// Prints:
// RegisterUser
// final

Frameworks use this kind of information to discover classes and understand how to build them.

Inspecting Constructor Parameters

Service containers often inspect constructor parameters to autowire dependencies.

PHP example
<?php

declare(strict_types=1);

interface Mailer
{
    public function send(string $email, string $message): void;
}

final class RegisterUser
{
    public function __construct(
        private Mailer $mailer,
        private string $appName,
    ) {
    }
}

$class = new ReflectionClass(RegisterUser::class);
$constructor = $class->getConstructor();

foreach ($constructor?->getParameters() ?? [] as $parameter) {
    $type = $parameter->getType();

    echo $parameter->getName() . ': ' . ($type?->__toString() ?? 'mixed') . PHP_EOL;
}

// Prints:
// mailer: Mailer
// appName: string

The container can infer that Mailer is a dependency. It still needs configuration for which Mailer implementation to use and what string value should be passed for $appName.

Inspecting Attributes

Reflection is how code reads attributes.

PHP example
<?php

declare(strict_types=1);

#[Attribute(Attribute::TARGET_METHOD)]
final readonly class Route
{
    public function __construct(
        public string $method,
        public string $path,
    ) {
    }
}

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

$method = new ReflectionMethod(UserController::class, 'index');

foreach ($method->getAttributes(Route::class) as $attribute) {
    $route = $attribute->newInstance();

    echo $route->method . ' ' . $route->path . PHP_EOL;
}

// Prints:
// GET /users

The attribute is metadata. Reflection lets runtime code read that metadata.

Invoking Methods With Reflection

Reflection can call methods dynamically, but this should be used carefully.

PHP example
<?php

declare(strict_types=1);

final class Calculator
{
    public function add(int $a, int $b): int
    {
        return $a + $b;
    }
}

$method = new ReflectionMethod(Calculator::class, 'add');

echo $method->invoke(new Calculator(), 2, 3) . PHP_EOL;

// Prints:
// 5

For normal code, $calculator->add(2, 3) is clearer. Reflection invocation is mainly useful for frameworks, test tools, and dynamic dispatch systems.

Visibility And Private Members

Reflection can inspect private properties and methods. That does not mean application code should bypass visibility.

Visibility expresses design. If you need reflection to change a private property in production code, the design probably needs a clearer public method.

Testing tools and serializers sometimes need controlled access, but treat that as infrastructure-level work.

Performance And Caching

Reflection has a cost. Frameworks often cache reflected metadata so they do not repeatedly inspect the same classes on every request.

Do not panic about using reflection where it belongs, but avoid reflection inside tight loops unless there is a clear reason and measurement.

What You Should Be Able To Do

After this lesson, you should be able to inspect a class, read constructor parameters, read attributes, and explain why reflection is common in frameworks but uncommon in ordinary business logic.

For junior work, this matters because many "magic" framework features are powered by reflection. Understanding that makes autowiring, attributes, routing, serializers, and testing tools less mysterious.

Practice

Practice: Inspect Constructor Parameters

Create a small PHP example that uses reflection to inspect a service constructor.

Task

Build:

  • a Mailer interface
  • a RegisterUser class with a constructor that accepts Mailer and a string app name
  • reflection code that prints each constructor parameter name and type

Use strict types. Keep the expected output in the PHP code block as printed lines or comments.

Check Your Work

Confirm:

  • ReflectionClass targets the service class
  • getConstructor() finds the constructor
  • each parameter name and type is printed

Afterward, explain how this relates to service-container autowiring.

Show solution

This solution uses ReflectionClass to inspect constructor dependencies.

PHP example
<?php

declare(strict_types=1);

interface Mailer
{
    public function send(string $email, string $message): void;
}

final class RegisterUser
{
    public function __construct(
        private Mailer $mailer,
        private string $appName,
    ) {
    }
}

$class = new ReflectionClass(RegisterUser::class);
$constructor = $class->getConstructor();

foreach ($constructor?->getParameters() ?? [] as $parameter) {
    $type = $parameter->getType();

    echo $parameter->getName() . ': ' . ($type?->__toString() ?? 'mixed') . PHP_EOL;
}

// Prints:
// mailer: Mailer
// appName: string

Service containers use this kind of reflection to see what a class needs. They can usually resolve class or interface dependencies with bindings, but scalar values such as strings often need explicit configuration.