Rendering menus

Now that we have covered some theory about the menu system, it's time to get our hands dirty with some code. The first thing we will look at is how to work with menus programmatically with the view of rendering them in our module. For this, we will work with the default Administration menu that comes with Drupal core and has many links in it, at various levels. Note that the code we write in this section will not be included in the code repository.

Drupal core provides a block, called SystemMenuBlock, which can be used to render any menu inside a block. However, let's take a look at how we can do this ourselves instead.

The first thing we will need to do is get the MenuLinkTree service. We can inject it, or, if that's not possible, get it statically via the helper \Drupal class:

$menu_link_tree = \Drupal::menuTree(); 

Next, we will need to create a MenuTreeParameters object so that we can use it to load our menu tree. There are two ways we can do this. We can either create it ourselves and set our own options on it or we can get a default one based on the current route:

$parameters = $menu_link_tree->getCurrentRouteMenuTreeParameters('admin');  

Providing the name of a menu (in our case, "admin"), this method gives us a MenuTreeParameters instance with the following options set on it:

Essentially, this set of parameters gives us a tree within the context of the current route we are on. In other words, it will load all the root links in the menu and all the children of the root link that are in the active trail of the current route. It will leave out the children of the other root links.

You can, of course, further customize this set of parameters or create one from scratch. For example, if we want to load only the tree of a root link inside a menu, we could do it as follows:

$parameters = new MenuTreeParameters();
$parameters->setRoot($plugin_id);

In this example, $plugin_id is the ID of the menu link that should be at the root of the tree (defined in the YAML file or derived through a derivative).

I encourage you to look inside the MenuTreeParameters class and explore the other options you have for loading a tree.

For our example, we want to work with the entire menu tree of the Administration menu, so just instantiating a new MenuTreeParameters object will be enough, as we want to load all links in the menu. We can do this as follows:

$tree = $menu_link_tree->load('admin', $parameters); 

Now, we have an array of MenuLinkTreeElement objects inside the $tree variable, which contain, among others, the following:

However, it is important to note that notwithstanding any MenuTreeParameters we may have had, we are now sitting on top of all menu links in that menu, regardless of any access check. It is our responsibility to make sure that we don't render links to pages the user has no access to (as they will get a 403 error when they get there). To do this, we use the manipulators we discussed earlier, which are simple methods on a service.

The Drupal 8 menu system comes with a few default manipulators that can be found inside the DefaultMenuLinkTreeManipulators service. Most of the time, they will be sufficient for you:

If these are not enough, you can add your own manipulators as needed. All you have to do is define a service that has a public method and then reference it when transforming the tree. However, speaking of transforming, let's go ahead and use the access check manipulator to ensure that the current user has access to our tree links:

$manipulators = [
['callable' => 'menu.default_tree_manipulators:checkAccess']
];
$tree = $menu_link_tree->transform($tree, $manipulators);

As I mentioned earlier, we use the transform() method on the service and pass an array of callables. The latter are nothing more than the service name, followed by : and the method name to be used (like shown in the code above). So if you create your own service, you can reference it the same way.

Now, each MenuLinkTreeElement that remains in the tree has its access property filled with an instance of AccessResultInterface (a system of denoting access that we will talk more about in a later chapter). If the link is not accessible, it becomes an instance of InaccessibleMenuLink, so we know that we cannot render it, and even if we did render it, it will go to the home page rather than the 403.

Now, to render the tree, all we have to do is turn this tree into a render array:

$menu = $menu_link_tree->build($tree);  

Inside $menu, we now have a render array that uses the menu theme hook with a theme hook suggestion based on the menu name. So, in our case, it is menu__admin. Remember what these are from the previous chapter?

The menu theme hook will use the menu.html.twig (or menu--admin.html.twig if it exists inside a theme) file to render the menu links inside a simple, albeit hierarchical, HTML list.

As a quick recap from the theming chapter, at this point you have a few options for gaining full control over the output of the menu:

So, as you can see, you have many options. The choice you make depends on what you need to achieve, how happy you are with what the default markup is, and so on.