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

zetta/menu-bundle

Zetta Menu Bundle is a PHP package for building and managing hierarchical navigation menus. It offers menu and menu-item models, tree operations, and integration points for Symfony applications, helping developers define, organize, and render multi-level menus with less boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and pin the Symfony version first. This package's last tagged release is from October 2015, so it targets Symfony 2.x (~2.3–~2.8). Add it alongside the versions of knplabs/knp-menu-bundle and Doctrine it was built against, or Composer's solver will pull modern versions that break the service definitions:

    {
        "require": {
            "zetta/menu-bundle": "dev-master",
            "symfony/symfony": "~2.8",
            "knplabs/knp-menu-bundle": "~2.0"
        }
    }
    

    There is no Flex recipe, so register the bundle manually in app/AppKernel.php:

    new Zetta\MenuBundle\ZettaMenuBundle(),
    
  2. Where to look first — in this order:

    • DependencyInjection/Configuration.php — the config tree tells you what is actually configurable (table/entity names, template, role field, cache settings).
    • Resources/config/services.xml — the real API surface. Service IDs here are what you will inject into your controllers and layouts; the README is sparse, this file is the documentation.
    • Entity/ (if the menu is Doctrine-backed) — look at the fields the bundle expects on a menu item: label, route/URI, parent, position, and the security field (role or voter attribute).
    • Resources/views/ and any Twig extension — rendering is almost certainly done through a Twig function/filter that wraps KnpMenu's knp_menu_render.
    • Security/ or Voter/ — this is where "Secure" happens: the check that removes items the current token cannot access.
  3. Configure the minimum. Register the bundle's routing file if it ships controllers/admin screens, and import its config in config.yml:

    zetta_menu:
        # keys per Configuration.php — typically entity class, template, and
        # an option controlling whether security filtering is on
    

    Then run doctrine:schema:update --dump-sql and inspect the output before applying — if the bundle ships entities, this is your first real look at the data model.

  4. First use case: a role-aware main navigation. Create a few menu items (route + required role) in the database or in a fixture, put a single call in your base layout:

    {# app/Resources/views/base.html.twig #}
    <nav>{{ zetta_menu_render('main') }}</nav>
    

    Log in as a user with a reduced role set and confirm the items disappear. That round trip — seed item → render → log in as a restricted user → item hidden — is the whole value proposition of the bundle and the fastest way to validate your configuration.

Implementation Patterns

Treat the bundle as a thin, security-aware wrapper around KnpMenu. The healthy way to use it day-to-day is to keep menu structure in the bundle and menu presentation in your own templates. Override the bundle's view rather than fighting it:

{# app/Resources/ZettaMenuBundle/views/menu.html.twig #}
{% for item in item.children %}
    <li class="{{ item.current ? 'active' }}">
        <a href="{{ item.uri }}">{{ item.label|trans({}, 'menu') }}</a>
        {{ block('children') }}
    </li>
{% endfor %}

Build menus in code when they are structural, in the database when they are editorial. A controller or MenuBuilder service gives you type safety, IDE navigation, and free diffs; the Doctrine-backed path is only worth it when non-developers maintain the tree. If you use both, namespace them (main, footer, admin) and don't mix sources in a single menu.

Filter with the security system, not with in_array($role, $user->getRoles()). The bundle's added value is delegating to Symfony's AuthorizationCheckerInterface, which means role hierarchy, remember-me, and switched users all behave correctly. If you add your own items, attach the security metadata through the same mechanism the bundle uses (check Configuration.php for whether it is a role string or a voter attribute/subject pair) so that filtering stays in one place.

Cache the tree, key it by the permission set. Rendering a menu on every request is cheap until the tree is large and role-filtered per user. Cache the unfiltered tree (it is identical for everyone) and apply security filtering per request; if you cache the filtered result, the cache key must include the user's effective roles, and you must invalidate on role changes. Caching the filtered tree under a generic key is the single fastest way to leak an admin-only link to an anonymous visitor.

Active-state and route parameters. Let the bundle compute current from the request rather than hardcoding class="active". If items link to parameterised routes, pass route params in the item definition, not by string-concatenating URIs — otherwise the current-item detection silently stops matching.

Integration tips:

  • Inject the menu factory/renderer as a service rather than calling Twig helpers from controllers; it keeps the API accessible in emails, PDFs, and JSON nav endpoints.
  • If you already have KnpMenuBundle wired, check whether Zetta's bundle depends on it or ships its own renderer — don't register two Twig extensions exposing similarly named functions.
  • For multi-navigation layouts, define distinct menus (main, sidebar, breadcrumb) and render each independently; breadcrumbs derived from a filtered menu are a good way to reuse the same security rules.

Testing pattern. Write one kernel/functional test per security boundary:

$client = static::createClient();
$client->request('GET', '/', [], [], [
    'PHP_AUTH_USER' => 'editor',
    'PHP_AUTH_PW'   => 'editor',
]);
$this->assertNotContains('Admin', $client->getResponse()->getContent());

This catches the two failure modes that actually matter: items leaking to users who shouldn't see them, and items vanishing for users who should.

Gotchas and Tips

  • The project is effectively unmaintained. Nine stars, zero dependents, last release 2015. Assume no Symfony 4/5/6 support, no PHP 8 fixes, and no responses to issues. Plan for a fork (or vendor copy) from day one; budget the first afternoon for making it boot on your target Symfony.
  • security.context is gone. Any 2015-era bundle that called $this->get('security.context')->isGranted() breaks on Symfony ≥2.6 (deprecated) and ≥3.0 (removed). The fix is mechanical — inject security.authorization_checker and security.token_storage — but you will be doing it in vendor code, so do it in a fork and record it in your composer repositories block.
  • No docs means read services.xml. Every guess about service IDs and constructor arguments costs a ServiceNotFoundException cycle. php app/console container:debug, debug:container <id> --show-arguments, and the profiler's service panel are your fastest debugging tools here.
  • Role hierarchy is not the same as role membership. Whether the bundle honours role_hierarchy depends entirely on whether it calls isGranted(). If it compares raw strings, ROLE_ADMIN will not see items gated on ROLE_USER unless you also list that role on the item. Verify this explicitly before relying on hierarchy.
  • Route generation at build time throws on typos. If items store a route name and the bundle resolves URIs eagerly, a renamed route produces a hard 500 during menu construction — not just a dead link. Grep for route names when renaming, or wrap item building so a missing route logs instead of fatals.
  • Doctrine mapping is yours to migrate. The bundle's entities (if any) are mapped from XML/YAML in the vendor tree; overriding the entity class requires re-mapping from your own Resources/config/doctrine. Don't patch the vendor mapping directly — your next composer update wipes it.
  • Extension points to reach for, in order of preference: override the Twig template → decorate/replace the menu factory service → add a custom voter (cleanest for anything domain-specific like "user owns this project") → subclass the item entity. Avoid editing bundle classes you can override via the container.
  • Escaping. Menu labels are frequently translated and occasionally HTML-bearing (icons, <span> wrappers). Decide once whether labels are |raw and make sure editors cannot inject markup through the label field if they are.
  • Version-control the config, not the data. Keep the zetta_menu config block and any code-built menus in the repo; treat database-stored menu trees as content that needs a fixture or migration path for new environments, or staging will come up with an empty nav.
  • Watch for silent no-ops. With a bundle this small, an option that isn't wired up simply does nothing rather than erroring. Change one config key at a time and confirm the rendered output changed; don't assume a YAML value took effect because there was no exception.
  • Exit strategy. Keep menu rendering behind one Twig function call in your layout. If the bundle becomes untenable, reimplementing that function against KnpMenu directly is a few hours of work — provided the call sites are not scattered across fifty templates.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings