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.
