Меню-билдер в лучших традициях симфони лучше объявить как сервис.
Создаем сервис app.knp.sidebar_menu в файле config/packages/knp_menu.yaml:
Содержимое config/packages/knp_menu.yaml:
knp_menu:
twig:
template: custom_knp_menu.html.twig
parameters:
knp_menu.renderer.twig.options:
currentClass: sel
services:
app.knp.sidebar_menu:
class: Knp\Menu\MenuItem
factory: ['@App\Menu\MenuBuilder', createSidebarMenu]
arguments: ["@request_stack"]
tags:
- { name: knp_menu.menu, alias: sidebar_menu }
Естественно, в knp_menu.yaml кастомный темлейт (knp_menu.twig.template) и css-класс для активного меню (currentClass) - это все опционально. Если поведение вашего меню совпадает с настройками kmpMenu по умолчанию, то кастомный темлейт нет смысла определять.
alias - указывает имя меня, которое можно будет передать при вызове knp_menu_render.
Благодаря autowiring, в конструкторе MenuBuilder мы можем прописать все зависимости, не создавая явно сервисы под каждую из зависимостей. В данном случае я внедрил 2 зависимости в MenuBuilder: MenuFactory и CategoryRepository, не создавая никаких дополнительных сервисов для них в yaml-файлах.
Содержимое билдера src/Menu/MenuBuilder.php:
<?php
namespace App\Menu;
use App\Entity\Category;
use App\Repository\CategoryRepository;
use Knp\Menu\FactoryInterface;
use Knp\Menu\ItemInterface;
use Symfony\Component\HttpFoundation\RequestStack;
class MenuBuilder
{
/**
* @var FactoryInterface
*/
private $factory;
/**
* @var CategoryRepository
*/
private $categoryRepository;
/**
* @param FactoryInterface $factory
* @param CategoryRepository $categoryRepository
*/
public function __construct(
FactoryInterface $factory,
CategoryRepository $categoryRepository
) {
$this->factory = $factory;
$this->categoryRepository = $categoryRepository;
}
public function createSidebarMenu(RequestStack $requestStack): ItemInterface
{
$activeParentCategories = $this->categoryRepository
->findActiveParentCategories();
$menu = $this->factory
->createItem('root')
->setChildrenAttribute('class', 'vertical_nav categories_nav');
foreach ($activeParentCategories as $category) {
$categoryMenuItem = $this->createMenuItemByCategory($category);
$this->addCategoryChildrenMenu($category, $categoryMenuItem);
$menu->addChild($categoryMenuItem);
}
return $menu;
}
/**
* Create menu item using route whether for parent or child category as route is the same for both
*
* @param Category $category
* @return ItemInterface
*/
private function createMenuItemByCategory(Category $category): ItemInterface
{
return $this->factory->createItem($category->getName(), [
'route' => 'category_show',
'routeParameters' => ['category' => $category->getUri()],
]);
}
private function addCategoryChildrenMenu(Category $category, ItemInterface $categoryMenuItem): void
{
foreach ($category->getChildren() as $childCategory) {
if (!$childCategory->isVisibleInMenu()) {
continue;
}
$childCategoryMenuItem = $this->createMenuItemByCategory($childCategory);
$categoryMenuItem->addChild($childCategoryMenuItem);
}
}
}
В моем случае у пунктов меню верхнего уровня могут быть только дети первого уровня. У потомков (детей) не может быть потомков.
Для рендеринга меню в twig-шаблоне достаточно указать:
{{ knp_menu_render('sidebar_menu') }}
где sidebar_menu - алиас, указанный в сервисе app.knp.sidebar_menu в knp_menu.yaml.
В результате будет сгенерировано меню следующего вида:
-
Category 1
- SubCategory 1.1
- SubCategory 1.2
- SubCategory 1.3 - Category 2
-
Category 3
- SubCategory 3.1
- SubCategory 3.2
- SubCategory 3.3