Spanish (Español) translation by Andrea Jiménez (you can also view the original English article)
La reflexión se define generalmente como la capacidad de un programa para inspeccionarse a sí mismo y modificar su lógica en el momento de la ejecución. En términos menos técnicos, la reflexión es pedirle a un objeto que te informe sobre sus propiedades y métodos, y alterar esos miembros (incluso los privados). En esta lección, profundizaremos en cómo se logra esto y cuándo podría resultar útil.
Una pequeña historia
En los inicios de la era de la programación, existía el lenguaje ensamblador. Un programa escrito en ensamblador se encuentra en los registros físicos dentro de la computadora. Su composición, métodos y valores pueden ser inspeccionados en cualquier momento leyendo los registros. Aún más, podrías alterar el programa mientras se ejecutaba simplemente modificando esos registros. Requería un conocimiento profundo sobre el programa en ejecución, pero era inherentemente reflexivo.
Como con cualquier juguete genial, usa la reflexión, pero no abuses de ella.
A medida que llegaban los lenguajes de programación de alto nivel (como C), esta reflectividad se desvaneció y desapareció. Más tarde se reintrodujo con la programación orientada a objetos.
Hoy en día, la mayoría de los lenguajes de programación pueden utilizar la reflexión. Los lenguajes de tipo estático, como Java, tienen poco o ningún problema con la reflexión. Sin embargo, lo que encuentro interesante es que cualquier lenguaje de tipo dinámico (como PHP o Ruby) se basa en gran medida en la reflexión. Sin el concepto de reflexión, lo más probable es que el duck-typing sea imposible de implementar. Cuando envías un objeto a otro (un parámetro, por ejemplo), el objeto receptor no tiene forma de conocer la estructura y el tipo de ese objeto. Todo lo que puede hacer es usar la reflexión para identificar los métodos que se pueden y no se pueden llamar en el objeto recibido.
Un ejemplo sencillo
La reflexión es frecuente en PHP. De hecho, hay varias situaciones en las que puedes usarlo sin siquiera saberlo. Por ejemplo:
1 |
// Nettuts.php
|
2 |
|
3 |
require_once 'Editor.php'; |
4 |
|
5 |
class Nettuts { |
6 |
|
7 |
function publishNextArticle() { |
8 |
$editor = new Editor('John Doe'); |
9 |
$editor->setNextArticle('135523'); |
10 |
$editor->publish(); |
11 |
}
|
12 |
|
13 |
}
|
Y:
1 |
// Editor.php
|
2 |
|
3 |
class Editor { |
4 |
|
5 |
private $name; |
6 |
public $articleId; |
7 |
|
8 |
function __construct($name) { |
9 |
$this->name = $name; |
10 |
}
|
11 |
|
12 |
public function setNextArticle($articleId) { |
13 |
$this->articleId = $articleId; |
14 |
}
|
15 |
|
16 |
public function publish() { |
17 |
// publish logic goes here
|
18 |
return true; |
19 |
}
|
20 |
|
21 |
}
|
En este código, tenemos una llamada directa a una variable inicializada localmente con un tipo conocido. La creación del editor en publishNextArticle() hace que sea obvio que la variable $editor es de tipo Editor. No se necesita ninguna reflexión aquí, pero introduzcamos una nueva clase, llamada Manager:
1 |
// Manager.php
|
2 |
|
3 |
require_once './Editor.php'; |
4 |
require_once './Nettuts.php'; |
5 |
|
6 |
class Manager { |
7 |
|
8 |
function doJobFor(DateTime $date) { |
9 |
if ((new DateTime())->getTimestamp() > $date->getTimestamp()) { |
10 |
$editor = new Editor('John Doe'); |
11 |
$nettuts = new Nettuts(); |
12 |
$nettuts->publishNextArticle($editor); |
13 |
}
|
14 |
}
|
15 |
|
16 |
}
|
Luego, modifica Nettuts, así:
1 |
// Nettuts.php
|
2 |
|
3 |
class Nettuts { |
4 |
|
5 |
function publishNextArticle($editor) { |
6 |
$editor->setNextArticle('135523'); |
7 |
$editor->publish(); |
8 |
}
|
9 |
|
10 |
}
|
Ahora, Nettuts no tiene absolutamente ninguna relación con la clase Editor. No incluye su archivo, no inicializa su clase y ni siquiera sabe que existe. Podría pasar un objeto de cualquier tipo al método publishNextArticle() y el código funcionaría.



