SymfonyWorld Online 2020
100% online
30+ talks + workshops
Live + Replay watch talks later

Using events to allow a menu to be extended

3.0 version
Maintained

Using events to allow a menu to be extended

If you want to let different parts of your system hook into the building of your menu, a good way is to use an approach based on the Symfony EventDispatcher component.

Create the menu builder

Your menu builder will create the base menu item and then dispatch an event to allow other parts of your application to add more stuff to it.

 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
// src/Menu/MainBuilder.php

namespace App\Menu;

use App\Event\ConfigureMenuEvent;
use Knp\Menu\FactoryInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;

class MainBuilder implements ContainerAwareInterface
{
    use ContainerAwareTrait;

    public function build(FactoryInterface $factory)
    {
        $menu = $factory->createItem('root');

        $menu->addChild('Dashboard', ['route' => '_acp_dashboard']);

        $this->container->get('event_dispatcher')->dispatch(
            ConfigureMenuEvent::CONFIGURE,
            new ConfigureMenuEvent($factory, $menu)
        );

        return $menu;
    }
}

Note

This implementation assumes you use the BuilderAliasProvider (getting your menu as App:MainBuilder:build) but you could also define it as a service and inject the event_dispatcher service as a dependency.

Create the Event object

The event object allows to pass some data to the listener. In this case, it will hold the menu being created and the factory.

 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
30
31
32
33
34
35
36
37
// src/Event/ConfigureMenuEvent.php

namespace App\Event;

use Knp\Menu\FactoryInterface;
use Knp\Menu\ItemInterface;
use Symfony\Component\EventDispatcher\Event;

class ConfigureMenuEvent extends Event
{
    const CONFIGURE = 'app.menu_configure';

    private $factory;
    private $menu;

    public function __construct(FactoryInterface $factory, ItemInterface $menu)
    {
        $this->factory = $factory;
        $this->menu = $menu;
    }

    /**
     * @return \Knp\Menu\FactoryInterface
     */
    public function getFactory()
    {
        return $this->factory;
    }

    /**
     * @return \Knp\Menu\ItemInterface
     */
    public function getMenu()
    {
        return $this->menu;
    }
}

That’s it. Your builder now provides a hook. Let’s see how you can use it!

Create a listener

You can register as many listeners as you want for the event. Let’s add one.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// src/Acme/AdminBundle/EventListener/ConfigureMenuListener.php

namespace Acme\AdminBundle\EventListener;

use App\Event\ConfigureMenuEvent;

class ConfigureMenuListener
{
    public function __invoke(ConfigureMenuEvent $event)
    {
        $menu = $event->getMenu();

        $menu->addChild('Matches', ['route' => 'versus_rankedmatch_acp_matches_index']);
        $menu->addChild('Participants', ['route' => 'versus_rankedmatch_acp_participants_index']);
    }
}

You can now register the listener.

1
2
3
4
5
# config/services.yaml
services:
    app.admin_configure_menu_listener:
        class: Acme\AdminBundle\EventListener\ConfigureMenuListener
        tags: [kernel.event_listener]

You could also create your listener as a subscriber and use the kernel.event_subscriber tag, which does not have any additional attributes.

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