Introduction

The menu system powers the navigation a user interacts with on a site. It is also responsible for how a request is directed to the right callback for a response to be generated.

Drupal 7

There are two parts to the menu system in Drupal 7.

Actions

The menu system holds the key to understanding how Drupal handles an HTTP request received by the index.php file. It is 4 lines of code and has calls to two Drupal functions:

// index.php
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
menu_execute_active_handler();

The application environment is prepared in drupal_bootstrap() and everything else is done by the menu_execute_active_handler() function. This includes:

  • checking menu tables for the best matching path and executing the associated callback function,
  • auto-loading wildcards,
  • checking access to the requested resource, and
  • returning the result to the browser or caller of the function.

The menu system also renders menu items and tabs as well as action and contextual links. This system drives the way a user navigates the site.

To define menu items, an implementation of hook_menu is required, which will register paths and specify how requests for a URL are handled. Examples from the user module:

// user.module

function user_menu() {
	// Anonymous users see "User account" while authenticated users see "My account"
	$items['user'] = array(
    'title' => 'User account',
    'title callback' => 'user_menu_title',
    'page callback' => 'user_page',
    'access callback' => TRUE,
    'file' => 'user.pages.inc',
    'weight' => -10,
    'menu_name' => 'user-menu',
  );

  // Default tab when anonymous users view /user path 
  $items['user/login'] = array(
    'title' => 'Log in',
    'access callback' => 'user_is_anonymous',
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  
  // One of 3 tabs when anonymous users view /user path
  $items['user/register'] = array(
    'title' => 'Create new account',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('user_register_form'),
    'access callback' => 'user_register_access',
    'type' => MENU_LOCAL_TASK,
  );
	
  // Users list page	
  $items['admin/people'] = array(
    'title' => 'People',
    'description' => 'Manage user accounts, roles, and permissions.',
    'page callback' => 'user_admin',
    'page arguments' => array('list'),
    'access arguments' => array('administer users'),
    'position' => 'left',
    'weight' => -4,
    'file' => 'user.admin.inc',
  );
  
  // "People" section in administration pages.
  $items['admin/config/people'] = array(
    'title' => 'People',
    'description' => 'Configure user accounts.',
    'position' => 'left',
    'weight' => -20,
    'page callback' => 'system_admin_menu_block_page',
    'access arguments' => array('access administration pages'),
    'file' => 'system.admin.inc',
    'file path' => drupal_get_path('module', 'system'),
  );
  
  return $items;
}

Drupal 8

The functionality of the menu system is no longer monolithic. It has been broken down into different systems.

Actions

The matching of paths to controllers is now taken care of by Symfony's Routing component. It manages the 3 parts of a routing system — RouteCollection (all defined routes), RequestContext (request information), and UrlMatcher (mapping requests to routes).

There is no hook_menu in Drupal 8. Basic interaction with the new menu system is configuration-driven by appropriately named YAML files. Here are the same Drupal 7 paths as above from the user module in Drupal 8:

All routes are defined in user.routing.yml:


user.page:
  path: '/user'
  defaults:
    _controller: '\Drupal\user\Controller\UserController::userPage'
    _title: 'My account'
  requirements:
    _user_is_logged_in: 'TRUE'

user.login:
  path: '/user/login'
  defaults:
    _form: '\Drupal\user\Form\UserLoginForm'
    _title: 'Log in'
  requirements:
    _user_is_logged_in: 'FALSE'
  options:
    _maintenance_access: TRUE
    
user.register:
  path: '/user/register'
  defaults:
    _entity_form: 'user.register'
    _title: 'Create new account'
  requirements:
    _access_user_register: 'TRUE'
    
entity.user.collection:
  path: '/admin/people'
  defaults:
    _entity_list: 'user'
    _title: 'People'
  requirements:
    _permission: 'administer users'

user.admin_index:
  path: '/admin/config/people'
  defaults:
    _controller: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
    _title: 'People'
  requirements:
    _permission: 'access administration pages'

Links have separate configuration files, MODULE_MACHINE_NAME.links.TYPE.yml:

  • user.links.action.yml — local action links
  • user.links.contextual.yml — quick links to admin actions
  • user.links.menu.yml — menu items
  • user.links.task.yml — tabs

Conclusion

In Drupal 8, we no longer have the hook_menu. Instead, we have a menu system driven by the Routing component from Symfony. The configuration of links is now easier to follow in separate YAML files that are parsed by the YAML component.

Recommended reading