SF2: Usando el EntityManager en un Listener

Hablando de Symfony2, el concepto de Listener es algo enrevesado mientras no se le pilla el truco. Parece que en un Controller no podemos definir un constructor que sea común a todos los métodos de la clase. No es posible. Si queremos compartir ciertos datos entre todos los métodos de una clase debemos optar por un Listener.

De acuerdo, pero, ¿qué es un Listener?. Sin entrar a una definición oficial, un Listener es algo así como un Hook o un Evento que se registra en ciertas circunstancias y ejecuta el código que contenga el ese instante. Haciendo una comparación con el mundo de Django digamos que podría ser un Middleware.

Para crear un Listener hay que registrarlo como un servicio un tanto especial, porque debemos decirle cuando se va a ejecutar, un ejemplo de registro de un Listener en services.yml podría ser el siguiente:

mi_listener:
    class: 'MiProyecto\MiBundle\Listener\MiListener'
    arguments: [@doctrine.orm.entity_manager]
    tags:
        - { name: kernel.event_listener, event: kernel.controller, method: onKernelController }

Resaltar que en arguments activamos el EntityManager para poder acceder a base de datos. El código de la clase MiListener sería parecido al que sigue:

namespace MiProyecto\MiBundle\Listener;

use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Doctrine\ORM\EntityManager;

class MiListener
{
    protected $em;

    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    }

    public function onKernelController(FilterControllerEvent $event) {
        // MiCodigo...
    }
}

Y así podríamos acceder a base de datos desde un Listener de Symfony2. Enrevesado, pero funcional.

Actualización: Para pasar una variable como "global" a todos los métodos del controlador desde el Listener podemos hacerlo de una forma sencilla tal que así:

$event->getRequest()->attributes->add(array('noticias' => $noticias));

De forma que, usando $container en vez del Entity Manager (así tenemos dos ejemplos en los que mirarnos), el Listener completo sería parecido a ésto:

namespace MiProyecto\MiBundle\Listener;

use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;

class MiListener
{
    protected $container;

    public function __construct($container)
    {
        $this->container = $container;
    }

    public function onKernelController(FilterControllerEvent $event)
    {
        $controller = $event->getController();

        /*
         * $controller passed can be either a class or a Closure. This is not usual in Symfony2 but it may happen.
         * If it is a class, it comes in array format
         */
        if (!is_array($controller)) {
            return;
        }

        if ($controller[0] instanceof \MiProyecto\MiBundle\Controller\DefaultController) {
            $repo = $this->container->get('doctrine')->getEntityManager()->getRepository('NoticiasBundle:Noticias');
            $noticias = $repo->getNoticias($this->container->get('twig')->getGlobals()['num_noticias']);

            $event->getRequest()->attributes->add(array('noticias' => $noticias));
        }
    }
}

About the author

Óscar
has doubledaddy super powers, father of Hugo and Nico, husband of Marta, *nix user, Djangonaut and open source passionate.