Symfony2: Cómo funciona la anotación @Template

Quería hacer una serie de artículos donde vayamos viendo cómo funcionan internamente algunas partes del framework. Ver el código de otros es enriquecedor, ves cómo otros han resuelto problemas que tú has tenido o que puede que tengas. En el artículo de hoy veremos cómo funciona internamente la anotación @Template del SensioFrameworkExtraBundle. Así que es posible que haya alguna parte un poco más dura.

Las anotaciones se parsean mediante Doctrine Annotations, una librería que nos permite mapear información en las clases, atributos o métodos mediante anotaciones.

Esta anotación nos permite indicar la plantilla que se va a usar en una acción, este es el ejemplo típico:

En este caso se  rendererizará la plantilla SensioBlogBundle:Post:show.html.twig pasándole el parámetro post.

A modo resumen lo que pasa es esto:

  • Cuando se lance el evento KernelEvents::CONTROLLER (se lanza cuando ya se sabe el controlador y la acción que se va a ejecutar) se leerá la anotación @Template y se guardarán esos valores.
  • Cuando se lance el evento KernelEvents::VIEW (se lanza cuando el controller devuelve un valor que no es un objeto Response) se leerán los valores que se han procesado previamente del @Template y en base a eso se creará el objeto Response.

Vamos a ver esto cómo funciona internamente. La idea de esto no es fijarse mucho en la implementación pura y dura sino más bien tener una visión más general.

Anotación @Template

La primera pista que tenemos es que en un controlador para usar esta anotación tenemos que usar el siguiente use:

Por lo tanto vamos a ver la clase que se encarga de esta anotación, para no hacerlo más largo dejamos los métodos más importantes:

Como podemos ver contiene atributos para almacenar los parámetros que se le pasan cuando usamos la anotación (además de los setters y getters), los dos últimos métodos son obligados por la interfaz que implementa la clase de la que extiende.

Lo importante es la anotación @Annotation que indica que va a ser una anotación, se puede ver más información de esto en la documentación de Doctrine.

Esta clase Template extiende de ConfigurationAnnotation que sólo contiene el constructor:

Por lo que vemos, cuando se le pase el array de opciones llamará a los respectivos setters.

Ahora que ya tenemos la clase que almacena la información que se le pasa a través de la anotación, nos falta la lógica de leer la anotación y actuar consecuentemente.

Configuración del bundle

En la documentación vemos la siguiente configuración:

Esto significa que dentro del directorio DependencyInjection del bundle habrá una información interesante, vamos a ver la extensión que se encargará de leer esta configuración, el fichero SensioFrameworkExtraExtension. Destacamos las líneas que parecen interesantes:

  • Línea 7: Carga el fichero services.xml donde declara el servicio sensio_framework_extra.view.guesser, no hace falta mirar la implementación, tiene pinta que será el encargado de cuando no indicamos un template lo intente buscar por el nombre de la acción.
  • Líneas 19-25: Si view está a true, como hemos visto en la configuración, carga el fichero view.xml que crea el subscriber sensio_framework_extra.view.listener que será el encargado de leer la configuración y hacer lo oportuno, luego lo vemos.
  • Líneas 31-42: Finalmente carga el fichero annotations.xml antes que cualquier otro. En este fichero se registra el subscriber sensio_framework_extra.controller.listener que se encargará de parsear las anotaciones y crear una configuración para los demás listeners, ahora lo veremos.


Listeners

Llegamos a la parte final, aquí sí que veremos el código un poco más en profundidad, vamos a ver estos listeners que se han registrado qué hacen. Antes de la implementación, esto es lo que pasa:

  • Se lanza el evento KernelEvents::CONTROLLER (cuando se sabe qué Controller y qué acción se va a ejecutar)
    • Primero se leen las anotaciones de la acción que se va a ejecutar y se guardan los datos.
    • El TemplateListener lee estos datos y en base a eso datos define unos nuevos que será los que se procesarán en el evento KernelEvents::VIEW.
  • Se lanza el evento KernelEvents::VIEW
    • Se leen los datos que se han guardado y se crea el objeto Response.

Como hemos visto, el primero listener es el que se declara en annotations.xml, el ControllerListener, este subscriber básicamente se encargará de leer las anotaciones y guardar esa información en la propiedad attributes de la petición, esta propiedad se usa para guardar información especial que usa la aplicación internamente. Esta información será usada posteriormente por los listener específicos de cada anotación:

Como vemos está suscrito al evento KernelEvents::CONTROLLER que se ejecuta cuando se ha encontrado el controlador que se va a ejecutar para una petición. Vemos también que se le inyecta el lector de anotaciones de Doctrine.

En la línea 50 y 51 se leen las anotaciones de la clase y de la acción que se está ejecutando y se guardan con una clave especial compuesta de un guión bajo y el alias de la anotación, es decir, en este punto tendremos en la variable $methodConfigurations un array:

Como hemos visto en las líneas anteriores se leen las anotaciones de la clase y de la acción, en el siguiente foreach se unifican, en el caso que se repita en la clase y en la acción, tiene prioridad la acción, ya que es más específico.

Finalmente se guardan en la propiedad attributes de la petición que se está manejando (líneas 72-75). Ya tenemos la información que necesitamos, ahora falta leerla para devolver la respuesta.

Vamos a ver el TemplateListener que será el encargado de, en base a lo que se ha añadido en la anotación, devolver una respuesta:

Lo primero que vemos es que este subscriber está suscrito a dos eventos, por una parte KernelEvents::CONTROLLER con prioridad -128, por lo que se ejecutará después del ControllerListener que hemos visto antes. Por otro lado está suscrito al evento KernelEvents::VIEW, este evento se produce cuando el controller devuelve algo que no es una instancia de Response.

Primero se ejecutará onKernelController, este método hace lo siguiente:

  • Líneas 47-49: Comprueba que existen los datos que se han almacenado en el listener anterior.
  • Líneas 51-54: Si no se ha indicado una plantilla la busca de acuerdo al nombre de la acción (usando el servicio sensio_framework_extra.view.guesser).
  • Líneas 56-70: En base a los datos que se han leído de la anotación en el Listener anterior, se definen nuevos parámetros en la propiedad attributes de la petición que servirán para generar la respuesta cuando se lance el evento KernelEvents::VIEW.

Finalmente el método onKernelView (ejecutado cuando se lanza el evento KernelEvents::VIEW) comprueba los parámetros que devuelve el controller y según los parámetros de la anotación actúa en consecuencia. En la línea 107 vemos como se renderiza la plantilla y crea un objeto Response.

Las demás anotaciónes del FrameworkExtraBundle funcionan muy similar.

Felicidades si has leído hasta aquí! Esto ha sido todo, espero que no haya quedado muy denso y más o menos se haya entendido. Lo que se pretende también con este tipo de artículos es, por una parte que la gente que no se ha metido nunca en el código de Symfony2 lo haga y por otra parte ver el código como una fuente de consulta a la hora de desarrollar.