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 le 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 ser útil.
En los albores de la era de la programación, estaba el lenguaje ensamblador. Un programa escrito en conjunto reside en registros físicos dentro de la computadora. Su composición, métodos y valores se pueden inspeccionar en cualquier momento mediante la lectura de los registros. Aún más, puede modificar el programa mientras se está ejecutando simplemente modificando esos registros. Se requería cierto conocimiento íntimo sobre el programa en ejecución, pero era inherentemente reflexivo.
Como con cualquier juguete fresco, usa la reflexión, pero no abuses de él..
A medida que los lenguajes de programación de nivel superior (como C) aparecieron, esta reflectividad se desvaneció y desapareció. Posteriormente fue reintroducido con programación orientada a objetos..
Hoy en día, la mayoría de los lenguajes de programación pueden usar la reflexión. Los lenguajes tipificados estáticamente, como Java, tienen poco o ningún problema con la reflexión. Sin embargo, lo que me parece 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 la tipificación de pato sea imposible de implementar. Cuando envía un objeto a otro (un parámetro, por ejemplo), el objeto receptor no tiene forma de saber la estructura y el tipo de ese objeto. Todo lo que puede hacer es usar la reflexión para identificar los métodos que pueden y no pueden invocarse en el objeto recibido.
La reflexión es frecuente en PHP. De hecho, hay varias situaciones en las que puedes usarlo sin siquiera saberlo. Por ejemplo:
// Nettuts.php require_once 'Editor.php'; clase Nettuts function publishNextArticle () $ editor = new Editor ('John Doe'); $ editor-> setNextArticle ('135523'); $ editor-> publish ();
Y:
// Editor.php class Editor private $ name; public $ articleId; función __construir ($ nombre) $ this-> nombre = $ nombre; función pública setNextArticle ($ articleId) $ this-> articleId = $ articleId; public function publish () // publish logic goes here devuelve true;
En este código, tenemos una llamada directa a una variable inicializada localmente con un tipo conocido. Creando el editor en publicarNextArtículo ()
hace obvio que el $ editor
la variable es de tipo Editor
. No se necesita reflexión aquí, pero introduzcamos una nueva clase, llamada Gerente
:
// Manager.php require_once './Editor.php'; require_once './Nettuts.php'; class Manager function doJobFor (DateTime $ date) if ((new DateTime ()) -> getTimestamp ()> $ date-> getTimestamp ()) $ editor = new Editor ('John Doe'); $ nettuts = nuevos Nettuts (); $ nettuts-> publishNextArticle ($ editor);
A continuación, modifique Nettuts
, al igual que:
// Clase Nettuts.php Nettuts function publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ editor-> publish ();
Ahora, Nettuts
no tiene absolutamente ninguna relación con el Editor
clase. No incluye su archivo, no inicializa su clase y ni siquiera sabe que existe. Podría pasar un objeto de cualquier tipo a la publicarNextArtículo ()
Método y el código funcionaría.
Como se puede ver en este diagrama de clase., Nettuts
Solo tiene una relación directa con Gerente
. Gerente
lo crea, y por lo tanto, Gerente
depende de Nettuts
. Pero Nettuts
Ya no tiene ninguna relación con el Editor
clase, y Editor
solo esta relacionado con Gerente
.
En tiempo de ejecución, Nettuts
utiliza un Editor
objeto, por lo tanto el <setNextArticle ()
y publicar()
metodos.
Podemos hacer que PHP muestre los detalles de un objeto. Vamos a crear una prueba de PHPUnit para ayudarnos a ejercer nuestro código fácilmente:
// ReflectionTest.php require_once '… /Editor.php'; require_once '… /Nettuts.php'; la clase ReflectionTest extiende PHPUnit_Framework_TestCase function testItCanReflect () $ editor = new Editor ('John Doe'); $ tuts = new Nettuts (); $ tuts-> publishNextArticle ($ editor);
Ahora, agregue un var_dump ()
a Nettuts
:
// Clase Nettuts.php NetTuts función publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ editor-> publish (); var_dump (nueva ReflectionClass ($ editor));
Ejecute la prueba y observe cómo ocurre la magia en la salida:
PHPUnit 3.6.11 por Sebastian Bergmann… objeto (ReflectionClass) # 197 (1) ["name"] => string (6) "Editor" Tiempo: 0 segundos, Memoria: 2.25Mb OK (1 prueba, 0 aserciones)
Nuestra clase de reflexión tiene una nombre
propiedad establecida en el tipo original de $ editor
variable: Editor
, Pero eso no es mucha información. Qué pasa Editor
métodos de?
// Clase Nettuts.php Nettuts function publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ editor-> publish (); $ reflector = new ReflectionClass ($ editor); var_dump ($ reflector-> getMethods ());
En este código, asignamos la instancia de la clase de reflexión a la $ reflector
variable para que ahora podamos activar sus métodos. Clase de reflexión
expone un gran conjunto de métodos que puede utilizar para obtener la información de un objeto. Uno de estos métodos es getMethods ()
, que devuelve una matriz que contiene la información de cada método.
PHPUnit 3.6.11 por Sebastian Bergmann ... array (3) [0] => & object (ReflectionMethod) # 196 (2) ["name"] => string (11) "__construct" ["class"] => string (6) "Editor" [1] => & object (ReflectionMethod) # 195 (2) ["name"] => string (14) "setNextArticle" ["class"] => string (6) "Editor" [2] => & object (ReflectionMethod) # 194 (2) ["name"] => string (7) "publish" ["class"] => string (6) "Editor" Tiempo: 0 segundos , Memoria: 2.25Mb OK (1 prueba, 0 aserciones)
Otro método, obtenerPropiedades ()
, recupera las propiedades (¡incluso propiedades privadas!) del objeto:
PHPUnit 3.6.11 por Sebastian Bergmann ... array (2) [0] => & object (ReflectionProperty) # 196 (2) ["name"] => string (4) "name" ["class"] => string (6) "Editor" [1] => & object (ReflectionProperty) # 195 (2) ["name"] => string (9) "articleId" ["class"] => string (6) "Editor" Tiempo: 0 segundos, Memoria: 2.25Mb OK (1 prueba, 0 aserciones)
Los elementos en las matrices devueltos desde getMethod ()
y obtenerPropiedades ()
son de tipo Método de reflexión
y Propiedad de reflexión
, respectivamente; estos objetos son muy útiles:
// Clase Nettuts.php Nettuts function publishNextArticle ($ editor) $ editor-> setNextArticle ('135523'); $ editor-> publish (); // primera llamada a publish () $ reflector = new ReflectionClass ($ editor); $ publishMethod = $ reflector-> getMethod ('publish'); $ publishMethod-> invoke ($ editor); // segunda convocatoria para publicar ()
Aquí, usamos getMethod ()
para recuperar un solo método con el nombre de "publicar"; el resultado de lo cual es un Método de reflexión
objeto. Entonces, llamamos al invocar()
método, pasandolo $ editor
objeto, para ejecutar el editor. publicar()
método por segunda vez.
Este proceso fue simple en nuestro caso, porque ya teníamos un Editor
objeto para pasar a invocar()
. Podemos tener varios Editor
objetos en algunas circunstancias, dándonos el lujo de elegir qué objeto usar. En otras circunstancias, es posible que no tengamos ningún objeto con el que trabajar, en cuyo caso necesitaríamos obtener uno de Clase de reflexión
.
Vamos a modificar Editor
es publicar()
Método para demostrar la doble llamada:
// Editor.php class Editor [...] public function publish () // publish logic go here echo ("HERE \ n"); devuelve verdadero
Y la nueva salida:
PHPUnit 3.6.11 por Sebastian Bergmann ... AQUÍ AQUÍ Tiempo: 0 segundos, Memoria: 2.25Mb OK (1 prueba, 0 aserciones)
También podemos modificar el código en tiempo de ejecución. ¿Qué pasa con la modificación de una variable privada que no tiene setter público? Añadamos un método para Editor
que recupera el nombre del editor:
// Editor.php class Editor private $ name; public $ articleId; función __construir ($ nombre) $ this-> nombre = $ nombre; [...] función getEditorName () return $ this-> name;
Este nuevo método se llama, getEditorName ()
, y simplemente devuelve el valor desde el privado $ nombre
variable. los $ nombre
La variable 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 utilizando la reflexión. Primero deberías probar el enfoque más obvio:
// Clase Nettuts.php Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = new ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('nombre'); $ editorName-> getValue ($ editor);
A pesar de que esto produce el valor en el var_dump ()
línea, arroja un error al intentar recuperar el valor con reflexión:
PHPUnit 3.6.11 por Sebastian Bergmann. Estring (8) "John Doe" Tiempo: 0 segundos, Memoria: 2.50Mb Hubo 1 error: 1) ReflectionTest :: testItCanReflect ReflectionException: No se puede acceder al editor no público miembro :: nombre [...] / Reflejo en PHP / Fuente / NetTuts.php: 13 […] / Reflexión en PHP / Source / Tests / ReflectionTest.php: 13 / usr / bin / phpunit: 46 ¡FALLAS! Pruebas: 1, aserciones: 0, errores: 1.
Para solucionar este problema, necesitamos preguntar al Propiedad de reflexión
Objeto que nos permita acceder a las variables y métodos privados:
// Clase Nettuts.php Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = new ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('nombre'); $ editorName-> setAccessible (true); var_dump ($ editorName-> getValue ($ editor));
Vocación setAccessible ()
y pasando cierto
Hace el truco:
PHPUnit 3.6.11 por Sebastian Bergmann… string (8) "John Doe" string (8) "John Doe" Tiempo: 0 segundos, Memoria: 2.25Mb OK (1 prueba, 0 aserciones)
Como puedes ver, hemos logrado leer la variable privada. La primera línea de salida es del propio objeto. getEditorName ()
Método, y el segundo proviene de la reflexión. ¿Pero qué hay de cambiar el valor de una variable privada? Utilizar el valor ajustado()
método:
// Clase Nettuts.php Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = new ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('nombre'); $ editorName-> setAccessible (true); $ editorName-> setValue ($ editor, 'Mark Twain'); var_dump ($ editorName-> getValue ($ editor));
Y eso es. Este código cambia "John Doe" a "Mark Twain".
PHPUnit 3.6.11 por Sebastian Bergmann… string (8) "John Doe" string (10) "Mark Twain" Tiempo: 0 segundos, Memoria: 2.25Mb OK (1 prueba, 0 aserciones)
Algunas de las funciones integradas de PHP utilizan indirectamente la reflexión, siendo uno call_user_func ()
función.
los call_user_func ()
La función acepta una matriz: el primer elemento que apunta a un objeto y el segundo el nombre de un método. Puede suministrar un parámetro opcional, que luego se pasa al método llamado. Por ejemplo:
// Clase Nettuts.php Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = new ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('nombre'); $ editorName-> setAccessible (true); $ editorName-> setValue ($ editor, 'Mark Twain'); var_dump ($ editorName-> getValue ($ editor)); var_dump (call_user_func (array ($ editor, 'getEditorName')));
La siguiente salida demuestra que el código recupera el valor correcto:
PHPUnit 3.6.11 por Sebastian Bergmann… string (8) "John Doe" string (10) "Mark Twain" string (10) "Mark Twain" Tiempo: 0 segundos, Memoria: 2.25Mb OK (1 prueba, 0 aserciones)
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:
// Clase Nettuts.php Nettuts function publishNextArticle ($ editor) var_dump ($ editor-> getEditorName ()); $ reflector = new ReflectionClass ($ editor); $ editorName = $ reflector-> getProperty ('nombre'); $ editorName-> setAccessible (true); $ editorName-> setValue ($ editor, 'Mark Twain'); var_dump ($ editorName-> getValue ($ editor)); $ methodName = 'getEditorName'; var_dump ($ editor -> $ methodName ());
Este código produce 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 desea crear objetos utilizando variables para nombres de clase.
Ahora que hemos dejado atrás los detalles técnicos, ¿cuándo debemos aprovechar la reflexión? Aquí hay algunos escenarios:
Como con cualquier juguete fresco, usa la reflexión, pero no abuses de él. La reflexión es costosa cuando inspecciona muchos objetos y tiene el potencial de complicar la arquitectura y el diseño de su proyecto. Le recomiendo que lo use solo cuando en realidad le da una ventaja, o cuando no tiene otra opción viable.
Personalmente, solo he usado la reflexión en algunos casos, más comúnmente cuando uso módulos de terceros que carecen de documentación. Me encuentro frecuentemente usando código similar al último ejemplo. Es fácil llamar al método apropiado, cuando su MVC responde con una variable que contiene valores "agregar" o "eliminar".
Gracias por leer!