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.
Bottom line for a TPM: this is not an adoptable dependency. It is a Symfony bundle with no maintenance, no users, and no security path, being evaluated against (presumably) a Laravel target. The correct framing is reference material for a small in-house build, not integration work. The decision to make is not "how do we install this" but "do we need to build menu/navigation at all, and if so, what is the cheapest maintained path."
Architecture fit — poor, and mismatched at the framework level. Despite being catalogued as a Laravel/PHP package, the artifact is a Symfony bundle: a Bundle subclass registered in config/bundles.php, a DependencyInjection/*Extension.php with a Configuration tree, compiler passes, and a Twig rendering extension. Laravel has no bundle registration mechanism, no Bundle base class, and no DI compiler pipeline for those passes to hook into. The Symfony-specific scaffolding is the majority of the package's surface area and is inert in a Laravel app. What is potentially reusable is narrow: the menu tree data model, ordering, active-item route matching, permission/attribute visibility rules, and renderer abstraction. That is a few hundred lines of domain logic wrapped in framework glue we would delete.
Integration feasibility — low as a dependency, moderate as prior art. There is no viable composer require path. An archived bundle almost certainly pins symfony/* constraints from a 3.x/4.x era and will not resolve on a current Symfony or PHP 8.2+ runtime; forcing it via a VCS repository entry in a Laravel app fails at boot because Laravel never instantiates Bundle classes. Even in a hypothetical Symfony host, this is a fork-and-maintain exercise, not an install. As source material it is readable — the domain model is small enough that a spike can inventory it in a day or two — which is precisely why it should be read rather than depended on.
Technical risk — high and entirely non-technical in origin. Three converging signals: zero stars, zero dependents, archived. That combination means no downstream users to surface bugs, no issue traffic to mine for known failure modes, no maintainer with any obligation to respond, no SECURITY.md, no CVE triage, no PHP-version drift plan. Adopting it converts a commoditized problem into a permanent, unfunded fork obligation. The risk is not complexity — menu trees are well-trodden — it is the false economy of unmaintained code and the resulting bus factor.
Key questions to resolve before committing engineering time:
spatie/laravel-menu for Laravel, knp-menu for Symfony) with a custom data source — not this repo.Stack fit. Assume Laravel 10/11 on PHP 8.2+. There is no installable path. Do not add it to a monorepo's shared dependency set, do not attempt composer require, and do not let it enter a lockfile. Any "integration" is a port, and must be scoped and estimated as one — or, more likely, scoped as a small native build informed by this source.
Recommended path: reference implementation, then build native. Vendor a read-only copy into a scratch branch (not vendor/) and map what exists to what a Laravel implementation would need:
| Bundle artifact | Laravel equivalent |
|---|---|
MenuBundle extends Bundle |
delete; register a MenuServiceProvider |
DependencyInjection/Configuration.php |
config/menu.php + mergeConfigFrom |
*Extension.php service definitions |
bind/singleton in the provider, or attribute-scanned services |
| Compiler passes (tagged menu-item collection) | a MenuRegistry singleton with explicit or tagged-interface registration |
| Twig extension for render | Blade component <x-menu :tree="$tree" /> or a @menu() directive |
| Doctrine entities for stored menus | Eloquent models; parent_id + position, or a closure-table package |
If the read reveals the bundle is a thin wrapper over knp-menu with no persistence layer, the port collapses to "use a maintained Laravel navigation package" and the fork never opens. That is the most likely outcome and should be stated up front so nobody is surprised when the spike ends in a no.
Migration path, if a legacy Symfony consumer exists. Strangler pattern: stand up the Laravel menu service behind an interface, run it in parallel with the Symfony consumer, and diff rendered HTML against a fixture set covering empty, single-level, deep-nested, multi-locale, permission-filtered, and unpublished-target cases. Parity across those fixtures is the cutover gate; keep the Symfony service live until parity holds for one full release cycle.
Compatibility. Pin two dimensions explicitly, before estimating. First, PHP ceiling: Symfony 3.x/4.x-era code frequently uses constructs removed in PHP 8 (dynamic properties, by-reference each, curly-brace offsets) and will need mechanical fixes even for a read-only port. Second, transitive surface: enumerate the bundle's dependency tree and diff it against the current composer.lock. A stale pin on symfony/translation or doctrine/* is the classic source of unresolvable conflicts, and that enumeration is a half-day task that can kill the effort early.
Sequencing.
Maintenance — the burden transfers to us permanently. An archived upstream means every future change is ours: the annual PHP-major bump, the framework-major bump, and security triage nobody else is doing. The port is the cheap part; the decade of ownership is the cost. Assign a named owning team before the fork exists. A fork with no owner is worse than a dependency because it looks owned on the inventory.
Support. No upstream channel, no maintainer to escalate to, no community to search. On-call runs through whoever did the port. Mitigate by writing the documentation during the port, not after — once the person who read the Symfony code leaves, the mapping table above is the only thing standing between the team and a rewrite.
Scaling. Menu data is small and read-mostly; the load profile is dominated by render frequency, not tree size. Two real risks: rebuilding the tree on every request, and N+1 queries when walking children. Mitigate with a full-tree cache keyed on a menu version stamp and invalidated on write, plus eager-loaded relations. If menus are permission-filtered, cache the unfiltered tree and prune in memory per user — never cache per-user.
Failure modes to design against.
Bundle-shaped package accidentally autoloaded in a Laravel app produces a container resolution error at startup — total outage, not a degraded menu. Never in a shared dependency set.parent_id model permits cycles; an uncached recursive walk on a cycle is a stack overflow. Enforce a depth cap, or use a closure table with write-time cycle prevention.Ramp-up. For a Laravel team, effectively zero useful ramp-up on the package itself — there is no Laravel API to learn. The ramp-up cost is concentrated in the short Symfony-reading spike and, if a port proceeds, in transferring domain knowledge from the porter to the owning team. Budget the spike honestly and socialize the recommendation as a decision, not an evaluation: this package is a signal that menu/navigation should be solved with maintained or in-house code, and the review itself is the deliverable.
How can I help you explore Laravel packages today?