Skip to content

Commit 7924573

Browse files
committed
Add support for autoconfiguring menu builder with [AsMenuBuilder]
1 parent ba7ae2d commit 7924573

7 files changed

Lines changed: 142 additions & 175 deletions

File tree

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
],
2121
"require": {
2222
"php": "^8.1",
23-
"knplabs/knp-menu": "^3.6",
23+
"knplabs/knp-menu": "^3.8@dev",
2424
"symfony/config": "^5.4 | ^6.0 | ^7.0",
2525
"symfony/dependency-injection": "^5.4 | ^6.0 | ^7.0",
2626
"symfony/deprecation-contracts": "^2.5 | ^3.3",

docs/events.rst

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,39 +13,39 @@ to allow other parts of your application to add more stuff to it.
1313

1414
.. code-block:: php
1515
16-
// src/Menu/MainBuilder.php
16+
// src/Menu/MainBuilder.php
1717
18-
namespace App\Menu;
18+
namespace App\Menu;
1919
20-
use App\Event\ConfigureMenuEvent;
21-
use Knp\Menu\FactoryInterface;
22-
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
23-
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
24-
25-
class MainBuilder implements ContainerAwareInterface
26-
{
27-
use ContainerAwareTrait;
20+
use App\Event\ConfigureMenuEvent;
21+
use Knp\Menu\FactoryInterface;
22+
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface:
2823
29-
public function build(FactoryInterface $factory)
30-
{
31-
$menu = $factory->createItem('root');
24+
class MainBuilder
25+
{
26+
private $factory;
27+
private $eventDispatcher
3228
33-
$menu->addChild('Dashboard', ['route' => '_acp_dashboard']);
29+
public function __construct(FactoryInterface $factory, EventDispatcherInterface $eventDispatcher)
30+
{
31+
$this->factory = $factory;
32+
$this->eventDispatcher = $eventDispatcher;
33+
}
3434
35-
$this->container->get('event_dispatcher')->dispatch(
36-
new ConfigureMenuEvent($factory, $menu),
37-
ConfigureMenuEvent::CONFIGURE
38-
);
35+
public function build(array $options)
36+
{
37+
$menu = $this->factory->createItem('root');
3938
40-
return $menu;
41-
}
42-
}
39+
$menu->addChild('Dashboard', ['route' => '_acp_dashboard']);
4340
44-
.. note::
41+
$this->eventDispatcher->dispatch(
42+
new ConfigureMenuEvent($this->factory, $menu),
43+
ConfigureMenuEvent::CONFIGURE
44+
);
4545
46-
This implementation assumes you use the ``BuilderAliasProvider`` (getting
47-
your menu as ``App:MainBuilder:build``) but you could also define
48-
it as a service and inject the ``event_dispatcher`` service as a dependency.
46+
return $menu;
47+
}
48+
}
4949
5050
Create the Event object
5151
-----------------------

docs/index.rst

Lines changed: 22 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -113,145 +113,54 @@ Create your first menu!
113113
There are two ways to create a menu: the "easy" way, and the more flexible
114114
method of creating a menu as a service.
115115

116-
Method a) The Easy Way (yay)!
117-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
118-
119-
To create a menu, first create a new class in the ``Menu`` directory of one
120-
of your bundles. This class - called ``Builder`` in our example - will have
121-
one method for each menu that you need to build.
122-
123-
An example builder class would look like this:
124-
125-
.. code-block:: php
126-
127-
// src/Menu/Builder.php
128-
namespace App\Menu;
129-
130-
use App\Entity\Blog;
131-
use Knp\Menu\FactoryInterface;
132-
use Knp\Menu\ItemInterface;
133-
134-
final class Builder
135-
{
136-
public function __construct(
137-
private EntityManagerInterface $em,
138-
) {
139-
}
140-
141-
public function mainMenu(FactoryInterface $factory, array $options): ItemInterface
142-
{
143-
$menu = $factory->createItem('root');
144-
145-
$menu->addChild('Home', ['route' => 'homepage']);
146-
147-
// findMostRecent and Blog are just imaginary examples
148-
$blog = $this->em->getRepository(Blog::class)->findMostRecent();
149-
150-
$menu->addChild('Latest Blog Post', [
151-
'route' => 'blog_show',
152-
'routeParameters' => ['id' => $blog->getId()]
153-
]);
154-
155-
// create another menu item
156-
$menu->addChild('About Me', ['route' => 'about']);
157-
// you can also add sub levels to your menus as follows
158-
$menu['About Me']->addChild('Edit profile', ['route' => 'edit_profile']);
159-
160-
// ... add more children
161-
162-
return $menu;
163-
}
164-
}
165-
166-
With the standard ``knp_menu.html.twig`` template and your current page being
167-
'Home', your menu would render with the following markup:
168-
169-
.. code-block:: html
170-
171-
<ul>
172-
<li class="current first">
173-
<a href="#route_to/homepage">Home</a>
174-
</li>
175-
<li class="current_ancestor">
176-
<a href="#route_to/page_show/?id=42">About Me</a>
177-
<ul class="menu_level_1">
178-
<li class="current first last">
179-
<a href="#route_to/edit_profile">Edit profile</a>
180-
</li>
181-
</ul>
182-
</li>
183-
</ul>
184-
185-
.. note::
186-
187-
The menu builder can be overwritten using the bundle inheritance.
188-
189-
To actually render the menu, just do the following from anywhere in any template:
190-
191-
.. configuration-block::
192-
193-
.. code-block:: html+jinja
194-
195-
{{ knp_menu_render('App:Builder:mainMenu') }}
196-
197-
.. code-block:: html+php
198-
199-
<?php echo $view['knp_menu']->render('App:Builder:mainMenu') ?>
200-
201-
With this method, you refer to the menu using a three-part string:
202-
**bundle**:**class**:**method**.
203-
204-
If you needed to create a second menu, you'd simply add another method to
205-
the ``Builder`` class (e.g. ``sidebarMenu``), build and return the new menu,
206-
then render it via ``App:Builder:sidebarMenu``.
207-
208-
That's it! The menu is *very* configurable. For more details, see the
209-
`KnpMenu documentation`_.
210-
211-
Method b) A menu builder as a service
116+
Method a) A menu builder as a service
212117
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
213118

