Menu API overview and related reading
For detail on Drupal 7 to Drupal 8 menu updates, see handbook pages: D7 to D8 upgrade tutorial: Convert hook_menu() to Drupal 8 APIs and Menu API in Drupal 8.
Related change records:
- All functionality of hook_menu() is replaced by new systems for routing, menu links, local tasks, actions and contextual links
- menu_get_active_trail(), menu_set_active_trail(), and menu_link_get_preferred() removed
- Menu tree building is now a service
- The default breadcrumb builder is path-based and decoupled from menu links
Overview
- Define an extensible framework for different types of links, with a plugin type as a common facade
- The developer-facing APi is a plugin manager that also handles building trees into render arrays and access checks. The tree storage is a separate service that hides the implementation details of efficient hierarchy handing. tree and tree storage handle menu cacheing.
- Manages links defined in YAML and links defined by views
- Full l10n support for localizing the D8 admin interface
- i18n support via config translation for translating the links provided by Views.
- A NG content entity whose base table stores the plugin definition
- The custom menu link entity is defined as being fieldable
- Link title is the (translatable) entity label
- Menu link content entity can be added/edited via the node form (substituting for existing functionality)
Code examples
Whenever a menu link's ID needs to be retrieved, use $menu_link->getPluginId().
Loading menu links
D7
$menu_link = menu_link_load($mlid);
D8
$menu_link = \Drupal::service('plugin.manager.menu.link')->createInstance($id);
Note that loading a menu link through menu_link_load() or entity_load() does not return an access-checked menu link anymore. In order to check access use the getUrl() method and the access manager.
Loading menu links with conditions
D7
$links = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC))
->leftJoin('menu_router', 'm', 'm.path = ml.router_path')
->fields('ml')
// Weight must be taken from {menu_links}, not {menu_router}.
->addField('ml', 'weight', 'link_weight')
->fields('m')
->condition('ml.menu_name', $menu_name)
->execute()
->fetchAll();
foreach ($links as &$link) {
$link['options'] = unserialize($link['options']);
}
D8
You can either use the really simple methods of the plugin manager, which allows to load links by ID (createInstance)
or by route name and parameters \Drupal::service('plugin.manager.menu.link')->loadLinksByRoute($route_name, $route_parameters);
For other usecases you most often want to retrieve menu links as part of the menu hierarchy as a menu tree.
Therefore Drupal provides the "menu.tree" service. The generic load method should be used to retrieve a menu tree.
The load method expects the menu name as well as an object called MenuTreeParameters.
So for example if you want to have level 2 and 3 of the menu tree you would do something like the following:
$menu_tree_parameters = new MenuTreeParameters();
$menu_tree_parameters->minDepth = 2;
$menu_tree_parameters->maxDepth = 3;
$tree = \Drupal::menuTree()->load('test_menu', $menu_tree_parameters);
If you need to fetch all menu links, just choose no additional values on the $menu_tree_parameters.
Creating new menu links
D7
$item = array(
'link_title' => t('Home'),
'link_path' => '<front>',
'menu_name' => 'main-menu',
);
menu_link_save($item);
D8
By default menu links, like all plugins, cannot be created by code, just be defined via code: here a links.menu.yml file. (see other change notices).
In case you really need to save new menu links via code, on option is that you can create a 'menu_link_content' entity:
// Special paths like '<front>' can be created using the route scheme.
$item = entity_create('menu_link_content', array(
'link' => [['uri' => 'route:<front>']],
'title' => 'Front',
'menu_name' => 'main',
));
$item->save();
// Using a path alias 'people' uses the user-path scheme.
$item = entity_create('menu_link_content', array(
'link' => [['uri' => 'user-path:/people']],
'title' => 'People',
'menu_name' => 'main',
));
$item->save();
// To attach to an entity using format "entity:{entity_type}/{entity_id}".
// In this example we use a node object to get the title and ID.
$item = entity_create('menu_link_content', array(
'link' => [['uri' => 'entity:node/' . $node->id()]],
'title' => $node->title,
'menu_name' => 'main',
));
$item->save();
// Using a route defined in a module.routing.yml file with format "route:{route_name};{route_parameters}"
// keep in mind that parameters must be provided as name=value. Multiple parameters can be separated
// with & within the {route_parameters} section.
$item = entity_create('menu_link_content', array(
'link' => [['uri' => 'route:node.add;node_type=page']],
'title' => 'Add a page',
'menu_name' => 'main',
));
$item->save();
As an alternative, your module can define menu link plugin derivatives. See Views module.
Saving menu links
D7
$result = db_query("SELECT * FROM {menu_links} WHERE link_path = :link_path", array(':link_path' => $link_path))->fetchAll(PDO::FETCH_ASSOC);
foreach ($result as $link) {
$link['link_title'] = t('New link title');
$link['options'] = unserialize($link['options']);
menu_link_save($link);
}
D8
$menu_link_manager = \Drupal::service('plugin.manager.menu.link');
$menu_links = $menu_link_manager->loadLinksByRoute('example_route');
foreach ($menu_links as $menu_link) {
$definition = $menu_link->getPluginDefinition();
$definition['title'] = 'new link title';
$menu_link_manager->updateDefinition($menu_link->getPluginId(), $definition);
}
Menu bundles
Although menu link content entities have a single default bundle, contrib may add more.
Note: In constrast to Drupal 7 neither shortcuts nor books are menu links.
Removed API constants, functions, and hooks
Here's a summary of all Drupal 7.x functions, constants, and hooks that will need to be updated in modules:
Menu-link related constants removed since 7.x:
| D7 | D8 |
|---|---|
| MENU_MAX_DEPTH | Defined per storage; see MenuLinkTreeInterface::maxDepth() to retrieve the value or MenuTreeStorage::MAX_DEPTH for the default storage max depth. |
| MENU_VISIBLE_IN_BREADCRUMB | Breadcrumbs are no longer defined as menu links; see The default breadcrumb builder is path-based and decoupled from menu links |
MENU_NORMAL_ITEM
MENU_SUGGESTED_ITEM
MENU_PREFERRED_LINK
Internal flags with no replacement
MENU_LINKS_TO_PARENT
MENU_VISIBLE_IN_TREE
MENU_IS_ROOT
hooks removed or replaced since 7.x
Content link CRUD hooks
| D7 | D8 |
|---|---|
| hook_menu_link_alter(&$item) | hook_menu_link_content_presave($entity) (hook_ENTITY_TYPE_presave()) |
| hook_menu_link_insert($link) | hook_menu_link_content_insert($entity) (hook_ENTITY_TYPE_insert()) |
| hook_menu_link_update($link) | hook_menu_link_content_update($entity) (hook_ENTITY_TYPE_update()) |
| hook_menu_link_delete($link) | hook_menu_link_content_delete($entity) (hook_ENTITY_TYPE_delete()) |
No direct replacement
hook_translated_menu_link_alter(&$item, $map)
Functions removed or replaced since 7.x
Covered by: menu_get_active_trail(), menu_set_active_trail(), and menu_link_get_preferred() removed
menu_set_active_item($path)
menu_link_get_preferred($path = NULL, $selected_menu = NULL)
Covered by: Menu tree building is now a service
menu_tree($menu_name)
menu_tree_output($tree)
menu_tree_all_data($menu_name, $link = NULL, $max_depth = NULL)
menu_tree_set_path($menu_name, $path = NULL)
menu_tree_get_path($menu_name)
menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail = FALSE)
menu_build_tree($menu_name, array $parameters = array())
menu_tree_collect_node_links(&$tree, &$node_links)
menu_tree_check_access(&$tree, $node_links = array())
menu_tree_data(array $links, array $parents = array(), $depth = 1)
Other API functions
| D7 | D8 |
|---|---|
| menu_link_load($mlid) | MenuLinkManagerInterface::createInstance() |
| menu_link_save(&$item, $existing_item = array(), $parent_candidates = array()) | For menu_link_content entities, use EntityInterface::save(). |
| menu_links_clone($links, $menu_name = NULL) | No replacement (was intended for internal use). |
| menu_load_links($menu_name) | No direct replacement; depends on usecase. Consider MenuTreeStorageInterface::loadByProperties() or MenuLinkTreeInterface::load() |
| menu_delete_links($menu_name) | MenuLinkManagerInterface::deleteLinksInMenu() |
| menu_link_delete($mlid, $path = NULL) | For menu_link_content entities, use EntityInterface::delete(). To remove link plugin definitions from the storage generally, use MenuLinkManagerInterface::removeDefinition(). |
| menu_link_maintain($module, $op, $link_path, $link_title) | No direct replacement. Use the MenuLinkManagerInterface. |
| menu_link_children_relative_depth($item) | MenuTreeStorageInterface::getSubtreeHeight() |
| menu_parent_options($menus, $item, $type = '') | MenuParentFormSelectorInterface::getParentSelectOptions() |
| menu_reset_item($link) | MenuLinkManagerInterface::resetLink() |
Page and form callbacks removed
menu_edit_item($form, &$form_state, $type, $item, $menu)
menu_edit_item_validate($form, &$form_state)
menu_item_delete_submit($form, &$form_state)
menu_edit_item_submit($form, &$form_state)
menu_item_delete_form($form, &$form_state, $item)
menu_item_delete_form_submit($form, &$form_state)
menu_reset_item_confirm($form, &$form_state, $item)
menu_reset_item_confirm_submit($form, &$form_state)
menu_item_delete_page($item)
(All have been replaced by Drupal 8 page and form controllers, or entity forms.)
Comments
Getting the link data
There is a useful code sample for getting links out of a menu in Drupal 8: https://www.drupal.org/node/2840180#comment-11849265