Reflection API

Ma Mariya Vinciya Updated 21 Oct 2026
6 min read ·Lesson 172 of 174
In this post

What Is Reflection? In simple terms: Reflection lets a PHP program examine its own code while it runs: which properties a class has, which parameters a method expects, what types they use, which attri

What Is Reflection?

In simple terms: Reflection lets a PHP program examine its own code while it runs: which properties a class has, which parameters a method expects, what types they use, which attributes are attached, and more. It can also create objects and call methods based on that information. Reflection is how frameworks "magically" know what your classes need.

Technically: The Reflection extension (always available) provides classes such as ReflectionClass, ReflectionObject, ReflectionMethod, ReflectionProperty, ReflectionFunction, ReflectionParameter, ReflectionNamedType, ReflectionEnum, and ReflectionAttribute. Each one describes a part of the code and offers methods to inspect — and sometimes use — it.

Where Reflection Is Used

ToolWhat It Does With Reflection
Dependency injection containers (Laravel, Symfony)Read constructor parameters to create and inject dependencies automatically
ORMs (Doctrine)Read attributes and properties to map objects to database tables
Validators and serializersRead attributes and property types
Testing tools (PHPUnit)Find test methods, create mocks
Documentation and static analysisDescribe classes, methods, and types

 

Application code rarely needs reflection directly, but understanding it explains how these tools work.


Inspecting a Class

<?php
final class Product
{
    public const CURRENCY = 'INR';

    public function __construct(
        public readonly string $sku,
        private int $pricePaise,
        protected ?string $category = null,
    ) {}

    public function price(): float
    {
        return $this->pricePaise / 100;
    }

    private function internalCode(): string
    {
        return 'P-' . $this->sku;
    }
}

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

echo $class->getName() . ($class->isFinal() ? ' (final)' : '') . "\n";

foreach ($class->getProperties() as $property) {
    $visibility = $property->isPublic() ? 'public' : ($property->isProtected() ? 'protected' : 'private');
    $readonly = $property->isReadOnly() ? ' readonly' : '';
    echo "  property: $visibility {$property->getType()} \${$property->getName()}$readonly\n";
}

foreach ($class->getMethods() as $method) {
    $returnType = $method->hasReturnType() ? ': ' . $method->getReturnType() : '';
    echo "  method: {$method->getName()}() with " . $method->getNumberOfParameters() . " parameter(s)$returnType\n";
}

print_r($class->getConstants());

 

Output

Product (final)
  property: public string $sku readonly
  property: private int $pricePaise
  property: protected ?string $category
  method: __construct() with 3 parameter(s)
  method: price() with 0 parameter(s): float
  method: internalCode() with 0 parameter(s): string
Array
(
    [CURRENCY] => INR
)

 

How It Works

  1. new ReflectionClass(Product::class) creates a reflection object describing the class (the class name as a string or an object can be passed).
  2. getProperties() returns ReflectionProperty objects, including private and protected ones, in declaration order. Promoted constructor properties are included.
  3. getType() returns a ReflectionType; converting it to a string gives the declared type (?string).
  4. getMethods() returns ReflectionMethod objects; getReturnType() describes return types.
  5. getConstants() returns all class constants.

Common ReflectionClass methods include getShortName(), getParentClass(), getInterfaceNames(), implementsInterface(), isAbstract(), isInterface(), hasMethod(), getConstructor(), getAttributes(), and newInstanceArgs().


Inspecting Parameters and Types

<?php
function createInvoice(int $customerId, array $items, ?string $coupon = null, string $currency = 'INR'): string
{
    return "invoice";
}

$function = new ReflectionFunction('createInvoice');

foreach ($function->getParameters() as $parameter) {
    $type = $parameter->getType();
    $line = sprintf('$%s: %s', $parameter->getName(), $type);

    if ($type instanceof ReflectionNamedType && $type->allowsNull()) {
        $line .= ' (nullable)';
    }
    if ($parameter->isDefaultValueAvailable()) {
        $line .= ' = ' . var_export($parameter->getDefaultValue(), true);
    }

    echo $line . "\n";
}

 

Output

$customerId: int
$items: array
$coupon: ?string (nullable) = NULL
$currency: string = 'INR'

 

ReflectionNamedType describes a single type: getName() returns string, isBuiltin() tells whether it is a built-in type (as opposed to a class), and allowsNull() whether null is accepted. Union and intersection types are described by ReflectionUnionType and ReflectionIntersectionType, whose getTypes() returns the individual types.


Accessing Private Members

Reflection can read private properties and call private methods. Since PHP 8.1, no setAccessible(true) call is needed:

<?php
final class Product
{
    public function __construct(private int $pricePaise, private string $sku) {}

    private function internalCode(): string
    {
        return 'P-' . $this->sku;
    }
}

$product = new Product(129900, 'LAMP-01');
$class = new ReflectionClass($product);

echo $class->getProperty('pricePaise')->getValue($product) . "\n";
echo $class->getMethod('internalCode')->invoke($product) . "\n";

 

Output

129900
P-LAMP-01

 

