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

Ktw Database Menu Bundle Laravel Package

kevintweber/ktw-database-menu-bundle

A Symfony bundle for creating database-backed menus. Store menu items as Doctrine entities, manage navigation without code changes, and render dynamic menus in Twig templates. Useful for CMS-style applications with frequently updated navigation.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: it is a Symfony bundle, not a Laravel package. It extends KnpMenuBundle and persists the menu tree through Doctrine ORM. There is no Laravel service provider, no Eloquent model, no Blade view. If you are in a Laravel codebase, your "day-to-day leverage" is limited to reading it as a reference implementation for a schema plus a tree provider — you cannot composer require it. The Laravel equivalents you'd actually adopt are spatie/laravel-menu or a small first-party build.

If you are on a Symfony app (realistically 2.x/3.x vintage, given the bundle's era), the minimal path is:

  1. composer require kevintweber/ktw-database-menu-bundle alongside knplabs/knp-menu-bundle and Doctrine ORM. Expect constraint conflicts on a modern Symfony — resolve by pinning, or accept that you are fork-and-owning it.
  2. Register the bundle in app/AppKernel.php (or config/bundles.php on Symfony 4).
  3. Configure the bundle to point at your concrete entity class. The bundle ships a base menu-item entity you extend so you can add label, routeName, routeParams, url, position, parent, enabled. Nothing about your navigation should live in the bundle's own columns.
  4. Generate the schema: doctrine:schema:update --force or a hand-written migration.
  5. Seed a couple of rows (fixtures or a data migration), then render.

The first real use case: a main menu with two levels where a content editor can reorder items without a deploy. Get that working end to end — entity extension → migration → one seeded tree → knp_menu_render('main') in a Twig layout — before touching anything else. That loop is the whole product; everything else is polish.

Implementation Patterns

Extend the entity, don't fight it. The bundle's value is that your columns are yours. Add:

class MenuItem extends BaseMenuItem
{
    /** @ORM\Column(type="string", length=120) */
    private $label;

    /** @ORM\Column(type="string", length=120, nullable=true) */
    private $routeName;

    /** @ORM\Column(type="json_array", nullable=true) */
    private $routeParams = [];

    /** @ORM\Column(type="integer") */
    private $position = 0;

    /** @ORM\ManyToOne(targetEntity="MenuItem", inversedBy="children") */
    private $parent;

    /** @ORM\Column(type="boolean") */
    private $enabled = true;
}

Keeping routeName and routeParams separate from a free-text url is the single highest-value decision. You generate links through Symfony's UrlGenerator at build time, so changing a route slug does not silently break the navigation.

Ordering. A position integer per parent is enough for editorial drag-and-drop. Always add id as a tiebreaker in the ORDER BY — two items at the same position otherwise render in non-deterministic order and your reorder tests flake.

Active state and the current URI. KnpMenu's isCurrent only works when the menu is built with the request context. Make sure whatever wires the provider passes the current URI/request (setCurrentUri on the root item, or the standard knp_menu Twig extension path). Verify "active trail" on a two-level menu explicitly — it is the most common "why is nothing highlighted" bug report.

Route vs URL resolution. Resolve route names to URLs in a service that takes all rows for a menu, builds an id-indexed array, and hydrates the tree in memory. Do not let the renderer call the URL generator per node inside a recursive template — that is where N+1 creeps in for larger trees.

Caching. Menu trees are tiny, read on every page, and change rarely. Cache the built tree per menu key (locale/tenant variants included in the key) and bust on write. A Doctrine postPersist/postUpdate/postRemove listener on the menu-item entity is the least error-prone invalidation point — editors reorder items through three different code paths, and only the ORM layer sees them all.

Admin surface. In practice the bundle gives you the schema and provider; the CRUD is yours. If you already run EasyAdmin or SonataAdmin, a menu-item CRUD with parent select and a position field is an afternoon. Drag-and-drop reordering is the part that eats the time — budget for it. If you skip the admin UI, you have not actually delivered the feature, just moved the hardcoding into SQL inserts.

Multi-tree / multi-locale. There is no first-class concept of named menus or translations. Add menuKey (and locale, tenantId if relevant) columns yourself and scope the provider by them. Prefer a translation-key column for labels over per-locale rows unless labels genuinely differ per locale.

Gotchas and Tips

  • Provenance is the real risk. Zero stars, zero dependents, no maintenance signal. Treat any commit history as unvetted. Read the license before copying code, vendor/fork the parts you reuse, and assume you will be the maintainer forever. For a menu this is defensible; just make it a conscious decision rather than a surprise.
  • Version drift. The bundle targets an old Symfony/KnpMenu generation. PHP and Symfony constraints will likely fail on a current stack; a fork with updated constraints is probably necessary before first use, not after.
  • Class-name/namespace surprises. Configuration points at your entity class, so a typo (or forgetting that a mapped superclass requires your class to be a full @Entity) produces confusing Doctrine errors at cache warmup rather than at the point of the mistake. Run doctrine:schema:validate after wiring.
  • Broken links from free-text URLs. If you allow a raw url column at all, validate it on save. An invalid route name throws RouteNotFoundException at render time — which means a 500 on every page of the site, triggered by an editor's typo.
  • Orphaned nodes and cycles. Enforce the parent FK, and add a guard preventing an item from becoming its own ancestor. Without it, one drag-and-drop can produce an infinite recursion that takes down every page.
  • Cap render depth. renderDepth in the Twig call is cheap insurance against a deep or wide tree you did not anticipate.
  • Template escaping. If editors can enter labels containing HTML (icons, marks), decide deliberately between raw and escaping; the default KnpMenu template escapes. Do not flip to raw globally just to get an icon in.
  • Cache staleness after reorder. Reordering often updates multiple rows in a transaction and sometimes bypasses the ORM if done in bulk. A cache key that only busts on postUpdate will silently serve an old order. Include a manual "rebuild navigation" action as an escape hatch.
  • Extension points worth knowing: the provider is the seam — replace or decorate it to add visibility rules (role, enabled, date window) without touching the entity. KnpMenu's MenuItem::setDisplay() / setDisplayChildren() lets you filter nodes during a menu-building event, which is the cleanest place to put authorization-aware visibility rather than in the template.
  • Tests that matter: ordering (including position ties), route resolution, active-trail on nested items, and reorder round-trips. Everything else is CRUD and can wait.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource