Reflexión en PHP

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.


Una pequeña historia

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.


Un ejemplo simple

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 <> y el signo de interrogación. En tiempo de ejecución, PHP inspecciona el objeto recibido y verifica que implementa el setNextArticle () y publicar() metodos.

Objeto de información del miembro

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 Editormé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 Editores 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)

Manipulación de datos de instancia

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)

Uso indirecto de la reflexión

Algunas de las funciones integradas de PHP utilizan indirectamente la reflexión, siendo uno call_user_func () función.

La devolución de llamada

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)

Usando el 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:

 // 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.


¿Cuándo debemos usar la reflexión??

Ahora que hemos dejado atrás los detalles técnicos, ¿cuándo debemos aprovechar la reflexión? Aquí hay algunos escenarios:

  • Tipificación dinámica Probablemente sea imposible sin reflexión..
  • Programación Orientada a Aspectos escucha las llamadas a los métodos y coloca el código alrededor de los métodos, todo esto realizado con reflexión.
  • PHPUnit Depende mucho de la reflexión, al igual que otros marcos burlones..
  • Frameworks web En general, utilizar la reflexión para diferentes propósitos. Algunos lo usan para inicializar modelos, construir objetos para vistas y más. Laravel hace un uso intensivo de la reflexión para inyectar dependencias..
  • Metaprogramacion, Como nuestro último ejemplo, es el reflejo oculto..
  • Marcos de análisis de código Usa la reflexión para entender tu código..

Pensamientos finales

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!