12

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

2
  • So, you need to include a variable defined in an EventListener (suppose Request?) to each any views you are calling from any controller? Commented Dec 3, 2013 at 11:27
  • @Touki yes that is correct Commented Dec 3, 2013 at 11:32

3 Answers 3

47

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);
    }
}
Sign up to request clarification or add additional context in comments.

4 Comments

I have a question with the twig extension case : the twig function will call getMyVar() which contains : return $this->listener->getMyVar();. But the listener doesn't have this method right ?
@AdrienG It's not given in my exampe, but this is just a getter to expose the computed var, which can be easily implemented
This does not work for anything computed within your typical onKernelResponse method; like the one you return in getSubscribedEvents(), even if you store the variable as a class member and try and retrieve it later.
Since Twig 1.21, for adding a global variable from a kernel.request listener, you need to update the global variable if it exists, instead you will have a deprecation notice.
2

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.

Comments

0

I don't know how to pass variables directly to view, but you can change response object, with kernel.response event listener. Take a look how symfony debug toolbar injects bottom bar, you could use similar technique.

https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php#L106

2 Comments

No, Symfony injects this toolbar by tweaking the Response, hence, TWIG has already been rendered
That's true, but it may work depending on OP needs. You can have placeholder in twig template and parse later it with listener.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.