This is useful for frameworks (hydrating objects from database rows) and occasionally for tests, but bypassing visibility in application code breaks encapsulation. Treat it as a tool for infrastructure code only.


Practical Example: A Tiny Autowiring Container

Dependency injection containers use reflection to read constructor parameters and create the required objects automatically ("autowiring"):

<?php
final class Logger
{
    public function log(string $message): void
    {
        echo "[log] $message\n";
    }
}

final class Mailer
{
    public function __construct(private Logger $logger) {}

    public function send(string $to, string $message): void
    {
        $this->logger->log("Mail to $to: $message");
    }
}

final class OrderService
{
    public function __construct(
        private Mailer $mailer,
        private Logger $logger,
        private int $retries = 3,
    ) {}

    public function place(int $orderId, string $email): void
    {
        $this->logger->log("Placing order $orderId (retries: {$this->retries})");
        $this->mailer->send($email, "Order $orderId confirmed");
    }
}

final class Container
{
    private array $instances = [];

    public function get(string $class): object
    {
        return $this->instances[$class] ??= $this->build($class);
    }

    private function build(string $class): object
    {
        $reflection = new ReflectionClass($class);
        $constructor = $reflection->getConstructor();

        if ($constructor === null) {
            return $reflection->newInstance();
        }

        $arguments = [];

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

            if ($type instanceof ReflectionNamedType && !$type->isBuiltin()) {
                $arguments[] = $this->get($type->getName());
            } elseif ($parameter->isDefaultValueAvailable()) {
                $arguments[] = $parameter->getDefaultValue();
            } else {
                throw new LogicException("Cannot resolve \${$parameter->getName()} for $class");
            }
        }

        return $reflection->newInstanceArgs($arguments);
    }
}

$container = new Container();
$container->get(OrderService::class)->place(501, 'asha@example.com');

var_dump($container->get(Logger::class) === $container->get(Logger::class));

 

Output

[log] Placing order 501 (retries: 3)
[log] Mail to asha@example.com: Order 501 confirmed
bool(true)

 

Step-by-Step Explanation

  1. Requesting OrderService makes the container inspect its constructor: it needs a Mailer, a Logger, and an int with a default.
  2. For class-typed parameters, the container recursively builds the dependency (Mailer itself needs a Logger).
  3. Built-in typed parameters with defaults use their default value; anything else that cannot be resolved causes a clear exception.
  4. ??= caches each created object, so every class gets one shared instance (the same Logger is used everywhere).
  5. newInstanceArgs() creates the object with the resolved arguments.

Real containers add interface-to-class bindings, configuration values, and caching of the reflection results, but the principle is the same.


Other Reflection Features

FeatureExample
Attributes$class->getAttributes(Route::class) (see PHP Attributes)
Enumsnew ReflectionEnum(OrderStatus::class) → getCases(), getBackingType()
Closuresnew ReflectionFunction($closure) → parameters, scope, static variables
Generators and fibersReflectionGenerator, ReflectionFiber for debugging tools
Lazy objects (PHP 8.4)$class->newLazyGhost(...) / newLazyProxy(...) create objects that initialize themselves on first use — used by ORMs and DI containers

 


Performance

Creating reflection objects and reading metadata is slower than ordinary code. Frameworks therefore analyze classes once — often during a build or cache-warming step — and store the results (for example, as compiled container code). Avoid reflection inside hot loops of request handling.


Common Mistakes

Using Reflection Where Plain Code Works

Effect: Harder-to-read, slower code.

Fix: Use interfaces, callbacks, and normal method calls; reserve reflection for generic infrastructure.

Breaking Encapsulation in Application Code

Effect: Code depends on private details that may change.

Fix: Expose what is needed through public methods.

Ignoring Union and Intersection Types

Effect: Code assumes getType() always returns a ReflectionNamedType with getName().

Fix: Check the type class with instanceof before calling type-specific methods.

Repeating Reflection on Every Request

Effect: Unnecessary overhead.

Fix: Cache the extracted metadata.


Best Practices

  • Use reflection for frameworks, libraries, and tooling — not for everyday business logic.
  • Check type classes (ReflectionNamedType, ReflectionUnionType) before using them.
  • Combine reflection with attributes for declarative configuration.
  • Cache reflection results in production.
  • Prefer public APIs over reading private members.

Official reference: https\://www.php.net/manual/en/book.reflection.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else User-defined Functions Slicing Strings PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning MySQL Database MySQL Connect MySQL Create DB MySQL Create Table MySQL Insert Data MySQL Get Last ID MySQL Insert Multiple MySQL Prepared MySQL Select Data MySQL Where Debugging PHP Project: Image Upload Gallery $_SESSION $_ENV PHP RegEx RegEx Patterns Heredoc and Nowdoc RegEx Modifiers preg_match preg_replace preg_split PHP RegEx Functions Nullable Types Mixed and Never Types Nullsafe Operator PHP Attributes PHP Fibers Readonly Classes Property Hooks (PHP 8.4) Asymmetric Visibility (PHP 8.4) Reflection API SPL Data Structures Composer
Ask about this post
AI Ask about this post

Ask questions about Reflection API and get answers drawn from it.

Signed-in readers only.