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 }
@mickaelCOLLET
Copy link

unable to make a PR but to be compatible with sf2.8+ quote the service name

arguments:
- "@knp_menu.helper"
- "@knp_menu.matcher"

because : The reserved indicator "@" cannot start a plain scalar; need to quote the scalar

@fsevestre
Copy link
Author

Thanks @mickaelCOLLET, I updated the Gist.

@fsevestre
Copy link
Author

A PR on the KnpMenu repo is open : KnpLabs/KnpMenu#228

@fsevestre
Copy link
Author

PR merged, it will be available on 2.2

@m453h
Copy link

m453h commented Sep 20, 2016

This is a very good solution 😄 @fsevestre many thanks for saving my day...I think it should've been shipped with the KnpMenuBundle in the begining

@RafalJaworski
Copy link

RafalJaworski commented Jan 24, 2017

Doesn't work for me...Why this shows only root not matched child of Item...in MenuExtension.php in method retrieveCurrentItem in line 70 "if" statement always return only root...removing this and adding else{ return $item } in line 82 . Make this working

@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