# MenuBundle - Documentation

## Vue d'ensemble

Le **MenuBundle** est une implémentation moderne et simplifiée d'un système de gestion de menus pour Symfony. Il remplace le bundle `prodigious/sonata-menu-bundle` qui causait des problèmes de chargement de classes avec Doctrine et le système `resolve_target_entity`.

### Compatibilité

- ✅ **Symfony 4.4** avec PHP 7.4 (configuration actuelle)
- ✅ **Symfony 6.4** avec PHP 8.2 (migration future)
- ✅ Utilise des **annotations Doctrine** pour Symfony 4.4
- 🔄 Prêt pour la migration vers les **attributs PHP 8** pour Symfony 6.4

## Architecture

### Structure du bundle

```
src/MenuBundle/
├── Admin/
│   ├── MenuAdmin.php           # Administration Sonata pour les menus
│   └── MenuItemAdmin.php       # Administration Sonata pour les éléments
├── Entity/
│   ├── Menu.php                # Entité Menu (table: sonata_menu)
│   └── MenuItem.php            # Entité MenuItem (table: sonata_menu_item)
├── Manager/
│   └── MenuManager.php         # Gestionnaire des menus et éléments
├── Repository/
│   ├── MenuRepository.php      # Repository pour les menus
│   └── MenuItemRepository.php  # Repository pour les éléments
├── Twig/
│   └── MenuExtension.php       # Extension Twig pour afficher les menus
└── README.md                   # Cette documentation
```

### Principe de conception

**Différence clé avec prodigious/sonata-menu-bundle :**

- ❌ **Ancien bundle** : Utilisait des interfaces (`MenuInterface`, `MenuItemInterface`) avec `resolve_target_entity` de Doctrine, ce qui causait des problèmes de timing de chargement
- ✅ **Nouveau bundle** : Utilise directement les **classes concrètes** (`Menu`, `MenuItem`) sans interfaces, éliminant complètement les problèmes de `resolve_target_entity`

## Migration depuis prodigious/sonata-menu-bundle

### Étape 1 : Compatibilité des données

**Bonne nouvelle** : Les tables de base de données restent identiques !

- Table `sonata_menu` : Structure inchangée
- Table `sonata_menu_item` : Structure inchangée

Toutes vos données existantes sont **automatiquement compatibles**. Aucune migration de données n'est nécessaire.

### Étape 2 : Configuration (déjà effectuée)

#### services.yaml

Les services suivants ont été configurés avec des alias pour la rétrocompatibilité :

```yaml
# Nouveau service MenuManager
App\MenuBundle\Manager\MenuManager:
    arguments:
        $em: '@doctrine.orm.entity_manager'
        $menuRepository: '@App\MenuBundle\Repository\MenuRepository'
        $menuItemRepository: '@App\MenuBundle\Repository\MenuItemRepository'
    public: true

# Alias pour compatibilité
prodigious_sonata_menu.manager:
    alias: App\MenuBundle\Manager\MenuManager
    public: true

# Extension Twig
App\MenuBundle\Twig\MenuExtension:
    arguments:
        $menuManager: '@App\MenuBundle\Manager\MenuManager'
    tags:
        - { name: twig.extension }

# Admins Sonata
App\MenuBundle\Admin\MenuAdmin:
    arguments: [~, App\MenuBundle\Entity\Menu, ~]
    tags:
        - { name: sonata.admin, manager_type: orm, label: 'Menus', group: 'menu_builder' }

App\MenuBundle\Admin\MenuItemAdmin:
    arguments: [~, App\MenuBundle\Entity\MenuItem, ~]
    tags:
        - { name: sonata.admin, manager_type: orm, label: 'Éléments de menu', group: 'menu_builder' }
```

#### doctrine.yaml

Le mapping MenuBundle a été ajouté :

```yaml
doctrine:
    orm:
        entity_managers:
            default:
                mappings:
                    MenuBundle:
                        is_bundle: false
                        type: annotation
                        dir: '%kernel.project_dir%/src/MenuBundle/Entity'
                        prefix: 'App\MenuBundle\Entity'
                        alias: MenuBundle
```

#### sonata_admin.yaml

Le groupe d'administration a été mis à jour :

```yaml
sonata.admin.group.menu_builder:
    label: "Menus"
    icon: '<i class="fa fa-magic"></i>'
    items:
        - App\MenuBundle\Admin\MenuAdmin
        - App\MenuBundle\Admin\MenuItemAdmin
```

### Étape 3 : Code des templates (aucun changement requis)

Votre code Twig existant continue de fonctionner **exactement pareil** :

```twig
{# Obtenir les éléments du menu par alias #}
{% set menuItems = getMenuItemsFromAlias('main') %}

{# Afficher le menu avec le macro #}
{% import '@kohinos/common/menu_tree_macro.html.twig' as tree %}
{{ tree.menu(menuItems, currentPath) }}
```

