Installation
composer require dama/menu-bundle
Add to config/app.php under providers:
Dama\DamaMenuBundle\DamaMenuBundle::class,
First Use Case: Basic Menu
Create a MenuTreeBuilder class (e.g., src/Menu/MainMenuTreeBuilder.php) implementing MenuTreeBuilderInterface:
use Dama\DamaMenuBundle\Menu\MenuTreeBuilderInterface;
use Dama\DamaMenuBundle\Menu\Node;
class MainMenuTreeBuilder implements MenuTreeBuilderInterface
{
public function buildTree(Node $root): void
{
$root
->child('Dashboard')
->setRoute('dashboard');
}
}
Register the Builder
In config/packages/dama_menu.yaml (create if missing):
menu_builder: App\Menu\MainMenuTreeBuilder
Render in Twig
{{ dump(app.dama_menu.render('main_menu')) }}
Hierarchical Node Building
Use fluent methods (child(), end()) to define nested menus:
$root
->child('Admin')
->setRoute('admin_dashboard')
->child('Users')
->setRoute('admin_users_list')
->end()
->end();
Permission-Based Visibility
Restrict nodes with setRequiredPermissions():
->setRequiredPermissions(['ROLE_ADMIN', 'ROLE_SUPER_ADMIN'])
Active State Logic
Use setAdditionalActiveRequestMatcher() for custom active state checks:
->setAdditionalActiveRequestMatcher(function (Request $request) {
return $request->query->has('active_tab');
})
Conditional Rendering
Dynamically add nodes with ifTrue():
->ifTrue($this->isFeatureEnabled('analytics'))
->child('Analytics')
->setRoute('analytics_dashboard')
->end();
Twig Integration
Access the menu in templates via app.dama_menu.render('menu_name').
Customize output with Twig filters (e.g., {{ menu|dama_menu_render }}).
Symfony Security Integration
Use ExpressionLanguage for complex permission checks:
->setRequiredPermissions([new Expression("has_role('ROLE_USER') and not has_role('ROLE_GUEST')")])
Caching
Enable caching in config/packages/dama_menu.yaml:
cache: true
cache_lifetime: 3600
Multi-Language Support Localize labels via translation keys:
->child('{{ 'menu.dashboard'|trans }}')
Permission Evaluation Order
setRequiredPermissions([]) on parent nodes if children should override visibility.Route Resolution
RouteNotFoundException.app.dama_menu.getMenu('menu_name')->getRoot()->getChildren() to inspect the tree.Circular References
ifTrue() conditions that rebuild the tree infinitely.Twig Context
Request object in ActiveRequestMatcher is not the current Twig request. Use RequestStack for context-aware checks:
use Symfony\Component\HttpFoundation\RequestStack;
$requestStack = $this->container->get('request_stack');
$request = $requestStack->getCurrentRequest();
$menu = app('dama_menu')->getMenu('main_menu');
dump($menu->getRoot()->getChildren());
AuthorizationChecker to verify user roles:
$this->authorizationChecker->enough($user, ['ROLE_ADMIN']);
ActiveRequestMatcher to log conditions:
->setAdditionalActiveRequestMatcher(function (Request $request) {
\Log::debug('Checking active state for:', ['path' => $request->getPathInfo()]);
return str_starts_with($request->path(), '/debug');
});
Custom Node Types
Extend Node to add metadata:
class CustomNode extends Node
{
public function setBadge(string $badge): self { ... }
}
Event Listeners
Subscribe to MenuBuilderEvent to modify menus dynamically:
use Dama\DamaMenuBundle\Event\MenuBuilderEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class MenuSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [MenuBuilderEvent::class => 'onMenuBuild'];
}
public function onMenuBuild(MenuBuilderEvent $event): void
{
$event->getMenu()->getRoot()->addChild('Dynamic Node');
}
}
Override Twig Rendering Create a custom Twig extension to modify output:
class CustomMenuExtension extends \Twig\Extension\AbstractExtension
{
public function getFilters(): array
{
return [
new \Twig\TwigFilter('custom_menu_render', [$this, 'renderMenu']),
];
}
public function renderMenu(array $menu): string
{
// Custom logic here
return '<ul>' . implode('', $menu) . '</ul>';
}
}
Database-Backed Menus Fetch menu structure from a DB and hydrate nodes:
$menuItems = $this->menuRepository->findAll();
foreach ($menuItems as $item) {
$root->child($item->label)
->setRoute($item->route)
->setRequiredPermissions($item->permissions);
}
How can I help you explore Laravel packages today?