Symfony 4: Kernel Events y casos de uso

Gerardo Fernández
Jun 13 · 5 min read

En este artículo quiero hablaros de dos casos de uso donde aplicar los Kernel Events de Symfony, es decir, los eventos que Symfony lanza a medida que procesa la Request entrante y genera la Response .

A modo de resumen os diré que existen 8 eventos que podemos escuchar desde un EventListener o un EventSubscriber aunque para este artículo solo emplearemos dos de ellos. Estos 8 eventos son los siguientes:

  • KernelEvents::REQUEST , que nos permite añadir información adicional a la Request o devolver directamente una Response (por ejemplo, si estamos implementando una capa de seguridad).
  • KernelEvents::CONTROLLER , donde podremos inicializar aquellas partes de nuestra aplicación que así lo requieran antes de que se ejecute el controlador. Además, también es posible modificar desde este evento el controlador que se va a ejecutar mediante el método setController del evento.
  • KernelEvents::CONTROLLER_ARGUMENTS desde el que podremos modificar los argumentos que posteriormente serán pasados a la acción del controlador.
  • KernelEvents::VIEW . Aunque generalmente estamos acostumbrados a que los controladores devuelvan una Response , no tiene siempre por qué ser así. En el caso de que un controlador devuelva otro tipo de valor (siempre que no sea null ) desde este evento podremos modificarlo para devolver una Response .
  • KernelEvents::RESPONSE , con el que podremos modificar el objeto Response que será devuelto.
  • KernelEvents::FINISH_REQUEST , que se ejecuta una vez que la Request entrante ha sido procesada completamente.
  • KernelEvents::TERMINATE , evento que se genera una vez que la respuesta ha sido enviada al usuario (siempre que estemos empleando un servidor con PHP-FPM) y que nos permitirá realizar operaciones complejas que habremos “retrasado” hasta ese momento de cara a devolver la Response lo más rápido posible.
  • KernelEvents::EXCEPTION donde podremos escuchar las excepciones generadas por la aplicación y actuar en consecuencia si así lo necesitásemos.

Una vez que ya conocemos todos los eventos que genera el Kernel de Symfony, vamos a ver 3 casos de uso para ellos que creo que pueden ser interesantes.

Establecer un argumento para todos los controladores

Esto, que podría sonar complicado de conseguir, es bastante sencillo si empleamos los Kernel Events y conocemos la manera en que Symfony asocia los parámetros de la Request a los argumentos de la función invocada en el controlador asociado a la ruta.

Grosso modo, lo que hace Symfony es obtener mediante reflection el nombre de los argumentos de la función a invocar e intenta asociarles su valor en 3 pasos:

  1. Si la Request contiene un atributo con ese nombre, asocia el valor de dicho atributo al argumento de la función.
  2. Si es un argumento tipado como Request , directamente le asocia el objeto Request .
  3. Si se trata de un tipo variadic comprueba si dentro de los atributos de la Request existe un array que pueda asociarlo.

Para este ejemplo lo que nos interesa es el punto 1, pues si por ejemplo nuestro controlador tiene una acción con esta forma:

public function someAction(CustomClass $object, Request $request) {

bastará con que la Request contenga un atributo llamado object para que Symfony lo asocie directamente. ¿Y como conseguimos eso? Mediante el KernelEvent::REQUEST :

En esta clase lo que hago es:

  1. Escuchar mediante un EventSubscriber el evento KernelEvents::REQUEST
  2. Descartar el evento si no se trata la Request principal (por ejemplo, el método forward de la clase Controller genera sub-requests )
  3. Calcular mediante el servicio customClassManager el objeto que queramos o el valor a partir de la Request
  4. Establecer un atributo en la Request llamado object con el valor del objeto calculado de modo que siempre que un controlador tenga en una acción un argumento llamado object , Symfony lo asociará directamente a este valor.

Como apunte final, me gustaría reseñar que este mecanismo es el que usa el componente ParamConverter para realizar directamente la carga de entidades o la creación de objetos DateTime a partir de fechas en la Request :

Establecer automáticamente un valor en la Response

En este caso el proceso es bastante sencillo. Declaramos un EventSubscriber asociado al evento KernelEvents::RESPONSE y en el método onResponse estableceremos la cookie que queramos en las cabeceras del objeto Response que posee el evento FilterResponseEvent .

Realizar tareas una vez que se ha enviado la Response al usuario

Una solución sería delegar esta tarea a una cola Rabbit, pero por desgracia no siempre disponemos de los recursos para montar este tipo de infraestructuras.

Sin embargo, Symfony nos provee de una alternativa que nos servirá en la mayoría de las ocasiones: el evento Kernel::TERMINATE el cual se ejecuta una vez que la respuesta se ha enviado al usuario:

// sends the headers and echoes the content
$response->send();

// triggers the kernel.terminate event
$kernel->terminate($request, $response);

Tal y como dice la documentación, el Kernel emplea el método fastcgi_finish_request para poder ejecutar código una vez que se ha devuelto al cliente la respuesta. Sin embargo, solo arquitecturas bajo PHP-FPM soportan ese funcionamiento, por lo que el resto seguirán esperando a que se complete la tarea antes de devolver la respuesta al usuario.

Dicho esto, para ejecutar este tipo de tareas nos bastará con un nuevo EventSubscriber con el siguiente aspecto:

Como habréis visto, el Kernel de Symfony nos da una forma bastante sencilla de interactuar con el proceso de generación de la Response en cualquier momento, por lo que si queréis profundizar más podéis ir a la documentación del resto de eventos:

Gerardo Fernández

Written by

Mejor amigo de Simba y desarrollador 100% fullstack