Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Menubundle Laravel Package

object.bg/menubundle

A Symfony bundle for defining, managing, and rendering menus in your applications. Build hierarchical navigation, configure menu items, and output them cleanly in templates with a flexible, extendable API.

View on GitHub
Deep Wiki
Context7

Getting Started

Run the framework check before anything else. Despite the object.bg/menubundle framing, this is a Symfony bundle, not a Laravel package — Bundle suffix, bundles.php registration, Doctrine entities, KnpMenuBundle for rendering, Sonata Admin for the CRUD surface. There is no service provider, no facade, no config/menus.php, no php artisan vendor:publish. Plan accordingly:

  • In a Symfony app: composer require object.bg/menubundle, register Warxcell\MenuBundle\WarxcellMenuBundle plus SonataAdminBundle, KnpMenuBundle, DoctrineBundle in config/bundles.php, generate schema (bin/console doctrine:migrations:diff — there are no shipped migrations), wire the Sonata admin services, then bin/console debug:container | grep -i menu to find the provider service ID. That provider is the only thing your runtime code touches.
  • In a Laravel app: do not install it. The day-to-day leverage you get from this package is as a reference model, not a dependency. Read the MenuProvider + entity mapping, then implement the same contract natively with Eloquent and a Filament/Nova admin. That port is 2–4 engineer-days for a mid-level developer and is the only path where you own the code.

Where to look first in the source: the Doctrine ORM mappings (Resources/config/doctrine/*.orm.xml) and the MenuProvider class/interface. Those two artifacts define the entire public contract — field names and provider signature are the API. Everything else is Sonata Admin plumbing.

First use case (either path): create a menu named main with two or three items (one internal route, one external URL), then render it. In Symfony that means handing the provider's Menu object to knp_menu_render() in Twig. In Laravel, that means a Menu::findByHandle('main') returning a cached tree your Blade component walks recursively. If that works end-to-end, you understand 90% of what this package does.

Implementation Patterns

Provider → tree → view is the whole loop. Everything else in the bundle (Sonata admin classes, service YAML) exists to feed this one call. Structure your application code to depend on the provider, never on the entities directly. If you port to Laravel, define the equivalent interface first:

interface MenuProvider {
    public function getMenu(string $handle, ?string $locale = null): MenuTree;
}

One menu record per context, not per page. Model main, footer, account-sidebar as distinct menus and let the admin own each one. Do not build one mega-tree with visibility flags — filtering a stored tree is more code and more bugs than storing three trees. This is the single most important modelling decision and it survives the Symfony→Laravel port unchanged.

Active-state via route name, not URL. Store the route name on each item (home, pricing, account.settings) and resolve active state at render time — KnpMenu's setCurrentUri() plus a voter on the Symfony side, Route::currentRouteName() comparison in a Blade component on the Laravel side. Route names survive domain changes, locale prefixes, and trailing-slash policy; hardcoded URLs do not. This is the reason the package's "active state detection" feature is worth copying.

Cache the assembled tree, invalidate on write. The provider runs on every request that renders chrome, so wrap it:

Cache::remember("menu:{$handle}:{$locale}", 3600, fn () => $this->buildTree($handle));

Invalidate from the admin write path — a Sonata postPersist/postUpdate/postRemove hook in Symfony, a Filament resource observer or Eloquent model event in Laravel. Never invalidate on a timer alone; the stale-menu-after-edit bug is the classic failure in this domain.

Load the whole tree in one query, assemble in PHP. A recursive parent_id schema invites N+1 on every render. Fetch all rows for the menu, index by id, link children arrays in a second pass. Do not lazy-load children per node. If you skip this, a five-level menu with 40 items becomes 40+ queries per page.

Seed programmatically — do not click through admin on every environment. Fixtures or a seeder that creates the canonical main / footer structure beats manual setup because it is diffable, reviewable, and reproducible in CI. Bonus: it forces route names into constants or enum cases, so a renamed controller action surfaces as a build failure rather than a 404 in production nav.

Extend at the admin/render layer, not the entity. Column layout, drag-reorder UX, and permission gating live in your Sonata admin subclass (or Filament resource). Treat the entity as read-only from application code, and treat the Blade/Twig template as the extension point for markup changes — icons, aria-current, active classes.

Blade rendering for the Laravel port. A single recursive <x-menu-item> component with @foreach ($item->children as $child) handles arbitrary depth, and you get publishable-template semantics for free by putting it under resources/views/components. That is what "publishable customizable templates" should mean in your stack.

Gotchas and Tips

  • Framework mismatch is the headline gotcha. No Laravel service provider, no config file, no vendor:publish. Any guide that tells you to composer require this into a Laravel app and register a service provider is wrong; there is nothing to register — Laravel will autoload the classes and then fail the moment you touch a Doctrine entity or a Sonata service.
  • Sonata Admin version lock is the whole ballgame. This bundle targets a specific Sonata generation; a Sonata 3→4 bump will almost certainly break it, and Sonata is itself in maintenance mode. If you adopt it in Symfony, pin sonata-project/admin-bundle explicitly and treat every bump as a migration project.
  • Zero dependents and a score of 0 means nobody is fixing bugs. Assume no upstream. Fork into your own VCS or, better, budget a day to replace it — a Menu + MenuItem entity pair plus KnpMenuBundle is genuinely most of what this package is.
  • No migrations, no fixtures, no schema docs. The mappings are the documentation; read doctrine:mapping:info and Resources/config/doctrine/*.orm.xml before running schema:update, so you know what tables you are about to create.
  • Check whether you can extend the entity at all. If the entities are not MappedSuperclass, adding an icon or css_class field means forking the mapping. Assuming extendability and being wrong is a refactor.
  • Route names live in the database. Renaming a controller action silently breaks every nav link pointing at it. Reference route names via constants/enum cases everywhere — seeders, admin dropdowns, render-time comparisons — so a rename is caught at build time.
  • Debugging order: debug:container for the provider service ID, doctrine:mapping:info for entity registration, debug:router when links 404, and Sonata's logs when the CRUD screen white-screens (usually a missing service definition or a template override that did not get registered).
  • Admin surface is a CMS, treat it like one. Sonata admin access is opt-in per admin service and easy to leave open. Confirm ROLE_ADMIN (or stricter) gates the menu screen, that it is not reachable in production without that gate, and that the auth model is reviewed before launch. A misconfigured admin route here is a public-content-takeover finding, not a cosmetic bug.
  • License is unverified. Confirm it permits forking and internal redistribution before copying a single line into your codebase — this is a hard gate on the port path, not a nice-to-have.
  • Recursion guard. Self-referential parent_id permits cycles created through the UI. Add a depth cap and a visited-set check in the tree builder, or a single bad admin edit takes down every page that renders chrome.
  • Headless/API is not supported out of the box. No serializer, no normalizer — you write the tree-to-array conversion yourself. If your frontend is JS-driven, that conversion plus the cache is again 90% of the package's value, which is the strongest argument for a native port.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package