### Étape 4 : Nettoyage (optionnel)

Une fois que tout fonctionne correctement, vous pouvez :

1. Supprimer les anciennes configurations temporaires dans `config/services.yaml` (lignes commentées)
2. Supprimer le répertoire `src/Prodigious/` si présent
3. Supprimer `src/DependencyInjection/Compiler/ForceMenuResolveTargetEntityPass.php` (n'est plus nécessaire)
4. Nettoyer les anciennes configurations dans `config/packages/doctrine.yaml` (resolve_target_entities pour MenuInterface/MenuItemInterface)

## Utilisation

### 1. Créer un menu via l'admin

1. Connectez-vous à l'administration Sonata (`/admin`)
2. Allez dans le groupe **"Menus"**
3. Cliquez sur **"Menus"** → **"Ajouter"**
4. Remplissez :
   - **Nom** : Nom affiché dans l'admin (ex: "Menu Principal")
   - **Alias** : Identifiant unique pour appeler le menu dans les templates (ex: "main")

### 2. Ajouter des éléments au menu

1. Dans l'admin, allez dans **"Menus"** → **"Éléments de menu"**
2. Cliquez sur **"Ajouter"**
3. Remplissez :
   - **Nom** : Texte affiché dans le menu
   - **URL** : Lien (absolu ou relatif, ex: `/page`, `/adherer`, `https://example.com`)
   - **Menu** : Sélectionnez le menu parent
   - **Parent** : (Optionnel) Pour créer un sous-menu
   - **Position** : Ordre d'affichage (plus petit = affiché en premier)
   - **Classe CSS** : (Optionnel) Classes CSS personnalisées
   - **Activé** : Cochez pour activer l'élément
   - **Ouvrir dans nouvel onglet** : Cochez pour `target="_blank"`

### 3. Afficher le menu dans un template Twig

```twig
{# Méthode 1 : Récupérer et afficher #}
{% set menuItems = getMenuItemsFromAlias('main') %}
{% if menuItems is not null and menuItems|length > 0 %}
    <ul>
    {% for item in menuItems %}
        <li>
            <a href="{{ item.url }}"
               {% if item.target %}target="_blank"{% endif %}
               {% if item.classAttribute %}class="{{ item.classAttribute }}"{% endif %}>
                {{ item.name }}
            </a>

            {# Afficher les enfants si présents #}
            {% if item.hasChild() %}
                <ul>
                {% for child in item.activeChildren %}
                    <li><a href="{{ child.url }}">{{ child.name }}</a></li>
                {% endfor %}
                </ul>
            {% endif %}
        </li>
    {% endfor %}
    </ul>
{% endif %}
```

### 4. Utiliser le MenuManager en PHP

```php
use App\MenuBundle\Manager\MenuManager;

class MyController
{
    public function __construct(private MenuManager $menuManager)
    {
    }

    public function myAction()
    {
        // Charger un menu par son alias
        $menu = $this->menuManager->loadByAlias('main');

        // Récupérer tous les éléments actifs du menu racine
        $rootItems = $this->menuManager->getRootItems($menu, MenuManager::STATUS_ENABLED);

        // Récupérer uniquement les éléments activés
        $enabledItems = $this->menuManager->getEnabledItems($menu);

        // Créer un nouveau menu
        $newMenu = new Menu();
        $newMenu->setName('Mon nouveau menu');
        $newMenu->setAlias('my-menu');
        $this->menuManager->save($newMenu);
    }
}
```

## API du MenuManager

### Constantes

```php
// Statuts d'activation
MenuManager::STATUS_ENABLED = true;    // Éléments activés uniquement
MenuManager::STATUS_DISABLED = false;  // Éléments désactivés uniquement
MenuManager::STATUS_ALL = null;        // Tous les éléments

// Types d'éléments
MenuManager::ITEM_ROOT = true;         // Éléments racines uniquement
MenuManager::ITEM_CHILD = false;       // Sous-éléments uniquement
MenuManager::ITEM_ALL = null;          // Tous les éléments
```

### Méthodes principales

```php
// Charger un menu
load(int $id): ?Menu
loadByAlias(string $alias): ?Menu
findAll(): Menu[]

// Sauvegarder/Supprimer
save(Menu $menu): void
remove(Menu $menu): void

// Récupérer les éléments avec filtres
getMenuItems(Menu $menu, $root = self::ITEM_ALL, $status = self::STATUS_ALL): MenuItem[]
getRootItems(Menu $menu, $status = self::STATUS_ALL): MenuItem[]
getEnabledItems(Menu $menu): MenuItem[]
getDisabledItems(Menu $menu): MenuItem[]

// Mettre à jour l'arborescence
updateMenuTree($menu, array $items, ?MenuItem $parent = null): bool
```

## Fonctions Twig disponibles

```twig
{# Récupérer les éléments d'un menu par son alias #}
{% set menuItems = getMenuItemsFromAlias('main') %}
{# Retourne un array de MenuItem activés et racines, ou null si le menu n'existe pas #}
```

## Méthodes des entités

### Menu

```php
getId(): ?int
getName(): ?string
setName(string $name): self
getAlias(): ?string
setAlias(string $alias): self
getMenuItems(): Collection<MenuItem>
addMenuItem(MenuItem $item): self
removeMenuItem(MenuItem $item): self
```

### MenuItem

```php
getId(): ?int
getName(): ?string
setName(string $name): self
getUrl(): ?string
setUrl(?string $url): self
getClassAttribute(): ?string
setClassAttribute(?string $class): self
getPosition(): ?int
setPosition(?int $position): self
getTarget(): ?bool
setTarget(?bool $target): self
getEnabled(): ?bool
setEnabled(?bool $enabled): self

// Relations
getParent(): ?MenuItem
setParent(?MenuItem $parent): self
getChildren(): Collection<MenuItem>
addChild(MenuItem $child): self
removeChild(MenuItem $child): self
getMenu(): ?Menu
setMenu(?Menu $menu): self

// Helpers
hasChild(): bool
hasParent(): bool
getActiveChildren(): MenuItem[]  // Retourne uniquement les enfants activés
```

## Migration vers Symfony 6.4 / PHP 8.2

Lorsque vous serez prêt à migrer vers Symfony 6.4 :

### 1. Convertir les annotations en attributs PHP 8

Les entités utilisent actuellement des annotations Doctrine. Pour Symfony 6.4, convertissez-les en attributs :

**Avant (annotations - actuel)** :
```php
/**
 * @ORM\Entity(repositoryClass="App\MenuBundle\Repository\MenuRepository")
 * @ORM\Table(name="sonata_menu")
 */
class Menu
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;
```

**Après (attributs PHP 8 - Symfony 6.4)** :
```php
#[ORM\Entity(repositoryClass: MenuRepository::class)]
#[ORM\Table(name: 'sonata_menu')]
class Menu
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private ?int $id = null;
```

### 2. Utiliser la promotion de propriétés dans les constructeurs

**Avant (PHP 7.4 - actuel)** :
```php
private $menuManager;

public function __construct(MenuManager $menuManager)
{
    $this->menuManager = $menuManager;
}
```

**Après (PHP 8 - Symfony 6.4)** :
```php
public function __construct(
    private MenuManager $menuManager
) {}
```

### 3. Mettre à jour doctrine.yaml

Changez le type de mapping de `annotation` à `attribute` :

```yaml
MenuBundle:
    is_bundle: false
    type: attribute  # ← Changé de "annotation" à "attribute"
    dir: '%kernel.project_dir%/src/MenuBundle/Entity'
    prefix: 'App\MenuBundle\Entity'
    alias: MenuBundle
```

## Dépannage

### Problème : "Could not find the entity manager for class Menu"

**Solution** : Vérifiez que le mapping MenuBundle est bien présent dans `config/packages/doctrine.yaml`

### Problème : "Unknown function getMenuItemsFromAlias"

**Solution** : Videz le cache : `php bin/console cache:clear`

### Problème : Menu n'apparaît pas dans l'admin

**Solution** : Vérifiez que les services Admin sont bien configurés dans `config/services.yaml` et que le groupe menu_builder est présent dans `config/packages/sonata_admin.yaml`

### Problème : Erreur "MenuInterface does not exist" (ancien bundle)

**Solution** : Ce problème ne devrait plus apparaître avec le nouveau MenuBundle. Si vous le voyez encore :
1. Vérifiez que vous utilisez bien `App\MenuBundle\` et non `Prodigious\Sonata\MenuBundle\`
2. Videz le cache complètement : `rm -rf var/cache/* && php bin/console cache:warmup`
3. Vérifiez qu'il n'y a pas d'anciennes références au bundle Prodigious dans votre code

## Avantages du nouveau MenuBundle

✅ **Pas d'interfaces** : Utilise directement des classes concrètes, évitant les problèmes de `resolve_target_entity`

✅ **Compatible dual-version** : Fonctionne sur Symfony 4.4 et sera facile à migrer vers 6.4

✅ **Code moderne** : Prêt pour PHP 8.2 avec annotations converties facilement en attributs

✅ **Rétrocompatible** : Les templates et le code existant continuent de fonctionner sans modification

✅ **Stable** : Pas de dépendances externes problématiques, contrôle total du code

✅ **Simplifié** : Code plus simple et maintenable que le bundle Prodigious

## Support

Pour toute question ou problème :
1. Consultez d'abord cette documentation
2. Vérifiez les logs Symfony dans `var/log/dev.log`
3. Utilisez `php bin/console debug:container menu` pour voir les services menu disponibles
4. Utilisez `php bin/console doctrine:mapping:info` pour vérifier que les entités sont bien mappées
