Weave Code
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

Easy Menu Bundle Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require agence-adeliom/easy-menu-bundle
    

    Ensure stof_doctrine_extensions is configured for tree behavior (see README).

  2. 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
    
  3. 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...
            ];
        }
    }
    
  4. 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 %}
    

Implementation Patterns

Workflows

  1. Hierarchical Menu Management:

    • Use parent associations to create nested menus (e.g., dropdowns).
    • Leverage Adeliom\EasyMenuBundle\Menu\MenuBuilder to fetch menus by name or root items:
      $menu = $menuBuilder->getMenu('main');
      $menu->getChildren(); // Recursively fetch child items
      
  2. 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.
      
  3. EasyAdmin Integration:

    • Filtering: Use EasyAdmin’s filter options to restrict menu items (e.g., by isActive):
      $crud->addFilter('isActiveFilter', Filter\BooleanFilter::new('isActive'));
      
    • Actions: Add bulk actions (e.g., "Publish") via EasyAdmin’s action:
      $crud->addAction(Action\PublishAction::new()->setLabel('Publish'));
      
  4. Caching:

    • Enable Symfony’s cache for menus in config/packages/easy_menu.yaml:
      easy_menu:
          cache: true
          cache_lifetime: 3600 # 1 hour
      

Integration Tips

  • 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;
    }
    

Gotchas and Tips

Pitfalls

  1. Tree Behavior Requirement:

    • Issue: Forgetting to enable tree in stof_doctrine_extensions causes parent associations to fail.
    • Fix: Verify config/packages/stof_doctrine_extensions.yaml includes:
      stof_doctrine_extensions:
          orm:
              default:
                  tree: true
      
  2. Circular References:

    • Issue: Deeply nested menus may cause stack overflows or performance issues.
    • Fix:
      • Limit recursion with depth in Twig/PHP:
        $menuBuilder->getMenu('main', ['depth' => 3]);
        
      • Use lazy-loading for large menus:
        $menu->getChildren()->matching(new Expression\Exists('children'));
        
  3. Cache Invalidation:

    • Issue: Menus aren’t updated after CRUD changes if caching is enabled.
    • Fix: Clear the cache manually or use Symfony’s cache invalidation:
      $this->get('easy_menu.cache')->clear();
      
  4. EasyAdmin Permissions:

    • Issue: Menu items may not appear if the user lacks permissions.
    • Fix: Use EasyAdmin’s permission system:
      $crud->setPermission('ROLE_ADMIN');
      

Debugging

  • Dump Menu Structure: Use Symfony’s var dumper to inspect the menu tree:
    dump($menuBuilder->getMenu('main')->toArray());
    
  • Check Entity Mapping: Verify the 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;
    

Extension Points

  1. 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']
    
  2. 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'));
        }
    }
    
  3. 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']),
            ];
        }
    }
    

Configuration Quirks

  • Default Menu Name: The default menu name is 'main'. Omit the name in getMenu() to use it:
    $menuBuilder->getMenu(); // Uses 'main'
    
  • Flat vs. Tree: Use getFlatItems() for linear menus (e.g., breadcrumbs) and getChildren() for nested structures.
  • Ordering: Sort menus by position field (add to entity):
    #[ORM\Column]
    private ?int $
    
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.
comsave/common
alecsammon/php-raml-parser
chrome-php/wrench
lendable/composer-license-checker
typhoon/reflection
mesilov/moneyphp-percentage
mike42/gfx-php
bookdown/themes
aura/view
aura/html
aura/cli
povils/phpmnd
nayjest/manipulator
omnipay/tests
psr-mock/http-message-implementation
psr-mock/http-factory-implementation
psr-mock/http-client-implementation
voku/email-check
voku/urlify
rtheunissen/guzzle-log-middleware