Drupal's menu building mechanism

Last modified: March 7, 2008 - 19:40

(Note: this is an analysis of the menu building mechanism in pre-4.5 CVS as of August 2004. It does not include menu caching.)

This continues our examination of how Drupal serves pages. We are looking specifically at how the menu system works and is built, from a technical perspective. See the excellent overview in the menu system documentation.

We begin in index.php, where menu_execute_active_handler() has been called. Diving in from menu_execute_active_handler(), we immediately set the $menu variable by calling menu_get_menu(). The latter function declares the global $_menu array (note the underline, it means a 'super global', which is a predefined array in PHP lore) and calls _menu_build() to fill the array, then returns $_menu. Although menu_get_menu() initializes the $_menu array, the _menu_build() function actually reinitializes the $_menu array. Then it sets up two main arrays within $_menu: the items array and the path index array.

The items array is an array keyed to integers. Each entry contains the following fields:

Required fields
path string the partial URL to the page for this menu item
title string the title that this menu item will have in the menu
type integer a constant denoting the menu item type (see comments in menu.inc)
Optional fields
access boolean
pid integer
weight integer
callback string name of the function to be called if this menu item is selected
callback arguments array

An array called $menu_item_list is populated by sending a 'menu' callback to all modules with 'menu' hooks (that is, they have a function called foo_menu() where foo is the name of the module). So each module has a chance to register its own menu items. It is interesting that when the node module receives the menu callback through node_menu(), and the path is something like 'node/1' as it is in our present case, the complete node is actually loaded via the node_load() function so it can be examined for permissions. The $node variable into which it was loaded then goes out of scope, so the node is gone and needs to be rebuilt completely later on. This seems like a golden opportunity for the node module to cache the node.

The $menu_item_list array is normalized by making sure each array entry has a path, type and weight entry. As each entry is examined, the path index array of the $_menu array is checked to see if the path of this menu item exists. If an equivalent path is already there in the path index array, it is blasted away. The path index of this menu item is then added as a key with the value being the menu id. In the items array of the $_menu array, the menu id is used as the key and the entire array entry is the value.

Note: the $temp_mid and $mid variables seem to do the same thing. Why, syntactically, cannot only one be used?

The path index array contained 76 items when serving out a simple node with only the default modules enabled.

Next the menu table from the database is fetched and its contents are used to move the position of existing menu items from their current menu ids to the menu ids saved in the database. The comments says "reassigning menu IDs as needed." This is probably to detect if the user has customized the menu entries using the menu module. The path index array entries generated from the database can be recognized because their values are strings, whereas up til now the values in the path index array have been integers.

Now I get sort of lost. It looks like the code is looking at paths to determine which menu items are children of other menu items. Then _menu_build_visible_tree is a recursive function that builds a third subarray inside $_menu, to go along with items and path index. It is called visible and takes into account the access attribute and whether or not the item is hidden in order to filter the items array. As an anonymous user, all items but the Navigation menu item are filtered out. See also the comments in menu.inc for menu_get_menu(). In fact, read all the comments in menu.inc!

Now the path is parsed out from the q parameter of the URL. Since node/1 is present in the path index, we successfully found a menu item. It points to menu item -44 in our case, to be precise, but there must be a bug in the Zend IDE because it shows item -44 as null. Anyway, the menu item entry is checked for callback arguments (there are none) and for additional parameters (also none), and execution is passed off to node_page() through the call_user_func_array function.

 
 

Drupal is a registered trademark of Dries Buytaert.