Skip to content

Instantly share code, notes, and snippets.

@fsevestre
Last active March 26, 2017 16:58
Show Gist options
  • Save fsevestre/b378606c4fd23814278a to your computer and use it in GitHub Desktop.
Save fsevestre/b378606c4fd23814278a to your computer and use it in GitHub Desktop.
Symfony 2 breadcrumbs based on KnpMenuBundle (KnpMenu 2.1)

If you are using KnpMenu > 2.1, you can directly use this feature:

You can use it like this:

<ol class="breadcrumb">
{% for breadcrumb_item in knp_menu_get_breadcrumbs_array(knp_menu_get_current_item('main')) %}
    {% if not loop.last %}
    <li><a href="{{ breadcrumb_item.uri }}">{{ breadcrumb_item.label }}</a></li>
    {% else %}
    <li class="active">{{ breadcrumb_item.label }}</li>
    {% endif %}
{% endfor %}
</ol>
{{ knp_menu_render('main', {'template': ':Menu:breadcrumb.html.twig'}) }}
<ol class="breadcrumb">
{% for breadcrumb_item in knp_menu_get_breadcrumbs_array(knp_menu_get_current_item(item)) %}
{% if not loop.last %}
<li><a href="{{ breadcrumb_item.uri }}">{{ breadcrumb_item.label }}</a></li>
{% else %}
<li class="active">{{ breadcrumb_item.label }}</li>
{% endif %}
{% endfor %}
</ol>
<?php
namespace AppBundle\Menu;
use Knp\Menu\FactoryInterface;
class Builder
{
/**
* @var FactoryInterface
*/
private $factory;
/**
* @param FactoryInterface $factory
*/
public function __construct(FactoryInterface $factory)
{
$this->factory = $factory;
}
/**
* @param array $options
*
* @return \Knp\Menu\ItemInterface
*/
public function createMainMenu(array $options)
{
$menu = $this->factory->createItem('Dashboard', ['route' => 'dashboard']);
$menu->addChild('Foo', ['route' => 'foo_list']);
$menu['Foo']->addChild('Create Foo', ['route' => 'foo_create', 'display' => false]);
$menu->addChild('Bar', ['route' => 'bar_list']);
$menu['Bar']->addChild('Create Bar', ['route' => 'bar_create', 'display' => false]);
return $menu;
}
}
<?php
namespace AppBundle\Twig;
use Knp\Menu\ItemInterface;
use Knp\Menu\Twig\Helper;
use Knp\Menu\Matcher\MatcherInterface;
class MenuExtension extends \Twig_Extension
{
/**
* @var Helper
*/
private $helper;
/**
* @var MatcherInterface
*/
private $matcher;
/**
* @param Helper $helper
* @param MatcherInterface $matcher
*/
public function __construct(Helper $helper, MatcherInterface $matcher)
{
$this->helper = $helper;
$this->matcher = $matcher;
}
/**
* @return array
*/
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('knp_menu_get_current_item', array($this, 'getCurrentItem')),
);
}
/**
* Retrieves the current item.
*
* @param ItemInterface|string $menu
*
* @return ItemInterface
*/
public function getCurrentItem($menu)
{
$rootItem = $this->helper->get($menu);
$currentItem = $this->retrieveCurrentItem($rootItem);
if (null === $currentItem) {
$currentItem = $rootItem;
}
return $currentItem;
}
/**
* @param ItemInterface $item
*
* @return ItemInterface|null
*/
private function retrieveCurrentItem(ItemInterface $item)
{
$currentItem = null;
if ($this->matcher->isCurrent($item)) {
return $item;
}
if ($this->matcher->isAncestor($item)) {
foreach ($item->getChildren() as $child) {
$currentItem = $this->retrieveCurrentItem($child);
if (null !== $currentItem) {
break;
}
}
}
return $currentItem;
}
/**
* @return string
*/
public function getName()
{
return 'menu';
}
}
services:
app.menu.builder:
class: AppBundle\Menu\Builder
arguments:
- '@knp_menu.factory'
tags:
- { name: knp_menu.menu_builder, method: createMainMenu, alias: main }
app.twig.menu_extension:
class: AppBundle\Twig\MenuExtension
arguments:
- '@knp_menu.helper'
- '@knp_menu.matcher'
tags:
- { name: twig.extension }
@leperse
Copy link

leperse commented Feb 17, 2017

Thank you @fsevestre, but in my project I found an issue :

I have two routes :
@route("/blog/{currentPage}", name="post_list", requirements={"currentPage": "\d+"}) // Blog
@route("/blog/posts/{slug}", name="post_show") // Blog post title ex: Lorem ipsum

When I'm on the post_list page, it shows a breadcrumbs like : Home -> Blog and not : Home -> Blog -> Blog post title

To resolve the issue I changed the function :

private function retrieveCurrentItem(ItemInterface $item)

by :

    private function retrieveCurrentItem(ItemInterface $item)
    {
        $currentItem = null;

        if ($this->matchItem($item)) {
            return $item;
        }

        if ($this->matcher->isAncestor($item)) {
            foreach ($item->getChildren() as $child) {
                $currentItem = $this->retrieveCurrentItem($child);
                if (null !== $currentItem) {
                    break;
                }
            }
        }

        return $currentItem;
    }

    private function matchItem(ItemInterface $item)
    {
        if ($item->getUri() === $this->requestStack->getCurrentRequest()->getRequestUri()) {
            // URL's completely match
            return true;
        }

        return null;
    }

@fsevestre
Copy link
Author

fsevestre commented Feb 27, 2017

@leperse I don't think there is an issue with the code since I don't implement the way of checking if the item is the current one but use the knp_menu.matcher service provided by KnpMenu. You should implement your own matcher if the default one don't work as you expected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment