Installation:
composer require knplabs/knp-menu-bundle
Ensure the bundle is enabled in config/bundles.php:
Knp\Bundle\MenuBundle\KnpMenuBundle::class => ['all' => true],
First Menu Creation:
Create a menu builder service (e.g., src/Menu/Builder.php):
use Knp\Menu\FactoryInterface;
use Knp\Menu\MenuItem;
class Builder
{
public function __construct(private FactoryInterface $factory)
{
}
public function createMainMenu(): MenuItem
{
$menu = $this->factory->createItem('root');
$menu->addChild('Home', ['route' => 'home']);
$menu->addChild('About', ['route' => 'about']);
return $menu;
}
}
Register the Builder:
Add to services.yaml:
services:
App\Menu\Builder:
arguments:
$factory: '@knp_menu.factory'
Render in Twig:
{{ knp_menu_render('main', {'menu': app.menu_builder.createMainMenu()}) }}
Use the matcher and provider services to auto-generate menus from routes or entities:
# config/packages/knp_menu.yaml
knp_menu:
matcher: ~ # Auto-match routes
provider: ~ # Auto-provide items from routes
Render in Twig:
{{ knp_menu_render('main') }}
Service-Based Builders:
Create reusable builders for different menu types (e.g., HeaderMenu, FooterMenu).
Inject dependencies like Router, Security, or Doctrine for dynamic logic:
public function __construct(
private FactoryInterface $factory,
private RouterInterface $router,
private Security $security
) {}
public function createAdminMenu(): MenuItem
{
$menu = $this->factory->createItem('admin');
if ($this->security->isGranted('ROLE_ADMIN')) {
$menu->addChild('Dashboard', ['route' => 'admin_dashboard']);
}
return $menu;
}
Event-Driven Menus:
Use Symfony events (e.g., KernelEvents::REQUEST) to dynamically update menus:
public function onKernelRequest(GetResponseEvent $event)
{
$menu = $this->factory->createItem('root');
$menu->addChild('Current User', ['route' => 'profile']);
$this->menuBuilder->setMenu('main', $menu);
}
Named Menus: Pass named menus to Twig for reuse:
$menu = $this->menuBuilder->createMainMenu();
return $this->render('home.html.twig', ['menu' => $menu]);
{{ knp_menu_render('menu', {'menu': menu}) }}
Menu Theming:
Override default Twig templates in templates/KnpMenu/:
{# templates/KnpMenu/menu.html.twig #}
<nav>
<ul>
{% for item in root.children %}
<li>{{ item.label }}
{% if item.children|length > 0 %}
{{ knp_menu_render('item', {'menu': item}) }}
{% endif %}
</li>
{% endfor %}
</ul>
</nav>
Route Matching:
Use the matcher to auto-generate menus from routes:
knp_menu:
matcher:
class: Knp\Menu\Matcher\RouteMatcher
options:
routes: ['home', 'about', 'contact']
Entity Providers: Fetch menu items from Doctrine entities:
knp_menu:
provider:
class: Knp\Menu\Provider\EntityProvider
options:
entity: App\Entity\MenuItem
property: path
Caching: Cache menus for performance:
knp_menu:
cache:
class: Knp\Menu\Cache\SymfonyCache
options:
namespace: knp_menu
Circular References:
Avoid circular references in menu items (e.g., A -> B -> A). KnpMenu throws a CircularReferenceException.
Matcher Conflicts:
If using both RouteMatcher and UriMatcher, ensure routes/URIs are unique to avoid duplicate items.
Twig Rendering Issues:
Ensure the KnpMenuBundle Twig extension is loaded. If not, manually register it:
// config/packages/twig.yaml
twig:
globals:
knp_menu_renderer: '@knp_menu.renderer.twig_extension'
Security Context:
Always check permissions in builders or use the Security component to avoid exposing sensitive menu items:
if (!$this->security->isGranted('ROLE_USER')) {
$menu->removeChild('profile');
}
Dump Menu Structure:
Use var_dump($menu->getChildren()) or Knp\Menu\Renderer\TwigRenderer::dump($menu) to inspect the menu tree.
Enable Debug Mode:
Set debug: true in knp_menu.yaml to log menu generation:
knp_menu:
debug: true
Check Cache: Clear the cache if menus aren’t updating:
php bin/console cache:clear
Custom Matchers:
Extend Knp\Menu\Matcher\MatcherInterface to match items against custom logic (e.g., database flags):
class CustomMatcher implements MatcherInterface
{
public function match(MenuItem $item, array $options)
{
// Custom logic here
return $item->getUri() === $options['expected_uri'];
}
}
Dynamic Child Addition:
Use addCallback to dynamically add children:
$menu->addChild('Dynamic', ['route' => 'dynamic'])
->setAttribute('callback', function (MenuItem $item) {
$item->addChild('Subitem', ['route' => 'subitem']);
});
Menu Events:
Listen to Knp\Menu\Event\MenuEvent to modify menus globally:
use Knp\Menu\Event\MenuEvent;
$eventDispatcher->addListener(MenuEvent::PRE_BUILD, function (MenuEvent $event) {
$event->setMenu($event->getMenu()->addChild('Global Item', ['route' => 'global']));
});
Provider vs. Matcher:
provider to generate menu items (e.g., from routes/entities).matcher to filter existing items (e.g., highlight active routes).Priority Order:
Matchers run in the order defined in knp_menu.yaml. Later matchers override earlier ones.
Twig Renderer Options:
Pass options to knp_menu_render for customization:
{{ knp_menu_render('main', {
'template': 'KnpMenu/menu.html.twig',
'depth': 2,
'currentUri': app.request.getUri()
}) }}
Symfony 6+ Changes:
If using Symfony 6+, ensure knp_menu.yaml is in config/packages/ and not config/knp_menu.yaml (deprecated).
How can I help you explore Laravel packages today?