agence-adeliom/easy-menu-bundle
Symfony bundle adding a basic menu system for EasyAdmin. Manage menus from your dashboard, with optional Gedmo Tree support for nested items. Supports Symfony 6.4/7.x (v3) and earlier branches for older Symfony/PHP versions.
Installation:
composer require agence-adeliom/easy-menu-bundle
Ensure stof_doctrine_extensions is configured for tree behavior (see README).
First Use Case:
Create a menu entity (e.g., MenuItem) extending Adeliom\EasyMenuBundle\Entity\AbstractMenuItem:
namespace App\Entity;
use Adeliom\EasyMenuBundle\Entity\AbstractMenuItem;
class MenuItem extends AbstractMenuItem
{
// Custom fields (e.g., title, route, icon)
}
Register the entity in config/packages/easy_menu.yaml:
easy_menu:
menu_items:
App\Entity\MenuItem:
class: App\Entity\MenuItem
Generate CRUD:
Use EasyAdmin’s crud configuration to manage menu items:
// src/Controller/Admin/MenuItemCrudController.php
namespace App\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
class MenuItemCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string { return MenuItem::class; }
public function configureFields(string $pageName): iterable
{
return [
TextField::new('title'),
AssociationField::new('parent')->autocomplete(),
// Other fields...
];
}
}
Render the Menu:
Inject the Adeliom\EasyMenuBundle\Menu\MenuBuilder service and fetch the menu:
{% for item in easy_menu.menu('main') %}
<li>{{ item.title }}</li>
{% endfor %}
Hierarchical Menu Management:
parent associations to create nested menus (e.g., dropdowns).Adeliom\EasyMenuBundle\Menu\MenuBuilder to fetch menus by name or root items:
$menu = $menuBuilder->getMenu('main');
$menu->getChildren(); // Recursively fetch child items
Dynamic Menu Rendering:
Twig Integration:
{% for item in easy_menu.menu('footer', {'depth': 2}) %}
<a href="{{ item.route.path }}">{{ item.title }}</a>
{% endfor %}
depth limits recursion (e.g., 2 = 2 levels deep).PHP Integration:
$menuItems = $menuBuilder->getMenu('header')->getFlatItems();
// Use in controllers/views for custom logic.
EasyAdmin Integration:
filter options to restrict menu items (e.g., by isActive):
$crud->addFilter('isActiveFilter', Filter\BooleanFilter::new('isActive'));
action:
$crud->addAction(Action\PublishAction::new()->setLabel('Publish'));
Caching:
config/packages/easy_menu.yaml:
easy_menu:
cache: true
cache_lifetime: 3600 # 1 hour
Route-Based Menus:
Link menu items to routes using route field in the entity:
class MenuItem extends AbstractMenuItem
{
#[ORM\Column]
private ?string $route = 'homepage';
}
Render with:
<a href="{{ path(item.route) }}">{{ item.title }}</a>
Icons/Styling:
Add an icon field to the entity and use in Twig:
<i class="fas {{ item.icon }}"></i> {{ item.title }}
Multi-Language Support:
Extend the entity with title translations (e.g., using gedmo/translatable):
use Gedmo\Mapping\Annotation as Gedmo;
#[Gedmo\Translatable]
class MenuItem extends AbstractMenuItem
{
#[Gedmo\TranslationListener]
#[ORM\Column]
private ?string $title;
}
Tree Behavior Requirement:
tree in stof_doctrine_extensions causes parent associations to fail.config/packages/stof_doctrine_extensions.yaml includes:
stof_doctrine_extensions:
orm:
default:
tree: true
Circular References:
depth in Twig/PHP:
$menuBuilder->getMenu('main', ['depth' => 3]);
$menu->getChildren()->matching(new Expression\Exists('children'));
Cache Invalidation:
$this->get('easy_menu.cache')->clear();
EasyAdmin Permissions:
permission system:
$crud->setPermission('ROLE_ADMIN');
dump($menuBuilder->getMenu('main')->toArray());
MenuItem entity is properly mapped with parent and lft/rgt (for tree behavior):
#[ORM\ManyToOne(targetEntity: MenuItem::class, inversedBy: 'children')]
#[ORM\JoinColumn(name: 'parent_id', referencedColumnName: 'id', onDelete: 'SET NULL')]
private ?self $parent = null;
Custom Menu Builders:
Extend Adeliom\EasyMenuBundle\Menu\MenuBuilder to add logic (e.g., filtering by role):
class CustomMenuBuilder extends MenuBuilder
{
public function getMenu(string $name, array $options = []): Menu
{
$menu = parent::getMenu($name, $options);
if ($this->security->isGranted('ROLE_SUPER_ADMIN')) {
$menu->addItem($this->createItem('admin', 'Admin Panel'));
}
return $menu;
}
}
Register as a service:
services:
App\Menu\CustomMenuBuilder:
decorates: 'easy_menu.menu_builder'
arguments: ['@easy_menu.menu_builder.inner']
Dynamic Menu Items: Add items programmatically via events:
// src/EventListener/AddDynamicMenuListener.php
class AddDynamicMenuListener implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
MenuEvents::PRE_BUILD => 'onPreBuild',
];
}
public function onPreBuild(MenuEvent $event): void
{
$menu = $event->getMenu();
$menu->addItem($this->createItem('dynamic', 'Dynamic Item'));
}
}
Override Twig Functions:
Extend Twig’s easy_menu functions in a custom extension:
// src/Twig/AppExtension.php
class AppExtension extends \Twig\Extension\AbstractExtension
{
public function getFunctions(): array
{
return [
new \Twig\TwigFunction('custom_menu', [$this->menuBuilder, 'getMenu']),
];
}
}
'main'. Omit the name in getMenu() to use it:
$menuBuilder->getMenu(); // Uses 'main'
getFlatItems() for linear menus (e.g., breadcrumbs) and getChildren() for nested structures.position field (add to entity):
#[ORM\Column]
private ?int $
How can I help you explore Laravel packages today?