Bienvenidos a otro artículo para aprender más cosas sobre la integración de symfony con drupal, en este caso hablaremos del evento del kernel «TERMINATE», un evento muy poco conocido que tremendamente útil, seguidme hasta el final y entenderéis porqué.

¿Qué son los eventos del kernel?

Antes de entrar en materia, dejadme refrescaros un poco qué es esto de los «eventos del kernel«.

Los eventos del kernel vienen dentro del componente «HttpKernel» de symfony, que básicamente es el encargado de convertir un «Request» (petición) en una «Response» (respuesta). Es decir, cuando accedemos a una web vía un enlace, este, nos devuelve la página html resultante que vemos.

Y para que esto pase, una clase de magia pasa por detrás de nuestra aplicación en la cual Symfony recoge la información de la petición y lo transforma en lo que queremos ver mediante una serie de controladores que ejecutan el código que nosotros hemos creado.

Aunque pueda parecer algo sacado de el libro oscuro del doctor Strange, la verdad que es un proceso bastante ordenado que gestiona symfony desde el  HttpKernel::handle() el cual se encarga de «guiar» la peticion y que siga el camino correcto.

Para ellos Symfony ha creado una serie de eventos sucesivos los cuales se ejecutan a medida que nuestra aplicación va avanzando.

Existen una serie de eventos, que explicaremos en otro artículo, que son:

  1. KernelEvents::REQUEST
  2. KernelEvents::CONTROLLER
  3. KernelEvents::CONTROLLER_ARGUMENTS
  4. KernelEvents::VIEW
  5. KernelEvents::RESPONSE
  6. KernelEvents::FINISH_REQUEST
  7. KernelEvents::TERMINATE
  8. KernelEvents::EXCEPTION

Prácticamente este es el orden en el que se van ejecutando, teniendo en cuenta que el «EXCEPTION» se ejecuta cuando hay un error.

¿Para qué sirve Kernel::TERMINATE?

Aunque no he explicado cada uno de los eventos, por los nombres, podemos hacernos una idea para que se usan, excepto, probablemente, el evento TERMINATE, y este es el que vamos a ver, porque aunque pueda parecer complejo, es super sencillo y super útil.

"El evento TERMINATE se genera una vez la respuesta a sido enviada, esto nos permite ejecutar acciones pesadas después de que el usuario haya recibido su respuesta sin afectar en la generación de la misma.(Solo funcionara si usamos PHP-FPM)"

Drupal usa este evento, para entre otros, escribir las caches o ejecutar el cron. Si estás familiarizado con Drupal 7 vendria a ser el remplazo al «hook_exit».

Bien, Y cómo y para que lo uso? os estaréis preguntando, imaginaos, que tenemos una aplicación, en la cual, cuando registramos a un usuario, tenemos que hacer una sincronización de datos con una aplicación de terceros, algo así como un CRM, pero como siempre, los del CRM han creado una herramienta tremendamente lenta.

Tener al usuario esperando 1, 2 o más minutos a que los datos se procesen es una locura, si eres, medianamente buen desarrollador, decidiras implementar un «job» y que se ejecute despues en algun momento, cuando se ejecute el cron sin molestar a nadie.

Pero qué pasaría si eso no fuera suficiente, dado que necesitáis los datos lo más pronto posible?, entonces puedes usar el evento terminate, para que una vez, procesado el registro en nuestra aplicación y mandando el mensaje de confirmación, esta pueda seguir con el proceso de sincronización con el CRM sin que afecte a la performance de la aplicación.

¿Como hacer esta clase de magia?

Antes de que desistas de seguir, pensando que esto va a ser tremendamente complicado os diré, que es completamente lo contrario, dejadme mostraros un ejemplo de codigo.

Como siempre vamos hacer un eventSubscriber y para eso podemos servirnos del drupal console con el siguiente comando

				
					$ drupal ges

// Welcome to the Drupal Event Subscriber generator
 Enter the module name [admin_toolbar]:
 > dummy_terminate

 Enter the service name [dummy_terminate.default]:
 > dummy_terminate.my_event

 Enter the class name [DefaultSubscriber]:
 > MyEventSubscriber

 
Type the event name or use keyup or keydown.
This is optional, press enter to continue

 Enter event name []:
 > kernel.terminate

 Enter the callback function name to handle event [kernelTerminate]:
 > mi_metodo_super_pesado

 Enter event name []:
 > 

 Do you want to load services from the container? (yes/no) [no]:
 > 

 Do you want proceed with the operation? (yes/no) [yes]:
 > 


  // cache:rebuild

 Rebuilding cache(s), wait a moment please.

 [OK] Done clearing cache(s). 
 
Generated or updated files
 Generation path: /var/www/html/web
 1 - modules/custom/dummy_terminate/src/EventSubscriber/MyEventSubscriber.php
 2 - modules/custom/dummy_terminate/dummy_terminate.services.yml
				
			

Esto nos generará varios archivos uno el .services donde básicamente definimos que tenemos un servicio que queremos ejecutar cuando se llame a los event_subscriber y el «MyEventSubscriber.php» donde pondemos toda la logica de nuestro codigo pesado.

				
					<?php

namespace Drupal\dummy_terminate\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Contracts\EventDispatcher\Event

/**
 * Class MyEventSubscriber.
 */
class MyEventSubscriber implements EventSubscriberInterface {

  /**
   * Constructs a new MyEventSubscriber object.
   */
  public function __construct() {

  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    $events['kernel.terminate'] = ['mi_metodo_super_pesado'];

    return $events;
  }

  /**
   * This method is called when the kernel.terminate is dispatched.
   *
   * @param \Symfony\Component\EventDispatcher\Event $event
   *   The dispatched event.
   */
  public function mi_metodo_super_pesado(Event $event) {
    \Drupal::messenger()->addMessage('Event kernel.terminate thrown by Subscriber in module dummy_terminate.', 'status', TRUE);
    // Aqui pondremos nuestro código super pesado
  }

}

				
			

Y voila, ya tenéis vuestro evento Terminate listo para usar!

Tened en cuenta que eso se ejecutará cada vez que se haga un request, por eso es importante confirmar que el proceso que vais a ejecutar, realmente se tiene que ejecutar, simplemente con un if al principio ya tendriais mas que suficiente, por ejemplo podrías pasar el request y revisar que estáis en la página correcta. 

Truco extra

Uno de los mayores inconvenientes, es su mayor ventaja, que se ejecuta en cada request y esto en ocasiones puede hacer que nuestro servidor colapse fácilmente con muchas peticiones pesadas al mismo tiempo.

Como siempre, esto ya le ha ocurrido a alguien, lo ha pensado otro y quien sabe quien lo ha solucionado y es que la solución perfecta y que creo que la deberías usar en complemento es, el servicio «\Drupal\Core\Lock\LockBackendInterface» tal y como lo usa drupal con el cron, este os permitirá bloquear una tarea mientras se está ejecutando y que no sea llamada de nuevo hasta que el proceso no haya terminado completamente, pero de esto hablaremos en el próximo artículo.

Conclusión

Este sistema es extremadamente útil y nos permite ampliar una cantidad de funcionalidades a nuestra aplicación sin que afecte a nuestra performance, esto hace que me guste mucho usarlo más que los típicos cron jobs, que ojo también tiene su función.

Os invito que lo probéis y que me conteis como fue vuestra experiencia.