lephare/menu-bundle
lephare/menu-bundle is a PHP package for defining, organizing, and rendering menus in your application. It supports nested menu items and configurable output, helping you build reusable navigation structures through a simple API and keep menu configuration consistent across your project.
Do not composer require this. lephare/menu-bundle is a Symfony bundle (Composer type symfony-bundle, a Bundle subclass, DI Extension/Configuration compiler passes, a Twig renderer extension). In a Laravel app there is no bundle registration pipeline, so even a forced VCS install resolves against stale symfony/* constraints and then fatal-errors at boot. "Getting started" here means reading, not installing.
Where to look first, in order:
*MenuBundle.php — confirms the Bundle subclass and shows which compiler passes and Twig extensions are registered. This tells you how much surface area is Symfony scaffolding versus real domain logic.DependencyInjection/Configuration.php + *Extension.php — the config tree and service wiring. This is your spec for what a config/menu.php and a MenuServiceProvider would need to look like if you port.Menu, MenuItem, tree assembly, active-detection, visibility rules) — the only part with reuse value.Extension — the rendering contract you'd re-express as a Blade component.First use case to evaluate against: a nested, ordered, route-aware primary navigation with permission-based visibility. That's the canonical thing this package models, and it's the thing you'll be rewriting. A MenuItem with children, a route/url used for active matching, and visibility gated on attributes/permissions is roughly 150–250 lines of Eloquent + a Blade component — read the bundle to confirm your model is no more complex than that, then build native.
If you're in a legacy Symfony app already consuming it, the first step is not learning the API — it's running composer why lephare/menu-bundle, checking the pinned symfony/* and PHP version constraints, and confirming the test suite (if any) passes on your target runtime. That determines whether you're maintaining it or migrating off it.
Treat the source as a reference implementation and map each bundle artifact to its Laravel equivalent. The day-to-day pattern is "read the domain layer, port the idea, discard the scaffolding."
Menu tree definition. The bundle's declarative tree (nested children, ordering) maps to either a config/menu.php array for static menus or an Eloquent model with parent_id + position for DB-editable menus. Keep the tree data separate from the rendering — that separation is the one architectural lesson worth keeping.
Active-item detection. The bundle auto-marks the item whose route matches the current request. In Laravel this is request()->routeIs($item->route) or url()->current() === $item->url, resolved once and passed into the renderer as a boolean per node. Don't recompute it inside the Blade loop.
Attribute/permission visibility. The bundle gates items on attributes and permissions. In Laravel that's a Gate::allows() check or a policy per item, evaluated during tree assembly — not during render. Assemble the visible tree first, then render, so caching stays simple.
Renderer as a pluggable boundary. The bundle exposes pluggable renderers + templates. Mirror this with a Blade component (<x-menu :tree="$tree" />) and a MenuRenderer contract so markup variants (top nav, sidebar, footer) are component props or separate views, not if ladders.
Registration pattern. The bundle collects items via compiler passes and service tags. The Laravel equivalent is a MenuRegistry singleton bound in a MenuServiceProvider, populated either explicitly (Menu::register('main', fn ($menu) => ...)) or by scanning tagged classes/attributes. Explicit registration is easier to debug and to test than tag scanning — prefer it.
Typical day-to-day workflow:
Caching pattern. Cache the unfiltered tree keyed on a menu version stamp, invalidate on write, then prune per-user in memory. Never cache per-user trees — the cardinality explodes and invalidation becomes impossible to reason about.
symfony-bundle. It will not boot under Laravel. If it lands in a shared dependency set by accident, you get a class-not-found / container resolution failure at startup — a total outage, not a broken menu.symfony/translation, doctrine/*) gets a CVE, nobody upstream bumps the constraint. Price adoption as "adopt + own indefinitely."composer.lock is a classic unresolvable-conflict trap.parent_id references are a stack overflow waiting to happen. Whatever model you adopt, enforce a depth cap or use a closure table with cycle prevention at write time. An uncached recursive walk on a legitimate-looking data cycle will take the request down.menu_item pointing at an unpublished or deleted page must be filtered at assembly, not discovered by a user clicking it.LICENSE file is legally ambiguous and can block a fork or closed-source redistribution. Resolve this before you vendor a single line.MenuRegistry (item collection), the renderer contract (markup), and the config tree (defaults + per-menu overrides). Everything else — Bundle subclass, DI extension, compiler passes, Twig extension — is Symfony glue with no Laravel analogue; delete it rather than translating it literally.How can I help you explore Laravel packages today?