0

I want to create event listener that add some results of db query to all symfony actions

for example:

class BlogController extends Controller
{
    /**
     * @Route("/blog/")
     * @Template()
     */
    public function indexAction()
    {
        ....

        return array(
            'entries' => $posts
        );
    }
}

This controller is passing entries variable to the view, I want to create listener that take the returned value of all actions and inject another index to the returned array to be (for example)

array(
    'entries' => $posts,
    'categories' => $categories
);

so I can call the $categories var from any where in my application views

I hope my question is clear to you guys. Thanks in advance.

3
  • So you want categories to be available in your twig templates? Commented May 29, 2014 at 21:25
  • @Cerad Yes, that what I want. Commented May 29, 2014 at 21:34
  • I think that the answer to this question is in this other thread: stackoverflow.com/questions/26549806/…. Commented Mar 19, 2015 at 14:45

5 Answers 5

1

You should consider creating a global variable or twig extension to make categories available in your templates, you can't do that by using events (since the template is parsed inside the controller, not before/after it)

Sign up to request clarification or add additional context in comments.

2 Comments

It's not true the template is parsed inside the controller. It's parsed on kernel.view event AFTER the controller has been called. It's techinically possible to achieve what @Khalled Attia needs with events, regardless of wether that's the right solution or not.
@JakubZalas yeah, ok in this case it's parsed in the kernel.view event, but normally it would be parsed in the controller. :)
1

This approach, although valid and commonly used in some frameworks, is not very common in Symfony as it suits more MVC than HMVC architecture.

I would suggest you a different one with the same result:

Instead of adding parameter to every controller return, render another controller which returns just a subview of what you're trying to show. Simple example:

 // article/index.html.twig

 <div class="category-bar">{{ render(controller('MyVendorMyBundle:CategoryController:bar')) }}</div>

 <div class="article-list">
     {% for article in articles %>
         {# Print article here #}
     {% endfor %}
 </div>

// CategoryController

class CategoryController extends Controller
{
    /**
     * @Template
     */
    public function barAction()
    {
        return ['categories' => $this->fetchCategoriesSomehow()];
    }
}

So when you render your article list action, twig will fire a subrequest to render categories bar above it.

Furthermore, if you don't like making subrequests, nothing stops you from creating a twig extension service which would fetch categories and render template for you.

Comments

1

In most cases I would go with @Wouter J's suggestion and create a twig extension or a global variable.

However, what you want to do is actually possible (regardless if that's the right solution or not).

The @Template annotation has a vars attribute, which lets you to specify which atttributes from the request should be passed to the template:

/**
 * @ParamConverter("post", class="SensioBlogBundle:Post")
 * @Template("SensioBlogBundle:Post:show.html.twig", vars={"post"})
 */
public function showAction()
{
}

Note, that request attributes can be set by you:

$request->attributes->set('categories', []);

So, you could implement a listener which would set the categories attribute on the request and than configure the vars on the @Template annotation:

/**
 * @Template("SensioBlogBundle:Post:show.html.twig", vars={"categories"})
 */
public function showAction(Post $post)
{
}

Have a look at the TemplateListener from the SensioFrameworkExtraBundle for more insight. The listener defines template vars on kernel.controller and uses them to render the view on kernel.view.

You could avoid defining vars on the annotation if your listener was registered after the TemplateListener::onController(). It would have to add categories to the _template_vars request attribute.

3 Comments

Having to add stuff to each controller is what the poster is trying to avoid.
And I explained how to avoid it in three ways: twig extension, global (also twig extension) and custom view/controller listener. Have you read full answer? ;)
Another possibility would be to register a listener before the TemplateListener on the kernel.view event and then append the categories variable to the controller result using GetResponseForControllerResultEvent#setControllerResult().
0

Use Twig extension to create function that will return list of available categories

<?php

class CategoriesExtension extends \Twig_Extension
{
    public function getFunctions()
    {
        return [
            new \Twig_SimpleFunction('getCategories', [$this, 'getCategoriesList'])
        ];
    }

    /**
     * @return null|string
     */
    public function getCategoriesList()
    {
        return CategoryQuery::create()->find();
    }

    /**
     * Returns the name of the extension.
     *
     * @return string The extension name
     */
    public function getName()
    {
        return 'list_categories';
    }

}

You can pass parameter to function if You would like do some conditions on query.

Comments

-1

The trick is to get the twig service in your listener and then use addGlobal to add your categories

namespace Cerad\Bundle\CoreBundle\EventListener;

use Symfony\Component\DependencyInjection\ContainerAware;

use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class MyEventListener extends ContainerAware implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
    return array(
        KernelEvents::CONTROLLER => array(
            array('doCategories', -1100),
    );
}
public function doCategories(FilterControllerEvent $eventx)
{
    // Query your categories
    $categories = array('cat1','cat2');

    // Make them available to all twig templates
    $twig = $this->container->get('twig');
    $twig->addGlobal('categories',$categories);
}

# services.yml
cerad_core__my__event_listener:
    class:  '%cerad_core__my__event_listener__class%'
    calls:
        - [setContainer, ['@service_container']]
    tags:
        - { name: kernel.event_subscriber }

4 Comments

It'd be better to create a twig extension rather than a listener. Twig extension has getGlobals() method, which could return categories.
Nope. You got it backwards. Goal is to inject categories into the twig service. After which, it becomes available to all templates without any additional effort.
You don't need a listener to do that. Twig extension is sufficient as it can provide globals. It is more appropriate since globals are twig related. You did what a twig extension would do but in a wrong layer.
Nope. Business data should be pushed into twig, not pulled.

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.