Reflection API en PHP: ReflectionClass, ReflectionMethod y usos prácticos

La Reflection API de PHP permite inspeccionar clases, métodos, propiedades y funciones en tiempo de ejecución: leer sus nombres, modificadores de acceso, tipos, valores por defecto, atributos y documentación. Es la base de frameworks DI, ORMs, test runners y librerías de serialización que necesitan conocer la estructura de tu código sin tocarlo directamente.

ReflectionClass: inspeccionar una clase

<?php
class Producto {
    public function __construct(
        private readonly int    $id,
        public string           $nombre,
        protected float         $precio = 0.0
    ) {}

    public function getNombre(): string    { return $this->nombre; }
    private function calcularIva(): float  { return $this->precio * 0.21; }
}

$ref = new ReflectionClass(Producto::class);

echo $ref->getName();                    // Producto
echo $ref->getFileName();                // /ruta/al/fichero.php
var_dump($ref->isFinal());               // false
var_dump($ref->isAbstract());            // false
var_dump($ref->implementsInterface('Stringable')); // false

// Métodos de la clase
foreach ($ref->getMethods() as $m) {
    $visibilidad = match(true) {
        $m->isPublic()    => 'public',
        $m->isProtected() => 'protected',
        $m->isPrivate()   => 'private',
    };
    echo "$visibilidad {$m->getName()}n";
}
// public __construct
// public getNombre
// private calcularIva

ReflectionMethod: inspeccionar un método

<?php
$metodo = new ReflectionMethod(Producto::class, 'calcularIva');

var_dump($metodo->isPrivate());      // true
var_dump($metodo->isStatic());       // false
var_dump($metodo->getReturnType()); // ReflectionNamedType(float)

// Leer los parámetros
$constructor = new ReflectionMethod(Producto::class, '__construct');
foreach ($constructor->getParameters() as $param) {
    echo "${$param->getName()}: ";
    echo $param->getType() . " ";
    echo ($param->isOptional() ? "(opcional, default={$param->getDefaultValue()})" : "(requerido)");
    echo "n";
}
// $id: int (requerido)
// $nombre: string (requerido)
// $precio: float (opcional, default=0)

ReflectionProperty: leer y modificar propiedades privadas

Normalmente no puedes acceder a propiedades privadas desde fuera de la clase. Con la Reflection API sí puedes, previo setAccessible(true) (no necesario desde PHP 8.1):

<?php
$obj = new Producto(42, 'Teclado', 59.99);
$ref = new ReflectionClass($obj);

$propId = $ref->getProperty('id');
$propId->setAccessible(true);  // necesario en PHP < 8.1
echo $propId->getValue($obj);  // 42

// También puedes modificar valores en tests
$propNombre = $ref->getProperty('nombre');
$propNombre->setValue($obj, 'Ratón');
echo $obj->getNombre(); // Ratón

Caso práctico 1: contenedor de inyección de dependencias

<?php
class Container {
    private array $bindings = [];

    public function bind(string $clase, callable $factory): void {
        $this->bindings[$clase] = $factory;
    }

    public function make(string $clase): object {
        if (isset($this->bindings[$clase])) {
            return ($this->bindings[$clase])($this);
        }

        $ref = new ReflectionClass($clase);
        if (!$ref->isInstantiable()) {
            throw new RuntimeException("$clase no es instanciable");
        }

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

        $args = array_map(
            fn(ReflectionParameter $p) => $this->resolveParam($p),
            $constructor->getParameters()
        );

        return $ref->newInstanceArgs($args);
    }

    private function resolveParam(ReflectionParameter $param): mixed {
        $tipo = $param->getType();
        if ($tipo instanceof ReflectionNamedType && !$tipo->isBuiltin()) {
            return $this->make($tipo->getName());
        }
        if ($param->isDefaultValueAvailable()) {
            return $param->getDefaultValue();
        }
        throw new RuntimeException("No se puede resolver ${$param->getName()}");
    }
}

// Uso
class MailService {
    public function enviar(string $to, string $msg): void {
        echo "Enviando a $to: $msgn";
    }
}

class UsuarioService {
    public function __construct(private MailService $mailer) {}

    public function registrar(string $email): void {
        $this->mailer->enviar($email, 'Bienvenido');
    }
}

$container = new Container();
$service   = $container->make(UsuarioService::class);
$service->registrar('[email protected]'); // Enviando a [email protected]: Bienvenido

Caso práctico 2: serializer automático basado en Reflection

<?php
function toArray(object $obj): array {
    $ref    = new ReflectionClass($obj);
    $result = [];

    foreach ($ref->getProperties() as $prop) {
        $prop->setAccessible(true);
        $valor = $prop->getValue($obj);
        $result[$prop->getName()] = is_object($valor) ? toArray($valor) : $valor;
    }

    return $result;
}

class Direccion {
    public function __construct(
        public string $calle,
        public string $ciudad
    ) {}
}

class Persona {
    private int $id;
    public string $nombre;
    protected Direccion $direccion;

    public function __construct(int $id, string $nombre, Direccion $dir) {
        $this->id        = $id;
        $this->nombre    = $nombre;
        $this->direccion = $dir;
    }
}

$p = new Persona(1, 'Ana', new Direccion('Gran Vía 1', 'Madrid'));
print_r(toArray($p));
// Array ( [id] => 1 [nombre] => Ana [direccion] => Array ( [calle] => Gran Vía 1 [ciudad] => Madrid ) )

Rendimiento y cuándo no usarla

La Reflection API es más lenta que el acceso directo a propiedades. En producción, los frameworks la usan una sola vez y cachean el resultado. No la uses en bucles internos ni en paths críticos sin caché. Si solo necesitas saber si una clase implementa una interfaz, instanceof es la opción correcta; usa Reflection cuando necesites información estructural que no puedes obtener de otra forma.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP