KNPMenuBundle est un Bundle qui permet de gérer assez facilement les menus d'un site web. Faire un menu statique est très simple et expliqué clairement dans la documentation. Mais parfois, le développeur a besoin de construire un menu (ou un sous-menu) en fonction de contenus en base de données. Ce billet va donc vous expliquer comment faire cela.
Le contexte
Nous allons prendre un exemple simple pour illustrer ce billet : j'ai une entité Produit avec les attributs nom
et categorie
, et une entité Categorie avec les attributs nom
et produits
. Chaque produit est relié à une catégorie.
Notre objectif est de construire un menu qui liste les catégories, et envoie l'utilisateur vers une page listant les produits de la catégorie sélectionnée.
Nous partons du principe que nous avons déjà un contrôleur qui s'occupe de lister les produits d'une catégorie. Sa route s'appelle liste_produits_categorie
et elle attend un paramètre id
, qui est l'identifiant de la catégorie.
Création de la classe Builder
Pour construire notre menu, il nous faut une classe Builder. Voici donc notre classe Builder par défaut :
use Knp\Menu\FactoryInterface;
class Builder
{
public function createMainMenu(FactoryInterface $factory,
array $option)
{
$menu = $factory->createItem('root');
$menu->addChild(
'categorie',
array('label' => 'Nos produits par catégories')
);
// D'autres menus ensuite...
return $menu;
}
}
Notre objectif maintenant est de pouvoir utiliser le service Doctrine pour pouvoir faire une requête dans notre classe Builder. Pour cela, il va donc falloir transformer notre Builder en service. Ça tombe bien, la procédure est décrite dans la documentation (et en plus, l'utilisation des menus en sera aussi simplifiée).
Convertir notre classe Builder en service Symfony2
Premièrement, la documentation nous indique qu'il va falloir récupérer l'objet implémentant FactoryInterface
dans le constructeur car ce dernier ne sera plus passé en argument auprès de notre fonction createMainMenu()
. Dans notre cas, nous allons nous aussi avoir besoin d'un autre service : l'Entity Manager de Doctrine. Transformons donc notre classe ainsi :
use Knp\Menu\FactoryInterface;
use Doctrine\ORM\Entitymanager;
class Builder
{
private $factory;
private $em;
public function __construct(FactoryInterface $factory,
Entitymanager $em)
{
$this->factory = $factory;
$this->em = $em;
}
public function createMainMenu()
{
$menu = $this->factory->createItem('root');
$menu->addChild(
'categorie',
array('label' => 'Nos produits par catégories')
);
// D'autres menus ensuite...
return $menu;
}
}
Déclarer les services dans la configuration de l'application
Il nous faut maintenant déclarer nos services auprès de Symfony2. Cela se fait dans le fichier services.yml
de votre bundle, ou bien directement dans le fichier app/config/config.yml
.
services:
acme_main.menu_builder:
class: Acme\MainBundle\Menu\Builder
arguments: ['@knp_menu.factory', '@doctrine.orm.entity_manager']
acme_main.menu.main:
class: Knp\Menu\MenuItem
factory_service: acme_main.menu_builder
factory_method: createMainMenu
tags:
- { name: knp_menu.menu, alias: main }
On voit bien ici que notre builder attend deux paramètres : le service Factory
de KnpMenu ainsi que le service EntityManager
de Doctrine. Notre builder est donc déclaré en tant que service.
Le deuxième service concerne le menu principal. En effet, on le déclare aussi comme service pour faciliter son utilisation dans les vues. Ce service fait appel à notre service Builder, et on précise la fonction appelée : createMainMenu
. De cette manière, vous pouvez créer plusieurs menus au sein de votre classe Builder. Il vous suffit de déclarer deux services Menu, puis d'indiquer la bonne fonction pour l'option factory_method
.
Voilà, tout est prêt maintenant pour construire notre menu en extrayant les informations de la base de données.
Construction du menu
Pour cela, rien de plus simple : nous avons l'EntityManager à notre disposition.
use Knp\Menu\FactoryInterface;
use Doctrine\ORM\Entitymanager;
class Builder
{
private $factory;
private $em;
public function __construct(FactoryInterface $factory,
Entitymanager $em)
{
$this->factory = $factory;
$this->em = $em;
}
public function createMainMenu()
{
$menu = $this->factory->createItem('root');
$menu->addChild(
'categorie',
array('label' => 'Nos produits par catégories')
);
// Récupération de la liste des catégories.
$listeCategories = $this->em->getRepository('AcmeMainBundle:Categorie')->find();
// Création des sous-menus.
foreach ($listeCategories as $categorie)
{
$menu['categorie']->addChild(
'categorie_' . $categorie->getId(), // Identifiant du menu.
array(
'label' => $categorie->getNom();
'route' => 'liste_produits_categorie'
'routeParameters' => array('id' => $categorie->getId()),
)
);
}
// D'autres menus ensuite...
return $menu;
}
}
Et voilà donc notre menu construit avec toutes les catégories présentes en base de données. Pour l'utiliser dans vos vues, il suffit simplement d'ajouter :
{{ knp_menu_render('main') }}
À noter que main
représente l'alias que nous avons donné à notre menu dans la configuration des services.