Reflection en PHP 7

Reflection es un concepto muy importante que todo desarrollador debe tener en cuenta. Denota la capacidad de un programa para inspeccionarse a sí mismo durante el tiempo de ejecución, lo que permite una fácil ingeniería inversa de clases, interfaces, funciones, métodos y extensiones.

Podemos obtener una muestra rápida de las capacidades de reflexión de PHP directamente desde la consola. La CLI de PHP admite varios comandos basados en reflection:

  • –rf : muestra información sobre una función
  • –rc : muestra información sobre una clase
  • –re : muestra información sobre una extensión
  • –rz : muestra información sobre la extensión Zend
  • –ri : muestra la configuración de una extensión

El siguiente resultado muestra el resultado del comando php –rf str_replace:

Function [ <internal:standard> function str_replace ] {
- Parameters [4] {
Parameter #0 [ <required> $search ]
Parameter #1 [ <required> $replace ]
Parameter #2 [ <required> $subject ]
Parameter #3 [ <optional> &$replace_count ]
}
}

El resultado se refleja en la función str_replace(), que es una función estándar de PHP. Describe claramente el número total de parámetros, junto con su nombre y asignación requerida u opcional.

El poder real de reflection, el que los desarrolladores pueden utilizar, proviene de la API reflection. Echemos un vistazo al siguiente ejemplo:

<?php
class User
{
public $name = 'John';
protected $ssn = 'AAA-GG-SSSS';
private $salary = 4200.00;
}
$user = new User();
echo $user->name = 'Marc'; // Marc
//echo $user->ssn = 'BBB-GG-SSSS';
// Uncaught Error: Cannot access protected property User::$ssn
//echo $user->salary = 5600.00;
// Uncaught Error: Cannot access private property User::$salary
var_dump($user);
//object(User)[1]
// public 'name' => string 'Marc' (length=4)
// protected 'ssn' => string 'AAA-GG-SSSS' (length=11)
// private 'salary' => float 4200

Comenzamos definiendo una clase User con tres propiedades, cada una con una visibilidad diferente.
Luego instanciamos un objeto de la clase User e intentamos cambiar el valor de las tres propiedades. Normalmente, no se puede acceder a los miembros que se definen como protected o private fuera de un objeto. Intentar acceder a ellos en modo lectura o escritura lanzaría un error No se puede acceder … Esto es lo que consideraríamos un comportamiento normal.
Usando la API de reflection PHP, podemos eludir este comportamiento normal, haciendo posible el acceso a miembros privados y protegidos. La propia API de reflexión proporciona varias clases para que usemos:

  • Reflection
  • ReflectionClass
  • ReflectionZendExtension
  • ReflectionExtension
  • ReflectionFunction
  • ReflectionFunctionAbstract
  • ReflectionMethod
  • ReflectionObject
  • ReflectionParameter
  • ReflectionProperty
  • ReflectionType
  • ReflectionGenerator
  • Reflector (interface)
  • ReflectionException (exception)

Cada una de estas clases expone un conjunto diverso de funcionalidades, lo que nos permite jugar con elementos internos de otras clases, interfaces, funciones, métodos y extensiones. Asumiendo que nuestro objetivo es cambiar los valores de las propiedades protected y private del ejemplo anterior, podríamos usar ReflectionClass y ReflectionProperty, de acuerdo con el siguiente ejemplo:

<?php
// …
$user = new User();
$reflector = new ReflectionClass('User');
foreach ($reflector->getProperties() as $prop) {
$prop->setAccessible(true);
if ($prop->getName() == 'name') $prop->setValue($user, 'Alice');
if ($prop->getName() == 'ssn') $prop->setValue($user, 'CCC-GG-SSSS');
if ($prop->getName() == 'salary') $prop->setValue($user, 2600.00);
}
var_dump($user);
//object(User)[1]
// public 'name' => string 'Alice' (length=5)
// protected 'ssn' => string 'CCC-GG-SSSS' (length=11)
// private 'salary' => float 2600

Comenzamos instanciando un objeto de una clase User, como lo hicimos en el ejemplo anterior.
Luego creamos una instancia de ReflectionClass, pasando a su constructor el nombre de la clase User. La instancia $reflector recién creada nos permite obtener una lista de todas las propiedades de clase de User a través de su método getProperties(). Recorriendo las propiedades, una por una, iniciamos la verdadera magia de la API de reflection. Cada propiedad ($prop) es una instancia de la clase ReflectionProperty. Dos de los métodos de ReflectionProperty, setAccessible() y setValue(), proporcionan la funcionalidad adecuada para que podamos alcanzar nuestro objetivo. Con estos métodos, podemos establecer el valor de lo contrario inaccesible propiedades del objeto.
Otro ejemplo de reflexión simple pero interesante es el de la extracción de comentarios de documentos:

<?php
class Calc
{
/**
* @param $x The number x
* @param $y The number y
* @return mixed The number z
*/
public function sum($x, $y)
{
return $x + $y;
}
}
$calc = new Calc();
$reflector = new ReflectionClass('Calc');
$comment = $reflector->getMethod('sum')->getDocComment();
echo $comment;

Con solo dos líneas de código, pudimos reflexionar sobre una clase Calc y extraer el comentario del documento de su método sum(). Si bien el uso práctico de la API de reflection puede no ser obvio al principio, son capacidades como estas las que nos permiten construir bibliotecas y plataformas potentes y dinámicas.

La herramienta phpDocumentor utiliza las funciones de reflexión de PHP para
generar automáticamente documentación desde el código fuente. La popular plataforma de comercio electrónico Magento v2.x utiliza ampliamente las funciones de reflexión de PHP para crear instancias automáticamente de objetos insinuados como argumentos __construct().

Comparte