knplabs/knp-menu-bundle
KnpMenuBundle integrates the KnpMenu PHP library with Symfony, making it easy to build feature-rich, flexible menus in your Symfony applications with minimal configuration.
Reality check first: KnpMenuBundle is a Symfony bundle (it registers services into Symfony's kernel/bundles.php, ships a DI extension, and leans on Twig helpers + the Symfony request stack). You cannot composer require knplabs/knp-menu-bundle into a Laravel app and get the bundle to boot — there is no bundle registration hook. What you can do is pull in the underlying standalone library (knplabs/knp-menu) and use it directly, then mirror the bundle's idioms in Laravel. Treat this repo as the reference implementation/architecture guide rather than a drop-in dependency.
Minimal path to first use (Symfony reference, so you understand the model before porting it):
composer require knplabs/knp-menu-bundle (requires PHP 8.2+ and a supported Symfony as of v3.8.0).config/bundles.php (Flex does this for you).Knp\Menu\BuilderInterface (or ContainerAwareInterface variants for autowiring).knp_menu.menu_builder — or, since v3.6, just add #[AsMenuBuilder] and let autoconfiguration handle the tag.{{ knp_menu_render('main', {'currentClass': 'active'}) }}.Where to look first:
docs/ in the bundle repo (the canonical usage docs live at symfony.com/bundles/KnpMenuBundle).MenuFactory, ItemInterface::addChild, setUri, setRoute, and MatcherInterface are where the real behavior lives.knp_menu.html.twig theme, to see exactly what HTML the renderer emits and which blocks you can override.First Laravel-appropriate use case: a permission-gated main navigation. Build the tree in a dedicated class (App\Menus\MainMenu), inject it via the container, match "active" items against the current route, and render a Blade partial. The shape of the problem is identical; only the plumbing differs.
One builder class per menu, never inline arrays. The bundle's core value is that menus are services, so they are testable and injectable. In Laravel, mirror this with App\Menus\MainMenuBuilder classes resolved from the container — not Menu::make() calls in a controller.
Shared attributes via a factory extension. The bundle autoconfigures ExtensionInterface implementations (tag knp_menu.factory_extension, autoconfigured since v3.4). Use this to apply a common CSS class, icon, or extras array across every item so individual builders stay DRY:
public function buildOptions(array $options): array
{
return array_merge(['extras' => ['icon' => null]], $options);
}
Gate visibility with voters, not if statements in builders. Voters (RouteVoter, CallbackVoter — auto-registered since v3.6, plus custom VoterInterface implementations) are the sanctioned place for "should this item be shown/active." Priorities let you order them. In Laravel the equivalent is a small MenuVoter service using Gate::allows() and $request->routeIs(), composed before the tree is rendered.
Active-state control is now configurable. v3.8.0 lets you disable the default RouteVoter via configuration. This matters in multi-section products where a bare route-name match highlights the wrong section; swap in a CallbackVoter keyed on a "section" attribute instead of route names.
Render through a thin adapter, not directly in views. Keep knp_menu_render/ListRenderer calls behind one Blade component or a MenuRenderer service. When you eventually change markup (mega-menu, ARIA roles), you change one template, not 40 files.
Programmatic access for non-render use cases. The MenuProviderInterface (autowirable since 2.2) lets services fetch a built menu — useful for sitemaps, breadcrumb generation, and JSON APIs feeding an SPA. This is where the bundle earns its keep beyond HTML.
Cache the built tree, not the rendered HTML, when items are static. Menu construction is cheap but route/permission-dependent; cache the item list per role/section and re-run the matcher per request. Note the matcher state reset (kernel.reset tag added in 3.6) — replicate this in Laravel if you run under Octane/Swoole.
composer require knplabs/knp-menu-bundle will install, but nothing boots it; you'll get confusing "class not found" for the extension and no Twig helpers. Use knplabs/knp-menu standalone and write your own service provider.current false positives are the #1 day-to-day complaint. A RouteVoter matching on route name will light up "Blog" for blog.show, blog.index, and any admin route prefixed similarly. Either disable the voter (new in 3.8.0) or attach an explicit extras.section and match on that.$container->get('app.menu.main') in a controller it fails; inject the builder or use the provider instead.kernel.reset tag exists precisely because the matcher caches current-item results. Under Octane/queue workers, reset it between jobs or scope it as a singleton per-request.knp_menu.html.twig into your app, you may be rendering a stale copy. Prefer extending the bundle's template and overriding only item_class, link_class, etc.ExtensionInterface (shared attributes), VoterInterface (visibility/active logic), MatcherInterface (custom matching), and MenuManipulator (move/slice/reorder a tree at runtime — handy for A/B nav experiments). In Laravel these map cleanly to a factory decorator, Gate-backed voters, a matcher service, and a small tree utility.How can I help you explore Laravel packages today?