Como puedes ver en este diagrama de clases, Nettuts solo tiene una relación directa con Manager. Manager lo crea y, por lo tanto, Manager depende de Nettuts. Pero Nettuts ya no tiene ninguna relación con la clase Editor y Editor solo está relacionado con Manager.
En el momento de la ejecución, Nettuts utiliza un objeto de la clase Editor, por ende, <uses>> y el signo de interrogación. En el momento de la ejecución, PHP inspecciona el objeto recibido y verifica que implemente los métodos setNextArticle() y publish().
Información del miembro del objeto
Podemos hacer que PHP muestre los detalles de un objeto. Creemos una prueba PHPUnit para ayudarnos a emplear fácilmente nuestro código:
1 |
// ReflectionTest.php
|
2 |
|
3 |
require_once '../Editor.php'; |
4 |
require_once '../Nettuts.php'; |
5 |
|
6 |
class ReflectionTest extends PHPUnit_Framework_TestCase { |
7 |
|
8 |
function testItCanReflect() { |
9 |
$editor = new Editor('John Doe'); |
10 |
$tuts = new Nettuts(); |
11 |
$tuts->publishNextArticle($editor); |
12 |
}
|
13 |
|
14 |
}
|
Ahora, agrega un var_dump() a Nettuts:
1 |
// Nettuts.php
|
2 |
|
3 |
class NetTuts { |
4 |
|
5 |
function publishNextArticle($editor) { |
6 |
$editor->setNextArticle('135523'); |
7 |
$editor->publish(); |
8 |
var_dump(new ReflectionClass($editor)); |
9 |
}
|
10 |
|
11 |
}
|
Ejecuta la prueba y observa cómo sucede la magia en la salida:
1 |
PHPUnit 3.6.11 by Sebastian Bergmann. |
2 |
|
3 |
.object(ReflectionClass)#197 (1) {
|
4 |
["name"]=> |
5 |
string(6) "Editor" |
6 |
} |
7 |
|
8 |
|
9 |
Time: 0 seconds, Memory: 2.25Mb |
10 |
|
11 |
OK (1 test, 0 assertions) |
Nuestra clase de reflexión tiene una propiedad name establecida en el tipo original de la variable $editor: Editor, pero no es mucha información. ¿Qué pasa con los métodos de Editor?
1 |
// Nettuts.php
|
2 |
|
3 |
class Nettuts { |
4 |
|
5 |
function publishNextArticle($editor) { |
6 |
$editor->setNextArticle('135523'); |
7 |
$editor->publish(); |
8 |
|
9 |
$reflector = new ReflectionClass($editor); |
10 |
var_dump($reflector->getMethods()); |
11 |
}
|
12 |
|
13 |
}
|
En este código, asignamos la instancia de la clase de reflexión a la variable $reflector para que ahora podamos activar sus métodos. ReflectionClass expone un gran conjunto de métodos que puedes usar para obtener la información de un objeto. Uno de estos métodos es getMethods(), el cual devuelve una matriz que contiene la información de cada método.
1 |
PHPUnit 3.6.11 by Sebastian Bergmann. |
2 |
|
3 |
.array(3) {
|
4 |
[0]=> |
5 |
&object(ReflectionMethod)#196 (2) {
|
6 |
["name"]=> |
7 |
string(11) "__construct" |
8 |
["class"]=> |
9 |
string(6) "Editor" |
10 |
} |
11 |
[1]=> |
12 |
&object(ReflectionMethod)#195 (2) {
|
13 |
["name"]=> |
14 |
string(14) "setNextArticle" |
15 |
["class"]=> |
16 |
string(6) "Editor" |
17 |
} |
18 |
[2]=> |
19 |
&object(ReflectionMethod)#194 (2) {
|
20 |
["name"]=> |
21 |
string(7) "publish" |
22 |
["class"]=> |
23 |
string(6) "Editor" |
24 |
} |
25 |
} |
26 |
|
27 |
Time: 0 seconds, Memory: 2.25Mb |
28 |
|
29 |
OK (1 test, 0 assertions) |
Otro método, getProperties(), recupera las propiedades (¡incluso las propiedades privadas!) del objeto:
1 |
PHPUnit 3.6.11 by Sebastian Bergmann. |
2 |
|
3 |
.array(2) {
|
4 |
[0]=> |
5 |
&object(ReflectionProperty)#196 (2) {
|
6 |
["name"]=> |
7 |
string(4) "name" |
8 |
["class"]=> |
9 |
string(6) "Editor" |
10 |
} |
11 |
[1]=> |
12 |
&object(ReflectionProperty)#195 (2) {
|
13 |
["name"]=> |
14 |
string(9) "articleId" |
15 |
["class"]=> |
16 |
string(6) "Editor" |
17 |
} |
18 |
} |
19 |
|
20 |
Time: 0 seconds, Memory: 2.25Mb |
21 |
|
22 |
OK (1 test, 0 assertions) |
Los elementos de las matrices devueltas por el método getMethod() y getProperties() son de tipo ReflectionMethod y ReflectionProperty, respectivamente; estos objetos son bastante útiles:
1 |
// Nettuts.php
|
2 |
|
3 |
class Nettuts { |
4 |
|
5 |
function publishNextArticle($editor) { |
6 |
$editor->setNextArticle('135523'); |
7 |
$editor->publish(); // first call to publish() |
8 |
|
9 |
$reflector = new ReflectionClass($editor); |
10 |
$publishMethod = $reflector->getMethod('publish'); |
11 |
$publishMethod->invoke($editor); // second call to publish() |
12 |
}
|
13 |
|
14 |
}
|
Aquí, usamos el método getMethod() para recuperar un solo método con el nombre de "publicar"; cuyo resultado es un objeto ReflectionMethod. Luego, llamamos al método invoke(), pasándole el objeto $editor, para ejecutar el método publish() del editor una segunda vez.
Este proceso fue simple en nuestro caso, porque ya teníamos un objeto Editor para pasarlo a invoke(). Es posible que tengamos varios objetos Editor en algunas circunstancias, lo que nos da el lujo de elegir qué objeto usar. En otras circunstancias, es posible que no tengamos objetos con los que trabajar, en cuyo caso tendríamos que obtener uno de ReflectionClass.
Modifiquemos el método publish() del Editor para demostrar la doble llamada:
1 |
// Editor.php
|
2 |
|
3 |
class Editor { |
4 |
|
5 |
[ ... ] |
6 |
|
7 |
public function publish() { |
8 |
// publish logic goes here
|
9 |
echo ("HERE\n"); |
10 |
return true; |
11 |
}
|
12 |
|
13 |
}
|
Y la nueva salida:
1 |
PHPUnit 3.6.11 by Sebastian Bergmann. |
2 |
|
3 |
.HERE |
4 |
HERE |
5 |
|
6 |
Time: 0 seconds, Memory: 2.25Mb |
7 |
|
8 |
OK (1 test, 0 assertions) |
Manipular datos de la instancia
También podemos modificar el código en el momento de la ejecución. ¿Qué pasa con la modificación de una variable privada que no tiene ningún regulador público? Agreguemos un método al Editor que recupere el nombre del editor:
1 |
// Editor.php
|
2 |
|
3 |
class Editor { |
4 |
|
5 |
private $name; |
6 |
public $articleId; |
7 |
|
8 |
function __construct($name) { |
9 |
$this->name = $name; |
10 |
}
|
11 |
|
12 |
[ ... ] |
13 |
|
14 |
function getEditorName() { |
15 |
return $this->name; |
16 |
}
|
17 |
|
18 |
}
|
Este nuevo método se llama getEditorName() y simplemente devuelve el valor de la variable privada $name. La variable $name se establece en el momento de la creación y no tenemos métodos públicos que nos permitan cambiarla. Pero podemos acceder a esta variable usando la reflexión. En primer lugar, podrías probar el enfoque más obvio:
1 |
// Nettuts.php
|
2 |
|
3 |
class Nettuts { |
4 |
|
5 |
function publishNextArticle($editor) { |
6 |
var_dump($editor->getEditorName()); |
7 |
|
8 |
$reflector = new ReflectionClass($editor); |
9 |
$editorName = $reflector->getProperty('name'); |
10 |
$editorName->getValue($editor); |
11 |
|
12 |
}
|
13 |
|
14 |
}
|
Aunque esto genera el valor en la línea var_dump(), arroja un error al intentar recuperar el valor con reflexión:
1 |
PHPUnit 3.6.11 by Sebastian Bergmann. |
2 |
|
3 |
Estring(8) "John Doe" |
4 |
|
5 |
|
6 |
Time: 0 seconds, Memory: 2.50Mb |
7 |
|
8 |
There was 1 error: |
9 |
|
10 |
1) ReflectionTest::testItCanReflect |
11 |
ReflectionException: Cannot access non-public member Editor::name |
12 |
|
13 |
[...]/Reflection in PHP/Source/NetTuts.php:13 |
14 |
[...]/Reflection in PHP/Source/Tests/ReflectionTest.php:13 |
15 |
/usr/bin/phpunit:46 |
16 |
|
17 |
FAILURES! |
18 |
Tests: 1, Assertions: 0, Errors: 1. |
Para solucionar este problema, necesitamos pedirle al objeto ReflectionProperty que nos conceda acceso a las variables y a los métodos privados:
1 |
// Nettuts.php
|
2 |
|
3 |
class Nettuts { |
4 |
|
5 |
function publishNextArticle($editor) { |
6 |
var_dump($editor->getEditorName()); |
7 |
|
8 |
$reflector = new ReflectionClass($editor); |
9 |
$editorName = $reflector->getProperty('name'); |
10 |
$editorName->setAccessible(true); |
11 |
var_dump($editorName->getValue($editor)); |
12 |
}
|
13 |
|
14 |
}
|
El truco se hace llamando al método setAccessible() y pasando el valor true:
1 |
PHPUnit 3.6.11 by Sebastian Bergmann. |
2 |
|
3 |
.string(8) "John Doe" |
4 |
string(8) "John Doe" |
5 |
|
6 |
|
7 |
Time: 0 seconds, Memory: 2.25Mb |
8 |
|
9 |
OK (1 test, 0 assertions) |
Como puedes ver, hemos logrado leer la variable privada. La primera línea de salida es del método getEditorName() del objeto, y la segunda proviene de la reflexión. Pero, ¿qué pasa si se cambia el valor de una variable privada? Utiliza el método setValue():
1 |
// Nettuts.php
|
2 |
|
3 |
class Nettuts { |
4 |
|
5 |
function publishNextArticle($editor) { |
6 |
var_dump($editor->getEditorName()); |
7 |
|
8 |
$reflector = new ReflectionClass($editor); |
9 |
$editorName = $reflector->getProperty('name'); |
10 |
$editorName->setAccessible(true); |
11 |
$editorName->setValue($editor, 'Mark Twain'); |
12 |
var_dump($editorName->getValue($editor)); |
13 |
}
|
14 |
|
15 |
}
|
Y eso es todo. Este código cambia "John Doe" a "Mark Twain".
1 |
PHPUnit 3.6.11 by Sebastian Bergmann. |
2 |
|
3 |
.string(8) "John Doe" |
4 |
string(10) "Mark Twain" |
5 |
|
6 |
|
7 |
Time: 0 seconds, Memory: 2.25Mb |
8 |
|
9 |
OK (1 test, 0 assertions) |
Uso indirecto de la reflexión
Algunas de las funciones integradas de PHP usan indirectamente la reflexión, una de ellas es la función call_user_func().
La devolución de llamada
La función call_user_func() acepta una matriz: el primer elemento apunta a un objeto y el segundo al nombre de un método. Puedes proporcionar un parámetro opcional, que luego se pasa al método llamado. Por ejemplo:
1 |
// Nettuts.php
|
2 |
|
3 |
class Nettuts { |
4 |
|
5 |
function publishNextArticle($editor) { |
6 |
var_dump($editor->getEditorName()); |
7 |
|
8 |
$reflector = new ReflectionClass($editor); |
9 |
$editorName = $reflector->getProperty('name'); |
10 |
$editorName->setAccessible(true); |
11 |
$editorName->setValue($editor, 'Mark Twain'); |
12 |
var_dump($editorName->getValue($editor)); |
13 |
|
14 |
var_dump(call_user_func(array($editor, 'getEditorName'))); |
15 |
}
|
16 |
|
17 |
}
|
El siguiente resultado demuestra que el código recupera el valor adecuado:
1 |
PHPUnit 3.6.11 by Sebastian Bergmann. |
2 |
|
3 |
.string(8) "John Doe" |
4 |
string(10) "Mark Twain" |
5 |
string(10) "Mark Twain" |
6 |
|
7 |
|
8 |
Time: 0 seconds, Memory: 2.25Mb |
9 |
|
10 |
OK (1 test, 0 assertions) |
Uso del valor de una variable
Otro ejemplo de reflexión indirecta es llamar a un método por el valor contenido dentro de una variable, en lugar de llamarlo directamente. Por ejemplo:
1 |
// Nettuts.php
|
2 |
|
3 |
class Nettuts { |
4 |
|
5 |
function publishNextArticle($editor) { |
6 |
var_dump($editor->getEditorName()); |
7 |
|
8 |
$reflector = new ReflectionClass($editor); |
9 |
$editorName = $reflector->getProperty('name'); |
10 |
$editorName->setAccessible(true); |
11 |
$editorName->setValue($editor, 'Mark Twain'); |
12 |
var_dump($editorName->getValue($editor)); |
13 |
|
14 |
$methodName = 'getEditorName'; |
15 |
var_dump($editor->$methodName()); |
16 |
}
|
17 |
|
18 |
}
|
Este código genera la misma salida que el ejemplo anterior. PHP simplemente reemplaza la variable con la cadena que representa y llama al método. Incluso funciona cuando quieres crear objetos utilizando las variables para nombres de clases.
¿Cuándo debemos usar la reflexión?
Ya que dejamos atrás los detalles técnicos, ¿cuándo deberíamos aprovechar la reflexión? Estas son algunas posibilidades:
- La escritura dinámica es probablemente imposible sin reflexión.
- La programación orientada a aspectos escucha las llamadas a los métodos y coloca el código alrededor de los métodos, todo realizado con la reflexión.
- PHPUnit depende en gran medida de la reflexión, al igual que otros frameworks de simulación.
- Los frameworks web en general utilizan la reflexión para diferentes propósitos. Algunos lo utilizan para inicializar modelos, construir objetos para vistas y mucho más. Laravel hace un uso intensivo de la reflexión para ingresar dependencias.
- La metaprogramación, como nuestro último ejemplo, es la reflexión oculta.
- Los frameworks de análisis de código usan la reflexión para comprender el código.
Opiniones finales
Como con cualquier juguete genial, usa la reflexión, pero no abuses de ella. La reflexión es costosa cuando inspecciona muchos objetos y tiene el potencial de complicar la arquitectura y el diseño de tu proyecto. Te recomiendo que hagas uso de ella solo cuando realmente te brinde una ventaja, o cuando no tengas otra opción viable.
Personalmente, solo he utilizado la reflexión en algunos casos, más comúnmente cuando uso módulos de terceros que carecen de documentación. Me encuentro usando con frecuencia un código similar al último ejemplo. Es fácil llamar al método adecuado, cuando tu MVC responde con una variable que contiene "agregar" o "eliminar" valores.
¡Gracias por leer!



