Create a breadcrumbs menu with KnpMenuBundle

Share Button

Lire la version française

In this first post, I’ll explain how to make a breadcrumbs menu with Symfony and the great KnpMenuBundle. The documentation explains the basics and how to make a common menu, but a breadcrumbs menu has a particular behaviour.

I don’t pretend this implementation is the perfect one, I just want to explain what I did to help someone else to create a breadcrumbs menu more quickly than what I did.

Install the KnpMenuBundle

First, download and install the bundle. With Symfony 2.1.x, just add the code below to your composer.json file :

{
  // ...
  "knplabs/knp-menu-bundle":"dev-master",
}

Then run the command

php composer.phar update

Don’t forget to register the bundle in your app/appKernel.php file and check that the namespace has been correctly added by composer in the vendor/composer/autoload_namespaces.php.

Create the breadcrumbs menu

Now you can create the menu class. As explained in the documentation, there are two ways to create a menu : the “easy way” (the menu will be an object you will instanciate when needed) and the “flexible way” (create the menu as a service). I chose the first one for specific reasons (I needed to pass some options to the menu), but most of time, it’s a better choice to create the menu as a service. That’s I’m going to explain.

First you need to create the menu builder class:

<?php

  namespace Acme\MainBundle\Menu;

  use Knp\Menu\FactoryInterface;
  use Symfony\Component\HttpFoundation\Request;

  class MenuBuilder
  {
    private $factory;

    /**
     * @param FactoryInterface $factory
     */
    public function __construct(FactoryInterface $factory)
    {
        $this->factory = $factory;
    }

    public function createBreadcrumbMenu(Request $request)
    {
        $menu = $this->factory->createItem('root');
        // this item will always be displayed
        $menu->addChild('Home', array('route' => 'Acme_homepage'));

        // create the menu according to the route
        switch($request->get('_route')){
            case 'Acme_create_post':
                $menu
                    ->addChild('label.create.post')
                    ->setCurrent(true)
                    // setCurrent is use to add a "current" css class
                ;
            break;
            case 'Acme_list_post':
                $menu
                    ->addChild('label.list.post')
                    ->setCurrent(true)
                ;
            break;
            case 'Acme_view_post':
                $menu->addChild('label.list.post', array(
                    'route' => 'Acme_list_post'
                ));
                
                $menu
                    ->addChild('label.view.post')
                    ->setCurrent(true)
                    ->setLabel($request->get('label'))
                    // the "label" parameter must be passed in your controller
                    // with $request->attributes->set('label','My label');
                ;
            break;
            case 'Acme_add_comment_on_post':
                $menu->addChild('label.list.post', array(
                    'route' => 'Acme_list_post'
                ));
                
                $menu
                    ->addChild('label.view.post', array(
                        'route' => 'Acme_view_post',
                        'routeParameters' => array('slug' => $request->get('slug'))
                        /* the "slug" parameter is the placeholder in the route
                           Acme_add_comment_on_post. If no placeholder is used, then
                           you must use the $request->attributes->set() method
                        */
                    ))
                    ->setLabel($request->get('label'))
                ;
                $menu
                    ->addChild('label.add.comment')
                    ->setCurrent(true)
                ;                    
            break;            
        }

        return $menu;
    }
}

Register the services

Now you can register the menu as a service. You will also need to create a service for the menu builder.

# src/Acme/MainBundle/Resources/config/services.yml
services:
    # the menu builder service. Can be used for several menus
    acme_main.menu_builder:
        class: Acme\MainBundle\Menu\MenuBuilder
        arguments: ["@knp_menu.factory"]

    # your menu
    acme_main.menu.breadcrumb:
        class: Knp\Menu\MenuItem
        factory_service: acme_main.menu_builder # the menu builder service above
        factory_method: createBreadcrumbMenu # the method name to generate the menu
        arguments: ["@request"]
        scope: request
        tags:
            - { name: knp_menu.menu, alias: breadcrumb } # The alias is used in the template

Render the menu

Add the menu in your template

{{ knp_menu_render('breadcrumb',{'currentAsLink':false}) }}

The “currentAsLink” option can be removed if you want your current item to be a link. In this case, don’t forget to add the “route” argument in the “addChild” method in your MenuBuilder.

Finally, as you use keys as label in your menu (ex “label.view.post”), you must override the default template as explained in the bundle documentation.

And don’t forget to add the translations.

With a little CSS, here is the result :

Breadcrumbs menu

Share Button

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Protected by WP Anti Spam