Lab 07: Reflection & Code Gen

Time: 60 minutes | Level: Architect | Docker: docker run -it --rm php:8.3-cli bash

Overview

PHP's Reflection API enables runtime inspection of classes, methods, and attributes. This lab covers ReflectionClass, ReflectionMethod, PHP 8 Attributes, dynamic proxy generation, and building a minimal DI container using reflection.


Step 1: ReflectionClass Basics

<?php
class UserService {
    public string $name = 'UserService';
    protected int $version = 2;
    private array $cache = [];
    
    public function __construct(private readonly string $dsn) {}
    
    public function findById(int $id): ?array { return null; }
    public function findAll(int $limit = 100, int $offset = 0): array { return []; }
    protected function buildQuery(string $table): string { return "SELECT * FROM {$table}"; }
    private function connect(): void {}
}

$rc = new ReflectionClass(UserService::class);

echo "=== ReflectionClass ===\n";
echo "Name:       " . $rc->getName() . "\n";
echo "Short name: " . $rc->getShortName() . "\n";
echo "File:       " . ($rc->getFileName() ?: 'internal') . "\n";
echo "Abstract:   " . ($rc->isAbstract() ? 'yes' : 'no') . "\n";
echo "Final:      " . ($rc->isFinal() ? 'yes' : 'no') . "\n";

echo "\n=== Methods ===\n";
foreach ($rc->getMethods() as $method) {
    $visibility = match(true) {
        $method->isPublic()    => 'public',
        $method->isProtected() => 'protected',
        $method->isPrivate()   => 'private',
    };
    printf("  %-12s %s(%s)\n",
        $visibility,
        $method->getName(),
        implode(', ', array_map(fn($p) => '$' . $p->getName(), $method->getParameters()))
    );
}

echo "\n=== Properties ===\n";
foreach ($rc->getProperties() as $prop) {
    $visibility = match(true) {
        $prop->isPublic()    => 'public',
        $prop->isProtected() => 'protected',
        $prop->isPrivate()   => 'private',
    };
    printf("  %-12s %s\n", $visibility, $prop->getName());
}

echo "\n=== Constructor Parameters ===\n";
foreach ($rc->getConstructor()->getParameters() as $param) {
    $type = $param->hasType() ? $param->getType()->getName() : 'mixed';
    $default = $param->isOptional() ? ' = ' . var_export($param->getDefaultValue(), true) : '';
    echo "  {$type} \${$param->getName()}{$default}\n";
}

Step 2: PHP 8 Attributes

📸 Verified Output:


Step 3: ReflectionMethod::invoke & Closures


Step 4: Code Generation with eval()

📸 Verified Output:

💡 In production, prefer writing generated code to .php files and including them, rather than using eval(). This enables OPcache and avoids eval security concerns.


Step 5: Dynamic Proxy Pattern


Step 6: Attribute-Based ORM Schema Builder

📸 Verified Output:


Step 7: Minimal DI Container

📸 Verified Output:


Step 8: Capstone — Full Attribute-Driven Framework

📸 Verified Output:


Summary

Feature
API
Use Case

Class introspection

new ReflectionClass($class)

DI containers, ORMs

Method list

$rc->getMethods()

Router registration

Property list

$rc->getProperties()

Serializers, mappers

Read attribute

$method->getAttributes(Attr::class)[0]->newInstance()

Metadata reading

Invoke method

$rm->invoke($obj, ...$args)

Dynamic dispatch

Private access

$rm->setAccessible(true)

Testing private methods

Get closure

$rm->getClosure($obj)

Callable from method

Constructor params

$rc->getConstructor()->getParameters()

Auto-wiring

Type inspection

$param->getType()->getName()

DI type resolution

Code generation

eval($generatedCode)

VO builders, proxies

Last updated