diff --git a/composer.json b/composer.json
index 10bb5a6e..9364a7b1 100644
--- a/composer.json
+++ b/composer.json
@@ -20,7 +20,7 @@
],
"require": {
"php": "^8.1",
- "knplabs/knp-menu": "^3.6",
+ "knplabs/knp-menu": "^3.8",
"symfony/config": "^5.4 | ^6.0 | ^7.0",
"symfony/dependency-injection": "^5.4 | ^6.0 | ^7.0",
"symfony/deprecation-contracts": "^2.5 | ^3.3",
diff --git a/docs/events.rst b/docs/events.rst
index eed35cd9..aea639cb 100644
--- a/docs/events.rst
+++ b/docs/events.rst
@@ -13,39 +13,39 @@ to allow other parts of your application to add more stuff to it.
.. code-block:: php
- // src/Menu/MainBuilder.php
+ // src/Menu/MainBuilder.php
- namespace App\Menu;
+ namespace App\Menu;
- use App\Event\ConfigureMenuEvent;
- use Knp\Menu\FactoryInterface;
- use Symfony\Component\DependencyInjection\ContainerAwareInterface;
- use Symfony\Component\DependencyInjection\ContainerAwareTrait;
-
- class MainBuilder implements ContainerAwareInterface
- {
- use ContainerAwareTrait;
+ use App\Event\ConfigureMenuEvent;
+ use Knp\Menu\FactoryInterface;
+ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface:
- public function build(FactoryInterface $factory)
- {
- $menu = $factory->createItem('root');
+ class MainBuilder
+ {
+ private $factory;
+ private $eventDispatcher
- $menu->addChild('Dashboard', ['route' => '_acp_dashboard']);
+ public function __construct(FactoryInterface $factory, EventDispatcherInterface $eventDispatcher)
+ {
+ $this->factory = $factory;
+ $this->eventDispatcher = $eventDispatcher;
+ }
- $this->container->get('event_dispatcher')->dispatch(
- new ConfigureMenuEvent($factory, $menu),
- ConfigureMenuEvent::CONFIGURE
- );
+ public function build(array $options)
+ {
+ $menu = $this->factory->createItem('root');
- return $menu;
- }
- }
+ $menu->addChild('Dashboard', ['route' => '_acp_dashboard']);
-.. note::
+ $this->eventDispatcher->dispatch(
+ new ConfigureMenuEvent($this->factory, $menu),
+ ConfigureMenuEvent::CONFIGURE
+ );
- This implementation assumes you use the ``BuilderAliasProvider`` (getting
- your menu as ``App:MainBuilder:build``) but you could also define
- it as a service and inject the ``event_dispatcher`` service as a dependency.
+ return $menu;
+ }
+ }
Create the Event object
-----------------------
diff --git a/docs/index.rst b/docs/index.rst
index 41befe31..693a270e 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -113,114 +113,24 @@ Create your first menu!
There are two ways to create a menu: the "easy" way, and the more flexible
method of creating a menu as a service.
-Method a) The Easy Way (yay)!
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To create a menu, first create a new class in the ``Menu`` directory of one
-of your bundles. This class - called ``Builder`` in our example - will have
-one method for each menu that you need to build.
-
-An example builder class would look like this:
-
-.. code-block:: php
-
- // src/Menu/Builder.php
- namespace App\Menu;
-
- use App\Entity\Blog;
- use Knp\Menu\FactoryInterface;
- use Knp\Menu\ItemInterface;
-
- final class Builder
- {
- public function __construct(
- private EntityManagerInterface $em,
- ) {
- }
-
- public function mainMenu(FactoryInterface $factory, array $options): ItemInterface
- {
- $menu = $factory->createItem('root');
-
- $menu->addChild('Home', ['route' => 'homepage']);
-
- // findMostRecent and Blog are just imaginary examples
- $blog = $this->em->getRepository(Blog::class)->findMostRecent();
-
- $menu->addChild('Latest Blog Post', [
- 'route' => 'blog_show',
- 'routeParameters' => ['id' => $blog->getId()]
- ]);
-
- // create another menu item
- $menu->addChild('About Me', ['route' => 'about']);
- // you can also add sub levels to your menus as follows
- $menu['About Me']->addChild('Edit profile', ['route' => 'edit_profile']);
-
- // ... add more children
-
- return $menu;
- }
- }
-
-With the standard ``knp_menu.html.twig`` template and your current page being
-'Home', your menu would render with the following markup:
-
-.. code-block:: html
-
-
-
-.. note::
-
- The menu builder can be overwritten using the bundle inheritance.
-
-To actually render the menu, just do the following from anywhere in any template:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {{ knp_menu_render('App:Builder:mainMenu') }}
-
- .. code-block:: html+php
-
- render('App:Builder:mainMenu') ?>
-
-With this method, you refer to the menu using a three-part string:
-**bundle**:**class**:**method**.
-
-If you needed to create a second menu, you'd simply add another method to
-the ``Builder`` class (e.g. ``sidebarMenu``), build and return the new menu,
-then render it via ``App:Builder:sidebarMenu``.
-
-That's it! The menu is *very* configurable. For more details, see the
-`KnpMenu documentation`_.
-
-Method b) A menu builder as a service
+Method a) A menu builder as a service
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For information on how to register a menu builder as a service, read
:doc:`Creating Menu Builders as Services `.
-
-Method c) A menu as a service
+Method b) A menu as a service
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For information on how to register a service and tag it as a menu, read
:doc:`Creating Menus as Services `.
+Method c) A menu discovered by convention
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For information on how to use menu based on the bundle alias convention,
+read :doc:`Creating Menu via Naming Convention `.
+
.. note::
To improve performances, you can :doc:`disable providers you don't need `.
@@ -228,18 +138,17 @@ For information on how to register a service and tag it as a menu, read
Rendering Menus
---------------
-Once you've set up your menu, rendering it is easy. If you've used the "easy"
-way, then do the following:
+Once you've set up your menu, rendering it is easy.
.. configuration-block::
.. code-block:: html+jinja
- {{ knp_menu_render('App:Builder:mainMenu') }}
+ {{ knp_menu_render('my_main_menu') }}
.. code-block:: html+php
- render('App:Builder:mainMenu') ?>
+ render('my_main_menu') ?>
Additionally, you can pass some options to the renderer:
@@ -247,11 +156,11 @@ Additionally, you can pass some options to the renderer:
.. code-block:: html+jinja
- {{ knp_menu_render('App:Builder:mainMenu', {'depth': 2, 'currentAsLink': false}) }}
+ {{ knp_menu_render('my_main_menu', {'depth': 2, 'currentAsLink': false}) }}
.. code-block:: html+php
- render('App:Builder:mainMenu', [
+ render('my_main_menu', [
'depth' => 2,
'currentAsLink' => false,
]) ?>
@@ -265,12 +174,12 @@ You can also "get" a menu, which you can use to render later:
.. code-block:: html+jinja
- {% set menuItem = knp_menu_get('App:Builder:mainMenu') %}
+ {% set menuItem = knp_menu_get('my_main_menu') %}
{{ knp_menu_render(menuItem) }}
.. code-block:: html+php
- get('App:Builder:mainMenu') ?>
+ get('my_main_menu') ?>
render($menuItem) ?>
If you want to only retrieve a certain branch of the menu, you can do the
@@ -281,13 +190,13 @@ beneath it.
.. code-block:: html+jinja
- {% set menuItem = knp_menu_get('App:Builder:mainMenu', ['Contact']) %}
- {{ knp_menu_render(['App:Builder:mainMenu', 'Contact']) }}
+ {% set menuItem = knp_menu_get('my_main_menu', ['Contact']) %}
+ {{ knp_menu_render(['my_main_menu', 'Contact']) }}
.. code-block:: html+php
- get('App:Builder:mainMenu', ['Contact']) ?>
- render(['App:Builder:mainMenu', 'Contact']) ?>
+ get('my_main_menu', ['Contact']) ?>
+ render(['my_main_menu', 'Contact']) ?>
If you want to pass some options to the builder, you can use the third parameter
of the ``knp_menu_get`` function:
@@ -296,12 +205,12 @@ of the ``knp_menu_get`` function:
.. code-block:: html+jinja
- {% set menuItem = knp_menu_get('App:Builder:mainMenu', [], {'some_option': 'my_value'}) %}
+ {% set menuItem = knp_menu_get('my_main_menu', [], {'some_option': 'my_value'}) %}
{{ knp_menu_render(menuItem) }}
.. code-block:: html+php
- get('App:Builder:mainMenu', [], [
+ get('my_main_menu', [], [
'some_option' => 'my_value'
]) ?>
render($menuItem) ?>
@@ -314,6 +223,7 @@ More Advanced Stuff
menu_service
menu_builder_service
+ menu_convention
i18n
events
custom_renderer
diff --git a/docs/menu_builder_service.rst b/docs/menu_builder_service.rst
index 146fc904..a7cd3bc2 100644
--- a/docs/menu_builder_service.rst
+++ b/docs/menu_builder_service.rst
@@ -1,15 +1,6 @@
Creating Menu Builders as Services
==================================
-This bundle gives you a really convenient way to create menus by following
-a convention.
-
-However, if you want to, you can instead choose to create a service for your
-menu builder. The advantage of this method is that you can inject the exact
-dependencies that your menu builder needs.
-This can lead to code that is more testable and also potentially
-more reusable. The disadvantage is that it needs just a little more setup.
-
Start by creating a builder for your menu. You can stick as many menus into
a builder as you want, so you may only have one (or just a few) of these
builder classes in your application:
@@ -20,6 +11,7 @@ builder classes in your application:
namespace App\Menu;
+ use Knp\Menu\Attribute\AsMenuBuilder;
use Knp\Menu\FactoryInterface;
use Knp\Menu\ItemInterface;
@@ -35,6 +27,7 @@ builder classes in your application:
$this->factory = $factory;
}
+ #[AsMenuBuilder(name: 'main')] // The name is what is used to retrieve the menu
public function createMainMenu(array $options): ItemInterface
{
$menu = $this->factory->createItem('root');
@@ -46,7 +39,13 @@ builder classes in your application:
}
}
-Next, register your menu builder as service and register its ``createMainMenu`` method as a menu builder:
+That's it! The menu is *very* configurable. For more details, see the
+`KnpMenu documentation`_.
+
+Next, register your menu builder as service and register its ``createMainMenu``
+method as a menu builder. When using autoconfiguration, the ``#[AsMenuBuilder]``
+attribute takes care of it. When not using autoconfiguration, you need to
+register the menu builder by add the ``knp_menu.menu_builder`` tag:
.. code-block:: yaml
@@ -60,8 +59,7 @@ Next, register your menu builder as service and register its ``createMainMenu``
# ...
-You can now render the menu directly in a template via the name given in the
-``alias`` key above:
+You can now render the menu directly in a template via the its name:
.. code-block:: html+jinja
@@ -80,6 +78,7 @@ is simple! Start by adding a new method to your builder:
{
// ...
+ #[AsMenuBuilder(name: 'sidebar')]
public function createSidebarMenu(array $options): ItemInterface
{
$menu = $this->factory->createItem('sidebar');
@@ -94,25 +93,11 @@ is simple! Start by adding a new method to your builder:
}
}
-Now, create a service for *just* your new menu, giving it a new name, like
-``sidebar``:
-
-.. code-block:: yaml
-
- # config/services.yaml
- services:
- app.menu_builder:
- class: App\Menu\MenuBuilder
- arguments: ["@knp_menu.factory"]
- tags:
- - { name: knp_menu.menu_builder, method: createMainMenu, alias: main } # the previous menu
- - { name: knp_menu.menu_builder, method: createSidebarMenu, alias: sidebar } # Named "sidebar" this time
-
- # ...
-
It can now be rendered, just like the other menu:
.. code-block:: html+jinja
{% set menu = knp_menu_get('sidebar', [], {include_homepage: false}) %}
{{ knp_menu_render(menu) }}
+
+.. _`KnpMenu documentation`: https://github.com/KnpLabs/KnpMenu/blob/master/doc/01-Basic-Menus.md
diff --git a/docs/menu_convention.rst b/docs/menu_convention.rst
new file mode 100644
index 00000000..f71ec479
--- /dev/null
+++ b/docs/menu_convention.rst
@@ -0,0 +1,73 @@
+Creating Menu via Naming Convention
+===================================
+
+KnpMenuBundle supports building menus using a naming convention to find
+the class building the menu.
+
+.. warning::
+
+ The naming convention is relying on bundles. Project keeping their
+ code outside a bundle (like in the Flex skeleton) cannot use this
+ way of building menus.
+
+To create a menu, first create a new class in the ``Menu`` directory of one
+of your bundles. This class - called ``Builder`` in our example - will have
+one method for each menu that you need to build.
+The builder methods will receive the ``Knp\Menu\FactoryInterface`` as first
+argument and an array of options as second argument.
+
+An example builder class would look like this:
+
+.. code-block:: php
+
+ // src/AppBundle/Menu/Builder.php
+ namespace AppBundle\Menu;
+
+ use App\Entity\Blog;
+ use Knp\Menu\FactoryInterface;
+ use Knp\Menu\ItemInterface;
+
+ final class Builder
+ {
+ public function __construct(
+ private EntityManagerInterface $em,
+ ) {
+ }
+
+ public function mainMenu(FactoryInterface $factory, array $options): ItemInterface
+ {
+ $menu = $factory->createItem('root');
+
+ $menu->addChild('Home', ['route' => 'homepage']);
+
+ // findMostRecent and Blog are just imaginary examples
+ $blog = $this->em->getRepository(Blog::class)->findMostRecent();
+
+ $menu->addChild('Latest Blog Post', [
+ 'route' => 'blog_show',
+ 'routeParameters' => ['id' => $blog->getId()]
+ ]);
+
+ // create another menu item
+ $menu->addChild('About Me', ['route' => 'about']);
+ // you can also add sub levels to your menus as follows
+ $menu['About Me']->addChild('Edit profile', ['route' => 'edit_profile']);
+
+ // ... add more children
+
+ return $menu;
+ }
+ }
+
+With this method, you refer to the menu using a three-part string:
+**bundle**:**class**:**method**.
+
+If you needed to create a second menu, you'd simply add another method to
+the ``Builder`` class (e.g. ``sidebarMenu``), build and return the new menu,
+then render it via ``App:Builder:sidebarMenu``.
+
+You can now render the menu directly in a template via the its name:
+
+.. code-block:: html+jinja
+
+ {{ knp_menu_render('App:Builder:mainMenu') }}
diff --git a/docs/menu_service.rst b/docs/menu_service.rst
index d2c8a622..e9ecb058 100644
--- a/docs/menu_service.rst
+++ b/docs/menu_service.rst
@@ -12,15 +12,6 @@ Creating Menus as Services
It is recommended to register only :doc:`menu builders as services `
instead.
-This bundle gives you a really convenient way to create menus by following
-a convention.
-
-However, if you want to, you can instead choose to create a service for your
-menu object. The advantage of this method is that you can inject the exact
-dependencies that your menu needs.
-This can lead to code that is more testable and also potentially
-more reusable. The disadvantage is that it needs just a little more setup.
-
Start by creating a builder for your menu. You can stick as many menus into
a builder as you want, so you may only have one (or just a few) of these
builder classes in your application:
diff --git a/src/DependencyInjection/KnpMenuExtension.php b/src/DependencyInjection/KnpMenuExtension.php
index 339ff3aa..ef8cc347 100644
--- a/src/DependencyInjection/KnpMenuExtension.php
+++ b/src/DependencyInjection/KnpMenuExtension.php
@@ -2,10 +2,12 @@
namespace Knp\Bundle\MenuBundle\DependencyInjection;
+use Knp\Menu\Attribute\AsMenuBuilder;
use Knp\Menu\Factory\ExtensionInterface;
use Knp\Menu\ItemInterface;
use Knp\Menu\Matcher\Voter\VoterInterface;
use Symfony\Component\Config\FileLocator;
+use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
@@ -42,6 +44,12 @@ public function load(array $configs, ContainerBuilder $container): void
->addTag('knp_menu.voter');
$container->registerForAutoconfiguration(ExtensionInterface::class)
->addTag('knp_menu.factory_extension');
+ $container->registerAttributeForAutoconfiguration(AsMenuBuilder::class, static function (ChildDefinition $definition, AsMenuBuilder $attribute, \ReflectionMethod $reflectionMethod): void {
+ $definition->addTag('knp_menu.menu_builder', [
+ 'alias' => $attribute->name,
+ 'method' => $reflectionMethod->getName(),
+ ]);
+ });
}
public function getNamespace(): string