Paso 12: Escuchando eventos

5.0 version
Maintained

Escuchando eventos

Al diseño actual le falta un encabezado de navegación para poder volver a la página principal o cambiar de una conferencia a la siguiente.

Añadiendo un encabezado al sitio web

Cualquier cosa que deba mostrarse en todas las páginas web, como un encabezado, debe formar parte del diseño (layout) base principal:

patch_file
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
--- a/templates/base.html.twig
+++ b/templates/base.html.twig
@@ -6,6 +6,15 @@
         {% block stylesheets %}{% endblock %}
     </head>
     <body>
+        <header>
+            <h1><a href="{{ path('homepage') }}">Guestbook</a></h1>
+            <ul>
+            {% for conference in conferences %}
+                <li><a href="{{ path('conference', { id: conference.id }) }}">{{ conference }}</a></li>
+            {% endfor %}
+            </ul>
+            <hr />
+        </header>
         {% block body %}{% endblock %}
         {% block javascripts %}{% endblock %}
     </body>

Añadir este código al layout hace que todas las plantillas que lo extienden tengan que definir una variable conferences, que se debe crear y pasar desde sus controladores.

Como sólo tenemos dos controladores, puedes hacer lo siguiente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
--- a/src/Controller/ConferenceController.php
+++ b/src/Controller/ConferenceController.php
@@ -32,9 +32,10 @@ class ConferenceController extends AbstractController
     /**
      * @Route("/conference/{slug}", name="conference")
      */
-    public function show(Conference $conference, CommentRepository $commentRepository)
+    public function show(Conference $conference, CommentRepository $commentRepository, ConferenceRepository $conferenceRepository)
     {
         return new Response($this->twig->render('conference/show.html.twig', [
+            'conferences' => $conferenceRepository->findAll(),
             'conference' => $conference,
             'comments' => $commentRepository->findBy(['conference' => $conference], ['createdAt' => 'DESC']),
         ]));

Imagina tener que actualizar docenas de controladores. Y hacer lo mismo con todos los nuevos. Esto no es muy práctico. Debe haber una manera mejor.

Twig tiene la noción de variables globales. Una variable global está disponible en todas las plantillas renderizadas. Puedes definirlas en un archivo de configuración, pero sólo funciona para valores estáticos. Para añadir todas las conferencias como una variable global de Twig, vamos a crear un listener.

Descubriendo los eventos de Symfony

Symfony viene de serie con un componente para lanzar eventos. Un despachador (dispatcher) dispara ciertos eventos en momentos específicos que los oyentes (listeners) pueden escuchar. Los oyentes son ganchos que nos conectan con los procesos internos del framework.

Por ejemplo, algunos eventos te permiten interactuar con el ciclo de vida de las peticiones HTTP. Durante la gestión de una solicitud, el despachador envía eventos cuando se ha creado una solicitud, cuando un controlador está a punto de ser ejecutado, cuando una respuesta está lista para ser enviada o cuando se ha lanzado una excepción. Un oyente puede escuchar uno o más eventos y ejecutar alguna lógica basada en el contexto del evento.

Los eventos son puntos de extensión bien definidos que hacen que el framework sea más genérico y extensible. Muchos componentes de Symfony como Security, Messenger, Workflow o Mailer los utilizan continuamente.

Otro ejemplo de eventos y oyentes en acción es el ciclo de vida de un comando: puedes crear un oyente para que ejecute código antes de que se ejecute cualquier comando.

Cualquier paquete o bundle también puede enviar sus propios eventos para hacer que su código sea extensible.

Para evitar tener un archivo de configuración que describa qué eventos quiere escuchar un oyente, crea un suscriptor (subscriber). Un suscriptor es un oyente con un método estático getSubscribedEvents() que devuelve su configuración. Esto permite que los suscriptores se registren en el despachador de Symfony automáticamente.

Implementando un suscriptor

Ahora te sabes la canción de memoria, usa el bundle maker para generar un suscriptor:

1
$ symfony console make:subscriber TwigEventSubscriber

El comando te pregunta sobre el evento que deseas escuchar. Selecciona el evento Symfony\Component\HttpKernel\Event\ControllerEvent, que se envía justo antes de que se llame al controlador. Es el mejor momento para inyectar la variable conferences global para que Twig tenga acceso a ella cuando el controlador llame a la plantilla. Actualiza tu suscriptor de la siguiente manera:

patch_file
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
--- a/src/EventSubscriber/TwigEventSubscriber.php
+++ b/src/EventSubscriber/TwigEventSubscriber.php
@@ -2,14 +2,25 @@

 namespace App\EventSubscriber;

+use App\Repository\ConferenceRepository;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 use Symfony\Component\HttpKernel\Event\ControllerEvent;
+use Twig\Environment;

 class TwigEventSubscriber implements EventSubscriberInterface
 {
+    private $twig;
+    private $conferenceRepository;
+
+    public function __construct(Environment $twig, ConferenceRepository $conferenceRepository)
+    {
+        $this->twig = $twig;
+        $this->conferenceRepository = $conferenceRepository;
+    }
+
     public function onControllerEvent(ControllerEvent $event)
     {
-        // ...
+        $this->twig->addGlobal('conferences', $this->conferenceRepository->findAll());
     }

     public static function getSubscribedEvents()

Ahora, puedes añadir tantos controladores como quieras: la variable conferences siempre estará disponible en Twig.

Nota

Hablaremos de una alternativa mucho mejor en cuanto a rendimiento en un paso posterior.

Ordenando conferencias por año y ciudad

Ordenar la lista de conferencias por año puede facilitar la navegación. Podríamos crear un método personalizado para recuperar y clasificar todas las conferencias, pero en su lugar, vamos a anular la implementación predeterminada del método findAll() para asegurarnos de que la clasificación se aplica en todas partes:

patch_file
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
--- a/src/Repository/ConferenceRepository.php
+++ b/src/Repository/ConferenceRepository.php
@@ -19,6 +19,11 @@ class ConferenceRepository extends ServiceEntityRepository
         parent::__construct($registry, Conference::class);
     }

+    public function findAll()
+    {
+        return $this->findBy([], ['year' => 'ASC', 'city' => 'ASC']);
+    }
+
     // /**
     //  * @return Conference[] Returns an array of Conference objects
     //  */

Tras acabar este paso, el sitio web debe tener el siguiente aspecto:


  • « Previous Paso 11: Ramificando el código
  • Next » Paso 13: Gestionando el ciclo de vida de los objetos de Doctrine

This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.