Symfony: tutorial 13: enganchándonos a los eventos

2019-03-09 - Categorías: PHP / Symfony
Heart beat

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

Symfony 5 hola mundo inicial

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:

Symfony, haciendo subscriber con el maker de Symfony.

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/

4 respuestas a “Symfony: tutorial 13: enganchándonos a los eventos”

  1. Gerardo Montivero dice:

    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í

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

 

© 2024 JnjSite.com - MIT license

Sitio hecho con WordPress, diseño y programación del tema por Jnj.