Menu Bundle
Simple, small and fast library for building menus in/for Symfony.
Menu Items
You can create items either as a single stand-alone item, or directly as a child of another item:
use Becklyn\Menu\Item\MenuItem;
$parent = new MenuItem("parent");
$child = $parent->createChild("child");
// --------------- or ---------------
$parent = new MenuItem("parent");
$child = new MenuItem("child");
$parent->addChild($child);
If possible you should always create direct children instead of separate items that are later joined together.
Overview
An item can have several properties. You can set all of them either via a setter or via an array in the constructor / createChild()
.
The setters are named according to the names of the constructor options and implement a fluent interface.
All parameters are optional.
Parameter | Description |
---|---|
| All children of a single item are ordered by descending priority. The ordering is stable, so the order of the items with the same priority is preserved. |
| The (HTML) attributes of the list item. |
| The (HTML) attributes of the link / span tag. |
| The (HTML) attributes on the list tag of children. |
| Directly set a |
| The link target as URI. |
| The name of the route this URL should be generated to. Should not be used with |
| The parameters for generating the route. Is ignored if not used together with |
| Whether this item is visible. Any item without a label will be invisible, no matter of this setting. |
| Whether this is a current item. |
| Any payload that will be attached to the item (see Extras for details). |
| A key that helps finding the node in the tree (see Key for details). |
| A security expression that decides whether this node should be hidden (see Security for details). |
| Flag whether to sort the children of the item (see Sort for details). |
You should only use one of
target
oruri
orroute
(+routeParameters
)
The renderer will dynamically set additional list item, link and child attributes, for example to mark the current item or the level of the rendering. For these 3 properties there are convenience setters available to either set a single attribute directly or just add a class.
Target
You can set the target to either null
, a URL (string
) or a LazyRoute
.
The LazyRoute
will be transformed to a URL by the core visitor. If you don’t provide any
parameters and the route fails to generate due these missing parameters the item will automatically
get a target of null
as fallback.
This null
fallback eases building a robust menu tree, but affects the ancestors
voting of items.
Extras
You can set additional data on any item, that will later be available for example when rendering the node:
$item->setExtra("pageId", 75);
and then in the template:
<div data-page="{{ item.extra("pageId") }}">...</div>
If the generated menu will be cached inside your app (for performance reasons), the extras must be serializable.
Key
Each MenuItem has a find()
method, with which you can find a child (or the item itself) via key
value.
$parent = new MenuItem();
$child1 = $parent->addChild("child1", ["key" => "ohai"]);
$child2 = $parent->addChild("child2");
assert($child1 === $parent->find("ohai"));
The search is a depth first search, and will return the first match. So if the keys are not unique, the first matched item will be returned.
This can be combined to only render subtrees:
{{- menu_render(item.find("some-key")) -}}
Security
You can set a security expression on any item, that will later be evaluated. If the expression evaluates to
false
, the node will be hidden.
All features of Symfony’s Access Decision Manager are available.
Sort
The children of each item are sorted before rendering. You can toggle sorting of the children on/off for each item. The default value is false
.
If enabled, the children are sorted
- by descending priority – so the higher the priority, the earlier the item.
- items with the same priority are sorted (asc) alphabetically by label.
Rendering Menus
You first need to fetch the menu item, which should be rendered. It will only render the children of this item, not the item itself.
You can render in PHP:
use Becklyn\Menu\Renderer\MenuRenderer;
public function doSth (MenuRenderer $renderer)
{
$root = $this->getRoot();
$options = [];
return $renderer->render($root, $options);
}
and you can render in Twig:
{{- menu_render(item, options) -}}
Render Options
You can pass several options to the render function:
Option | Type | Default | Description |
---|---|---|---|
|
|
| The translation domain with which all item labels should be translated. Pass |
|
|
| The class the list item of the current item will get. |
|
|
| The class the list item of any anscestor of an active element will get. |
|
|
| The depth the menu should be rendered to. Pass |
|
|
| A key that will be passed to the visitors, with which they can decide whether they should be applied. |
|
|
| Convenience setter for the list class for the root element. |
Item Visitors
You can register custom visitors that will be called for every menu item and can modify them. The whole interface of the menu item can be used, so you can:
- add children
- set extras
- overwrite the label (for example manually translate the label with custom translation parameters)
- …
You need to implement the ItemVisitor
interface:
use Becklyn\Menu\Visitor\ItemVisitor;
class MyVisitor implements ItemVisitor
{
/**
* @inheritDoc
*/
public function visit (MenuItem $item, array $options) : void
{
$item->setExtra("randomNumber", random_int());
}
/**
* @inheritDoc
*/
public function supports (array $options) : bool
{
return true;
}
}
You can either let your service be autoconfigured (by just using the Interface and adding it to the container)
or add the tag manually: becklyn.menu.visitor
.
You can define a priority for your tag. Visitors with a higher priority will run prior to visitors with a lower priority.
App\Menu\MyVisitor:
tags:
- name: becklyn.menu.visitor
priority: 100
There are two core listeners, here with their priorities:
CoreVisitor
(-1000): applies the security and resolvesLazyRoute
s.VoterVisitor
(-1500): applies the voter to the item.
Keep the order of the visitors in mind: your own visitor should run before the voter visitor, if your changes might influence the result of the selected item voting.
Each visitor will be passed the render options. Your visitor must decide whether it supports the current
rendering according to the render options. If true
is returned, the visitor will be called for all
menu items, if false
is returned, the visitor will never be called.
The supports()
method will be called before each render process.
Item Voters
Voters are also called for every item and decide whether this item is active.
Items that you marked explicitly as current: true
either when building the tree or in custom visitors
will stay active and the voter decision will just be skipped.
You need to implement the interface:
use Becklyn\Menu\Voter\VoterInterface;
class MyVoter implements VoterInterface
{
/**
* @inheritDoc
*/
public function vote (MenuItem $item) : ?bool
{
// sorry not sorry
$item->setCurrent(random_int(0, 10) > 5);
}
}
Your voter can either decide the decision by returning true
(= is active) or false
(= is not active),
or it can abstain from the vote by returning null
.
Your voters should be as lenient as possible and abstain from the vote if they can’t be 100% certain.
There is one core voter, with priority 0
, that is automatically registered. It just checks that the route
of the current request matches the route in the item (it ignores items with no target / URL target, as well
as route parameters).
For that visitor to work, the core visitor will store the route of all LazyRoute
s (before transforming them to URLs)
in an extra attribute called _route
. This attribute will then be checked by the core voter to match the
current request route.
If you create custom items with a URL, you can set this extra key manually, to opt-in to matching this item to some requests.