Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to send var to view from event listener in symfony2?

Tags:

php

symfony

I am trying to send var to view from event listener in symfony2 but I am stacked.

1) Is this possible?

2) What kernel event to use (kernel.view or kernel.request)?

3) Finally how to send some var to view?

My best guess is that I have to extend return from controller and then to let controller do his job.

I need to send some array of objects (entities).

like image 232
pregmatch Avatar asked Dec 03 '13 10:12

pregmatch


2 Answers

I see several ways to handle this.

Adding a global variable from a kernel.request listener

The idea is to add a global variable straight after the kernel.request event.

services.yml

services:
    class: Acme\FooBundle\Listener\MyListener
    arguments:
        - @twig
    tags:
        -
            name: kernel.event_listener
            event: kernel.request
            method: onKernelRequest

MyListener

class MyListener
{
    protected $twig;

    public function __construct(\Twig_Environment $twig)
    {
        $this->twig = $twig;
    }

    public function onKernelRequest(GetResponseEvent $event)
    {
        $myVar = 'foo'; // Process data

        $this->twig->addGlobal('myvar', $myVar);
    }
}

You can now use it at any time by doing

{{ myvar }}

From a kernel.view listener

First, you need to understand when kernel.view is called. It's only called when the return of the controller is not an instance of Response object.
That said, doing

// Acme/FooBundle/FooController#fooAction

return $this->render();

returns a Response object, so kernel.view is not called.

Defining controllers

The idea is to make all controller returns an array of data, just like @Template requirements.

// Acme/FooBundle/FooController#fooAction

return array(
    'template' => 'AcmeFooBundle:Foo:foo.html.twig',
    'data' => array(
        'entity' => $entity
    )
);

Defining the service

Since you already have your service definition, you just need to add some requirements in your service declaration.
You need the @templating service to render the data.
You need to set itself as a kernel.view listener

// Acme/FooBundle/Resources/config/services.yml

services:
    acme_foo.my_listener:
        class: Acme\FooBundle\Listener\MyListener
        arguments:
            - @templating
        tags:
            -
                name: kernel.event_listener
                event: kernel.request
                method: onKernelRequest
            -
                name: kernel.event_listener
                event: kernel.view
                method: onKernelView

Creating the service

// Acme/FooBundle/Listener/MyListener.php

use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;

class MyListener
{
    protected $templating;
    protected $myVar;

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

    public function getMyVar()
    {
        return $this->myVar;
    }

    public function onKernelRequest(GetResponseEvent $event)
    {
        $this->myVar = ""; // Process MyVar data
    }

    public function onKernelView(GetResponseForControllerResultEvent $event)
    {
        $result = $event->getControllerResult();

        if (null === $this->myVar || !isset($result['template']) || !isset($result['data'])) {
            return;
        }

        $data = array_merge($result['data'], array('myvar' => $this->myVar));
        $rendered = $this->templating->render($result['template'], $data);

        $event->setResponse(new Response($rendered));
    }
}

And there you are. The listener is creating a new response, adding your custom definition of myvar to any template rendered by him.


From a TWIG extension

An alternative is to create a TWIG extension. In the following example, I'm assuming the MyListener definition is the same as above.

Defining services

As per the documentation given above, you just have to create a simple extension class.

// Acme/FooBundle/Resources/config/services.yml

services:
    acme_foo.my_listener:
        class: Acme\FooBundle\Listener\MyListener
        tags:
            - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
    acme_foo.my_extension:
        class: Acme\FooBundle\Extension\MyExtension
        arguments:
            - @acme_foo.my_listener
        tags:
            - { name: twig.extension }

Defining the service

Just like in documentation, we'll create a simple function.

// Acme/FooBundle/Extension/MyExtension.php

use Acme\FooBundle\Listener\MyListener;

class MyExtension extends \Twig_Extension
{
    protected $listener;

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

    public function getName()
    {
        return 'my_extension';
    }

    public function getFunctions()
    {
        return array(
            'myvar' => new \Twig_Function_Method($this, 'getMyVar')
        );
    }

    public function getMyVar()
    {
        return $this->listener->getMyVar();
    }
}

Usage

Then you can use it in any view by doing

{{ myvar() }}

From a common controller

I don't like this idea, but this is an alternative. You just have to create a BaseController which will override the default render method.

// Acme/FooBundle/Controller/BaseController.php

abstract class BaseController extends Controller
{
    public function render($view, array $parameters = array(), Response $response = null)
    {
        $parameters = array_merge(
            $parameters,
            array(
                'myvar' => $this->get('my_listener')->getMyVar()
            )
        );

        return parent::render($view, $parameters, $response);
    }
}
like image 120
Touki Avatar answered Nov 19 '22 01:11

Touki


There's an alternative method here that I've had to do. I wanted to get some data, run it through json_encode(), then add that as a JavaScript variable to the response. Here's what I ended up doing.

I'm subscribing to kernel.response:

public static function getSubscribedEvents()
{
    return [
        KernelEvents::RESPONSE => 'onKernelResponse'
    ];
}

public function onKernelResponse(FilterResponseEvent $event)
{
    /** -- SNIP -- Cutting out how I get my serialised data **/
    $serialized = json_encode($data);

    /** Shove it into the response as some JS at the bottom **/
    $dom = new \DOMDocument;

    libxml_use_internal_errors(true);
    $dom->loadHTML($event->getResponse()->getContent());
    libxml_use_internal_errors(false);

    $node = $dom->createElement('script', "var data = $serialized;");

    $dom->getElementsByTagName('body')->item(0)->appendChild($node);

    $event->getResponse()->setContent($dom->saveHTML());
}

This is one way of doing it. Honestly, I don't like any of the methods described on this page. There should be a better way, but there isn't. This is what I'm using, though, and it works well. Just make sure you don't call your variable "data"; use something that won't be taken up elsewhere and preferably shove it in it's own (function() { } JS namespace.

like image 2
Jimbo Avatar answered Nov 19 '22 00:11

Jimbo