Siguiendo con la puesta al día que llevo con Symfony y Flex, llegamos a los eventos. Engancharnos a los eventos es una forma de editar el comportamiento natural de los proyectos Symfony. En el proceso de una petición HTTP al servidor, en Symfony ocurren muchas cosas. Durante ese proceso, se van ejecutando una serie de tareas en orden. A la vez que se procesan esas tareas, se van disparando una serie de señales. Estan señales definen los eventos. Y a su vez podemos engancharnos a estas señales para personalizar el funcionamiento de la aplicación web.
Es decir, cuando un navegador hace una petición a un proyecto web Symfony, éste la recibe con el fichero public/index.php y comienza la ejecución de todo el framework junto con todo lo que hayamos construido, además de todos los componentes extra que le hayamos instalado. Ésta ejecución se hace en perfecta armonía, es una buena forma de comunicar componentes de Symfony entre sí, con los nuevos componentes que hayamos instalado, o para personalizar el comportamiento de nuestra aplicación.
Más información sobre todo esto en la documentación oficial:
https://symfony.com/doc/current/introduction/http_fundamentals.html
Por si quieres seguir el post viendo el código fuente en tu PC lo tienes también en GitHub:
https://github.com/jaimenj/symfony-starter-tutorials
Creando un proyecto desastre para probar
Como viene siendo habitual en esta serie de tutoriales sobre symfony, vamos a la línea de comandos y ejecutamos lo siguiente:
symfony new symfony-tutorial-13
cd symfony-tutorial-13
composer require --dev maker
composer require doctrine/annotations doctrine twig
Con esto ya tenemos un proyecto nuevo con el que vamos a hacer unas pruebas. Vamos a pintar una página en la home sencilla. Para esto podemos generar el controlador así:
php bin/console make:controller DefaultController
Editamos el controlador para que reciba la página de inicio del proyecto. Para esto editamos el fichero symfony-tutorial-13/src/Controller/DefaultController.php y le ponemos la línea siguiente:
* @Route("/", name="default")
Hecho esto, podemos arrancar el proyecto en local así:
symfony server:start
Y tenemos que ver una página como la siguiente en el navegador al visitar http://localhost:8000/..
Listando los eventos y enganches
Sin entrar en más detalles, los podemos ver en nuestro proyecto poniendo en línea de comandos:
php bin/console debug:event-dispatcher
..con lo que veremos un listado de todos los eventos disponibles, junto con todos los «enganches». Es decir, junto con el código que hay enganchado a estos eventos.
Pongo «enganches» porque hay que saber que este proceso se hace con los que se llaman listeners o subscribers. Es decir, haremos listeners o subscribers, para engancharnos a los eventos. Y con cada listener o subscriber tendremos cierta información para hacer lo que necesitemos. Esto es algo parecido a los Eventos de Magento, y a los Hooks de Worpdress, Prestashop o Drupal.
Por ejemplo, en el proyecto desastre de antes, tenemos que ver un listado de eventos disponibles como el de la imagen siguiente:
Conforme vayamos instalando más vendors, comenzaremos a tener más eventos disponibles..
Los eventos básicos de Symfony
Los eventos básicos de un proyecto Symfony son:
- console.command -> se lanza justo antes de ejecutar cualquier comando de consola.
- console.error -> se lanza cuando hay un error en comando.
- console.terminate -> justo después de terminar un comando.
- kernel.exception -> cuando se lanza una excepción en la ejecución de Symfony.
- kernel.request -> justo antes de empezar a procesar la petición, antes de elegir ruta o controlador correspondiente.
- kernel.finish_request -> cuando termina la ejecución de la petición, justo antes del controlador.
- kernel.controller -> este evento se dispara después de saber qué controlador se va a ejecutar pero antes de que se ejecute.
- kernel.controller_arguments -> este evento se dispara después de haber resuelto los argumentos del controlador, así se pueden tratar éstos argumentos.
- kernel.view -> cuando ya se ha ejecutado un controlador sin devolver una respuesta se lanza este evento.
- kernel.response -> este evento se lanza después de que una respuesta al navegador.
- kernel.terminate -> aquí ya se ha devuelto la respuesta al navegador o cliente que ha hecho la petición.
He tratado de poner los eventos más o menos en orden. Pero hay algunos que pueden cambiar de orden, si ves alguno que no debiera estar en ese lugar deja un mensaje 😉
Entonces, ¿hacemos listeners o subscribers?
Según veo en el generador de código, sólo disponemos del siguiente comando:
php bin/console make:subscriber
Por otro lado, según leo en la documentación, Symfony internamente usa subscribers. Parece ser que los subscribers son más reutilizables, o sencillos de manejar. Así que lo más sencillo es ponerse a juguetear con un proyecto de pruebas..
Enganchándonos a un evento
Lo que tenemos que saber primero es a qué evento queremos engancharnos. Entonces podemos ir a línea de comandos una vez más y ejecutamos el comando:
php bin/console debug:event-dispatcher
Veremos así los eventos disponibles. Si por ejemplo queremos engancharnos a la respuesta del kernel de Symfony, tendremos que engancharnos a ‘kernel.response’, otra forma es importar la clase de eventos del kernel y usar KernelEvents::RESPONSE para engancharnos. Vamos a crear un evento propio. Con la siguiente clase podemos hacer uno:
<?php
namespace App\Event;
use Symfony\Contracts\EventDispatcher\Event;
class SampleEvent extends Event
{
public const NAME = 'sample.event';
protected $data;
public function __construct($data)
{
$this->data = $data;
}
public function getData()
{
return $this->data;
}
}
Entonces, en línea de comandos, que será lo más sencillo para hacer el subscriber, podemos usar el generador de código:
php bin/console make:subscriber
..veremos algo como lo siguiente:
Escribimos a qué evento queremos suscribirnos y se generará el código del subscriber. Este subscriber finalmente lo he editado para que se enganche a dos eventos con dos funciones distintas podría ser el siguiente:
<?php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use App\Event\SampleEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class SampleSubscriber implements EventSubscriberInterface
{
public function onSampleEvent($event)
{
// ...
touch(__DIR__.'/test.sample.file1');
}
public function onKernelResponse($event)
{
// ...
touch(__DIR__.'/test.sample.file2');
}
public static function getSubscribedEvents()
{
return [
SampleEvent::NAME => 'onSampleEvent',
KernelEvents::RESPONSE => 'onKernelResponse',
];
}
}
También he generado otro subscriber que se llama NewSampleSubscriber para probar. Ahora podemos volver a lanzar el comando de bug de los eventos así:
php bin/console debug:event-dispatcher
..y tenemos que ver el evento personalizado con los dos subscriber que comento:
Disparando el evento custom
Vale, hasta aquí ya tenemos dos subscribers que se suscriben a un evento del kernel de Symfony, y a otro evento custom que hemos creado. El enganche al kernel de Symfony ya funcionará, pero el del evento personalizado no. Esto es así porque los eventos de Symfony ya se disparan, pero el SampleEvent sólo lo hemos definido, pero no disparado.
Para ahorrarte explicaciones innecesarias, vamos al grano y en el controlador haciendo así podremos dispararlo:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use App\Event\SampleEvent;
use App\EventSubscriber\SampleSubscriber;
class DefaultController extends AbstractController
{
/**
* @Route("/", name="default")
*/
public function index(EventDispatcherInterface $dispatcher)
{
$event = new SampleEvent("Something to store into the event object.");
$dispatcher->dispatch($event, SampleEvent::NAME);
return $this->render('default/index.html.twig', [
'controller_name' => 'DefaultController',
]);
}
}
Es decir, para lanzar el SampleEvent que hemos creado tenemos que hacer 3 cosas:
- Inyectar el dispatcher.
- Creamos el objeto evento que hayamos creado.
- Lo disparamos con el dispatcher.
Ahora, según este controlador, cada vez que tengamos una visita en la home /, se disparará al evento SampleEvent::NAME.
Terminando y referencias
Esto mismo puedes hacer con todo tipo de vendors que tengas instalados. Es decir, puedes personalizar el funcionamiento de Symfony o de los vendors que instales a base de engancharte a sus eventos.
Me remito a la documentación oficial para los más avanzados o para complementar:
https://symfony.com/doc/current/components/event_dispatcher.html
..aquí los códigos fuentes de este tutorial:
https://github.com/jaimenj/symfony-starter-tutorials/tree/master/symfony-tutorial-13
..y al siguiente tutorial de la serie:
https://jnjsite.com/symfony-tutorial-14-navegando-con-domcrawler-browserkit-y-cssselector/
Gracias por compartir.
Tengo el siguiente codigo en mi controller
$event = new DocumentoEvent($this->getUser());
$this->get(‘event_dispatcher’)->dispatch(DocumentoEvent::DOCUMENTO_UPLOAD_EVENT, $event);
y obtengo el siguiente error
Service «event_dispatcher» not found: even though it exists in the app’s container, the container inside «App\Controller\DocumentosController» is a smaller service locator that only knows about the «doctrine», «form.factory», «http_kernel», «parameter_bag», «request_stack», «router», «security.authorization_checker», «security.csrf.token_manager», «security.token_storage», «session» and «twig» services. Try using dependency injection instead.
Y no puedo salir de ahí
Hola Gerardo! Gracias por dejar un comentario. Justo me pillas rehaciendo este post 13 ya que lo que necesitas no estaba en el post, por ejemplo. Para engancharte a la subida de elementos en versiones anteriores a Symfony 5 se podía hacer con eventos de Doctrine. Pero esto está desaconsejado porque se lleva la lógica de dominio de su sitio. Más información aquí:
https://symfony.com/doc/current/controller/upload_file.html
..aquí está la documentación oficial completa en Symfony 5 sobre lo que estás haciendo:
https://symfony.com/doc/current/components/event_dispatcher.html#creating-and-dispatching-an-event
Trataré de actualizar el post lo antes posible. ¡Un saludo!
Mil gracias por tu tiempo.
No hay de que Gerardo ? Estoy viendo que el post necesita arreglos, sigo en ello.. He tratado de replicar lo que estas haciendo y acabo de subir a GitHub unos códigos que generan un evento SampleEvent, equivalente al DocumentoEvent::DOCUMENTO_UPLOAD_EVENT, para luego con un subscriber engancharnos a dicho evento:
https://github.com/jaimenj/symfony-starter-tutorials/blob/master/symfony-tutorial-13/src/Controller/DefaultController.php
He tenido que añadir el Subscriber manualmente al evento propio, mientras que si el evento es de los que vienen en Symfony, no hace falta, es raro pero funciona.. Cuando lo tenga claro porqué es así terminaré con el post.. ¡Un saludo!