214119
For information on how to register a menu builder as a service, read
215120
:doc:`Creating Menu Builders as Services <menu_builder_service>`.
216121

217-
218-
Method c) A menu as a service
122+
Method b) A menu as a service
219123
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
220124

221125
For information on how to register a service and tag it as a menu, read
222126
:doc:`Creating Menus as Services <menu_service>`.
223127

128+
Method c) A menu discovered by convention
129+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
130+
131+
For information on how to use menu based on the bundle alias convention,
132+
read :doc:`Creating Menu via Naming Convention <menu_convention>`.
133+
224134
.. note::
225135

226136
To improve performances, you can :doc:`disable providers you don't need <disabling_providers>`.
227137

228138
Rendering Menus
229139
---------------
230140

231-
Once you've set up your menu, rendering it is easy. If you've used the "easy"
232-
way, then do the following:
141+
Once you've set up your menu, rendering it is easy.
233142

234143
.. configuration-block::
235144

236145
.. code-block:: html+jinja
237146

238-
{{ knp_menu_render('App:Builder:mainMenu') }}
147+
{{ knp_menu_render('my_main_menu') }}
239148

240149
.. code-block:: html+php
241150

242-
<?php echo $view['knp_menu']->render('App:Builder:mainMenu') ?>
151+
<?php echo $view['knp_menu']->render('my_main_menu') ?>
243152

244153
Additionally, you can pass some options to the renderer:
245154

246155
.. configuration-block::
247156

248157
.. code-block:: html+jinja
249158

250-
{{ knp_menu_render('App:Builder:mainMenu', {'depth': 2, 'currentAsLink': false}) }}
159+
{{ knp_menu_render('my_main_menu', {'depth': 2, 'currentAsLink': false}) }}
251160

252161
.. code-block:: html+php
253162

