Change record status: 
Project: 
Introduced in branch: 
8.x
Description: 

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:

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.)

Impacts: 
Site builders, administrators, editors
Module developers
Themers

Comments

arnoldbird’s picture

There is a useful code sample for getting links out of a menu in Drupal 8: https://www.drupal.org/node/2840180#comment-11849265