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

Menu Bundle Laravel Package

kunstmaan/menu-bundle

A menu bundle for Kunstmaan CMS that provides flexible, tree-based navigation menus you can configure and render in your templates. It lets developers define menu structures and integrate them into pages for consistent site navigation.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Kunstmaan CMS stack bundle, not a standalone menu library. It assumes kunstmaan/node-bundle, the Kunstmaan admin (Sonata-based), and Doctrine extensions are already installed. Pulling it into a plain Symfony app is not a realistic path.

Minimal install:

composer require kunstmaan/menu-bundle

Register Kunstmaan\MenuBundle\KunstmaanMenuBundle in app/AppKernel.php, then import routing:

# app/config/routing.yml
_kunstmaan_menu:
    resource: "@KunstmaanMenuBundle/Resources/config/routing.yml"
    prefix:   /admin

Declare the menus once in config — this is the contract between admin UI and your templates:

# app/config/config.yml
kunstmaan_menu:
    menus:
        main:   ~
        footer: ~

Override the entities (standard Kunstmaan pattern) before you ever run the schema tool:

# app/config/config.yml
parameters:
    kunstmaan_menu.menu.class:      AppBundle\Entity\Menu
    kunstmaan_menu.menu_item.class: AppBundle\Entity\MenuItem

The subclasses just extend Kunstmaan\MenuBundle\Entity\Menu / MenuItem and re-map the table via annotations. Then create tables (kunstmaan_menu_menus, kunstmaan_menu_menu_items), clear cache, and open the admin — a "Menu" tab appears where editors build the tree by drag-and-drop.

Where to look first, in this order:

  1. Entity/Menu.php and Entity/MenuItem.php — this is the data model you actually work with.
  2. Twig/MenuTwigExtension.php — the only API most developers touch (the get_menu() Twig function).
  3. Resources/views/ — the item rendering template you will override.
  4. Admin/ — controllers for add/edit/reorder and how the tree is persisted.

First use case: build the main navigation from existing node pages. In the admin, add items pointing at node translations (or raw URLs), then in your base layout:

{% set menu = get_menu('main', app.request.locale) %}
{% if menu is not null %}
    {{ include('@KunstmaanMenu/Menu/menu.html.twig', { menu: menu }) }}
{% endif %}

Implementation Patterns

Config menu keys are the shared identifier. The key in kunstmaan_menu.menus is what get_menu('main') resolves to, and it is what the admin uses to group items. Treat renaming a key as a data migration: existing Menu rows and their items are tied to that name, so renames silently orphan items.

Always render per-locale, and guard for null. The lookup is (name, locale) scoped; a menu configured for nl but requested with fr returns null rather than falling back. Put a single helper in your base template instead of repeating the guard:

{% macro nav(name, locale, depth) %}
    {% set menu = get_menu(name, locale) %}
    ...recursive include with item.parent / item.children...
{% endmacro %}

Recursion is done manually against the tree — the bundle gives you the hierarchy, not a renderer. The stock item template uses the item's title, nodeTranslation, url, and newWindow; override it at app/Resources/KunstmaanMenuBundle/views/Menu/menu.html.twig (or in your own templates/ and include it) to attach Bootstrap/Tailwind classes and aria-current.

Link resolution is "either node or URL". An item is normally bound to a NodeTranslation, which gives you locale-aware URLs and works with the CMS's online/offline state; a url field is the escape hatch for external links and anchors. Your template must branch:

{% if item.nodeTranslation is not null %}
    <a href="{{ path('_slug', { url: item.nodeTranslation.urlSlug }) }}"
       {% if item.newWindow %}target="_blank" rel="noopener"{% endif %}>
{% elseif item.url %}
    <a href="{{ item.url }}">
{% endif %}

Active state is best derived from the current node, not from string comparison: compare item.nodeTranslation against the _nodeTranslation request attribute where available, and fall back to app.request.pathInfo for URL items.

Seeding menus programmatically (fixtures, deploy scripts, site imports) is done through the entity manager, but you must respect the Doctrine tree extension: use the repository's persistAsFirstChild / persistAsLastChildOf (or set parent and let the listener fix lft/rgt/lvl). Never INSERT rows by hand.

Cache the rendered nav, not the entity graph. Menu output is expensive (recursive node translation joins) and changes rarely. Wrap the rendered string in your own cache keyed by name + locale + a version counter, and bump the counter from an admin save/reorder listener rather than trying to invalidate per-item.

Admin-side extensions are the usual Sonata routes: add form fields to MenuItemAdminType, add roles (ROLE_ADMIN-style checks) in the admin controllers, and override the admin templates. If editors need "title only in this locale", that is an entity override plus a form extension — not a config toggle.

Gotchas and Tips

  • Entity overrides are a one-way door. Set the kunstmaan_menu.*.class parameters before creating tables. Changing them after the fact leaves you with two tables and mapping drift.
  • Broken trees come from direct DB writes. lft/rgt/lvl/root are Gedmo-maintained. Any migration, importer, or manual UPDATE that touches parent_id must go through the tree repository, or the admin drag-and-drop renders nonsense.
  • The reorder endpoint is AJAX + CSRF. If you customise the admin JS or add items via a modal, keep the existing route names and payload shape. Reorder failures usually show up as "moved one step then snapped back" — that's a 4xx on the AJAX call, check the network tab before suspecting the tree.
  • Null menu is normal. New locales, renamed config keys, and fresh databases all yield null from get_menu(). Never assume menu.items exists.
  • Locale and site scoping. Menus are not shared across sites by default; on multi-site Kunstmaan installs be explicit about which locale you pass in each layout, or you will ship an empty nav to one site.
  • Performance. The default lookup hydrates items and lazily walks node translations. For deep menus (3+ levels, 100+ items) add an eager-loading repository method or cache the HTML; otherwise every page pays for it.
  • Abandoned stack. The package is inert (no recent activity, no dependents) and pins old Symfony/Doctrine-Gedmo constraints. On a modern project use knp-menu with your own entity; on an existing Kunstmaan install, lock the bundle and the tree extension version together and avoid upgrading one without the other.
  • Debugging checklist, in order: doctrine:schema:validate (mapping vs tables), debug:twig (confirm get_menu is registered), debug:container --parameter=kunstmaan_menu.menu.class (override actually applied), then the admin reorder AJAX response. Nine out of ten issues are one of those four.
  • Translations are per item, not per menu. Editors duplicating a whole menu for a second locale is a common mistake — the item fields carry the locale, the Menu row is a container.
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.
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings