## Getting Started
### Minimal Setup
1. **Installation**:
```bash
composer require aropixel/menu-bundle
Ensure aropixel/admin-bundle is already installed.
Configuration:
Add to config/packages/aropixel_menu.yaml:
aropixel_menu:
menus:
main:
name: "Main Menu"
depth: 3
Database: Run migrations:
php bin/console doctrine:migrations:migrate
Routes:
Include in config/routes.yaml:
aropixel_menu:
resource: '@AropixelMenuBundle/Resources/config/routes.yaml'
prefix: /admin
First Use Case:
Access /admin/menu to manage the "Main Menu" via the drag-and-drop interface. Use built-in sources (e.g., pages, links) to populate the menu.
Admin Management:
main, footer).link, page, section) or extend with custom sources (e.g., ProductMenuSource).Frontend Rendering: Use Twig functions/filters in templates:
{# Render a menu #}
<nav>
{% for item in get_menu('main') %}
{% if item|is_section %}
<div>{{ item.title }}</div>
{% else %}
<a href="{{ item|get_link }}" {{ item.newTab ? 'target="_blank"' : '' }}>
{{ item.title }}
</a>
{% endif %}
{% endfor %}
</nav>
Custom Entity Integration:
Extend the base Menu entity to add fields (e.g., newTab):
#[ORM\Entity]
class Menu extends BaseMenu {
#[ORM\Column(type: 'boolean')]
private bool $newTab = false;
}
Update config:
aropixel_menu:
entity: App\Entity\Menu
Custom Data Sources:
Implement MenuSourceInterface for dynamic content (e.g., products):
class ProductMenuSource implements MenuSourceInterface {
public function getAvailableItems(array $menuItems): array {
return $this->productRepository->findAll()->map(fn($p) => [
'value' => $p->getId(),
'label' => $p->getName(),
'type' => 'product',
]);
}
// ... other required methods
}
Nested Menus: Configure depth in YAML and iterate recursively in Twig:
{% for item in get_menu('main') %}
<li>
<a href="{{ item|get_link }}">{{ item.title }}</a>
{% if item.children|length > 0 %}
<ul>
{% include 'menu/_nested.html.twig' with {'items': item.children} %}
</ul>
{% endif %}
</li>
{% endfor %}
Entity Configuration:
aropixel_menu.entity in config after extending the base Menu entity will cause runtime errors.Source Registration:
MenuSourceInterface (e.g., resolveUrl(), getPayload()).Stimulus Controller:
aropixel-menu controller is auto-loaded (no manual assets/controllers.json needed since v2.3).php bin/console cache:clear) if the drag-and-drop UI fails to load.Nested Menus:
depth: 3).depth explicitly for each menu.Page Source:
aropixel/page-bundle (v2.0+). If missing, pages won’t appear in the selector.link sources temporarily.Caching Issues:
php bin/console cache:clear
Check Registered Sources: Dump available sources in a Twig template:
{{ dump(app.container.get('aropixel_menu.source_chain').getSources()) }}
Log Menu Data:
Override the getMenuItems method in a custom MenuHandler to log queries:
class CustomMenuHandler extends MenuHandler {
public function getMenuItems(string $menuCode): array {
$items = parent::getMenuItems($menuCode);
\Log::info('Menu items for '.$menuCode, ['items' => $items]);
return $items;
}
}
Twig Filter Debugging:
Test |get_link with {{ item|get_link|dump }} to verify URL resolution.
Database Schema:
If migrations fail, manually check the menu table structure. The bundle expects:
id, title, type, link, parent_id, lft, rgt (for nested sets).Custom Menu Handlers:
Extend MenuHandler to modify menu logic (e.g., filtering items):
class CustomMenuHandler extends MenuHandler {
public function getMenuItems(string $menuCode): array {
$items = parent::getMenuItems($menuCode);
return array_filter($items, fn($item) => $item->isActive());
}
}
Register as a service with the aropixel_menu.menu_handler tag.
Override Twig Templates:
Customize the admin UI by overriding templates in templates/AropixelMenuBundle/ (e.g., menu/edit.html.twig).
Dynamic Menu Codes: Fetch menu codes dynamically via a service and inject them into the config:
aropixel_menu:
menus: '%menu_codes%'
// config/services.yaml
parameters:
menu_codes: ['main', 'footer', '%env(MENU_CODE)%']
Validation:
Add custom validation to menu items via a Constraint and Validator:
#[Assert\Callback]
public function validate(ExecutionContextInterface $context) {
if ($this->type === 'page' && empty($this->link)) {
$context->buildViolation('Page link is required')->addViolation();
}
}
Performance: For large menus, optimize queries by:
parent_id and type columns.$qb->andWhere('m.published = :published')->setParameter('published', true);
Multi-Language Menus:
Extend the Menu entity to support translations:
#[ORM\Column(type: 'string', length: 255)]
private ?string $titleEn = null;
#[ORM\Column(type: 'string', length: 255)]
private ?string $titleFr = null;
Update getAvailableItems() in sources to filter by locale.
Menu Previews:
Add a preview field to the Menu entity and use it in the admin template:
<img src="{{ item.previewUrl }}" alt="{{ item.title }}" width="50">
Menu Events:
Listen for menu updates via Symfony events (e.g., aropixel.menu.pre_save):
// config/services.yaml
App\EventListener\MenuUpdateListener:
tags:
- { name: kernel.event_listener, event: aropixel.menu.pre_save, method: onMenuUpdate }
API Integration:
Expose menus via API using Symfony’s JsonResponse:
#[Route('/api/menus/{code}', name: 'api_menu_get', methods: ['GET'])]
public function getMenu(string $code, MenuHandler $handler): JsonResponse {
return new JsonResponse($handler->getMenuItems($code));
}
Testing:
Use the MenuHandler in PHPUnit tests to verify menu structure:
public function testMainMenuStructure() {
$handler = $this->container->get(MenuHandler::class);
$menu = $handler->getMenuItems('main');
$this->assertCount(3, $menu);
$this->assertEquals('Home',
How can I help you explore Laravel packages today?