254-
<?php echo $view['knp_menu']->render('App:Builder:mainMenu', [
163+
<?php echo $view['knp_menu']->render('my_main_menu', [
255164
'depth' => 2,
256165
'currentAsLink' => false,
257166
]) ?>
@@ -265,12 +174,12 @@ You can also "get" a menu, which you can use to render later:
265174

266175
.. code-block:: html+jinja
267176

268-
{% set menuItem = knp_menu_get('App:Builder:mainMenu') %}
177+
{% set menuItem = knp_menu_get('my_main_menu') %}
269178
{{ knp_menu_render(menuItem) }}
270179

271180
.. code-block:: html+php
272181

273-
<?php $menuItem = $view['knp_menu']->get('App:Builder:mainMenu') ?>
182+
<?php $menuItem = $view['knp_menu']->get('my_main_menu') ?>
274183
<?php echo $view['knp_menu']->render($menuItem) ?>
275184

276185
If you want to only retrieve a certain branch of the menu, you can do the
@@ -281,13 +190,13 @@ beneath it.
281190

282191
.. code-block:: html+jinja
283192

284-
{% set menuItem = knp_menu_get('App:Builder:mainMenu', ['Contact']) %}
285-
{{ knp_menu_render(['App:Builder:mainMenu', 'Contact']) }}
193+
{% set menuItem = knp_menu_get('my_main_menu', ['Contact']) %}
194+
{{ knp_menu_render(['my_main_menu', 'Contact']) }}
286195

287196
.. code-block:: html+php
288197

289-
<?php $menuItem = $view['knp_menu']->get('App:Builder:mainMenu', ['Contact']) ?>
290-
<?php echo $view['knp_menu']->render(['App:Builder:mainMenu', 'Contact']) ?>
198+
<?php $menuItem = $view['knp_menu']->get('my_main_menu', ['Contact']) ?>
199+
<?php echo $view['knp_menu']->render(['my_main_menu', 'Contact']) ?>
291200

292201
If you want to pass some options to the builder, you can use the third parameter
293202
of the ``knp_menu_get`` function:
@@ -296,12 +205,12 @@ of the ``knp_menu_get`` function:
296205

297206
.. code-block:: html+jinja
298207

299-
{% set menuItem = knp_menu_get('App:Builder:mainMenu', [], {'some_option': 'my_value'}) %}
208+
{% set menuItem = knp_menu_get('my_main_menu', [], {'some_option': 'my_value'}) %}
300209
{{ knp_menu_render(menuItem) }}
301210

302211
.. code-block:: html+php
303212

304-
<?php $menuItem = $view['knp_menu']->get('App:Builder:mainMenu', [], [
213+
<?php $menuItem = $view['knp_menu']->get('my_main_menu', [], [
305214
'some_option' => 'my_value'
306215
]) ?>
307216
<?php echo $view['knp_menu']->render($menuItem) ?>
@@ -314,6 +223,7 @@ More Advanced Stuff
314223

315224
menu_service
316225
menu_builder_service
226+
menu_convention
317227
i18n
318228
events
319229
custom_renderer

docs/menu_builder_service.rst

Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,6 @@
11
Creating Menu Builders as Services
22
==================================
33

4-
This bundle gives you a really convenient way to create menus by following
5-
a convention.
6-
7-
However, if you want to, you can instead choose to create a service for your
8-
menu builder. The advantage of this method is that you can inject the exact
9-
dependencies that your menu builder needs.
10-
This can lead to code that is more testable and also potentially
11-
more reusable. The disadvantage is that it needs just a little more setup.
12-
134
Start by creating a builder for your menu. You can stick as many menus into
145
a builder as you want, so you may only have one (or just a few) of these
156
builder classes in your application:
@@ -20,6 +11,7 @@ builder classes in your application:
2011
2112
namespace App\Menu;
2213
14+
use Knp\Menu\Attribute\AsMenuBuilder;
2315
use Knp\Menu\FactoryInterface;
2416
use Knp\Menu\ItemInterface;
2517
@@ -35,6 +27,7 @@ builder classes in your application:
3527
$this->factory = $factory;
3628
}
3729
30+
#[AsMenuBuilder(name: 'main')] // The name is what is used to retrieve the menu
3831
public function createMainMenu(array $options): ItemInterface
3932
{
4033
$menu = $this->factory->createItem('root');
@@ -46,7 +39,13 @@ builder classes in your application:
4639
}
4740
}
4841
49-
Next, register your menu builder as service and register its ``createMainMenu`` method as a menu builder:
42+
That's it! The menu is *very* configurable. For more details, see the
43+
`KnpMenu documentation`_.
44+
45+
Next, register your menu builder as service and register its ``createMainMenu``
46+
method as a menu builder. When using autoconfiguration, the ``#[AsMenuBuilder]``
47+
attribute takes care of it. When not using autoconfiguration, you need to
48+
register the menu builder by add the ``knp_menu.menu_builder`` tag:
5049

5150
.. code-block:: yaml
5251
@@ -60,8 +59,7 @@ Next, register your menu builder as service and register its ``createMainMenu``
6059
6160
# ...
6261
63-
You can now render the menu directly in a template via the name given in the
64-
``alias`` key above:
62+
You can now render the menu directly in a template via the its name:
6563

6664
.. code-block:: html+jinja
6765

@@ -80,6 +78,7 @@ is simple! Start by adding a new method to your builder:
8078
{
8179
// ...
8280
81+
#[AsMenuBuilder(name: 'sidebar')]
8382
public function createSidebarMenu(array $options): ItemInterface
8483
{
8584
$menu = $this->factory->createItem('sidebar');
@@ -94,25 +93,11 @@ is simple! Start by adding a new method to your builder:
9493
}
9594
}
9695
97-
Now, create a service for *just* your new menu, giving it a new name, like
98-
``sidebar``:
99-
100-
.. code-block:: yaml
101-
102-
# config/services.yaml
103-
services:
104-
app.menu_builder:
105-
class: App\Menu\MenuBuilder
106-
arguments: ["@knp_menu.factory"]
107-
tags:
108-
- { name: knp_menu.menu_builder, method: createMainMenu, alias: main } # the previous menu
109-
- { name: knp_menu.menu_builder, method: createSidebarMenu, alias: sidebar } # Named "sidebar" this time
110-
111-
# ...
112-
11396
It can now be rendered, just like the other menu:
11497

11598
.. code-block:: html+jinja
11699

117100
{% set menu = knp_menu_get('sidebar', [], {include_homepage: false}) %}
118101
{{ knp_menu_render(menu) }}
102+
103+
.. _`KnpMenu documentation`: https://github.com/KnpLabs/KnpMenu/blob/master/doc/01-Basic-Menus.md

0 commit comments

Comments
 (0)