Drupal - If two modules both defined the same menu path in 'hook_menu', which one Drupal will choose?

The function invoking hook_menu() is menu_router_build(), called by menu_rebuild(). It contains the following code.

  foreach (module_implements('menu') as $module) {
    $router_items = call_user_func($module . '_menu');
    if (isset($router_items) && is_array($router_items)) {
      foreach (array_keys($router_items) as $path) {
        $router_items[$path]['module'] = $module;
      }
      $callbacks = array_merge($callbacks, $router_items);
    }
  }
  // Alter the menu as defined in modules, keys are like user/%user.
  drupal_alter('menu', $callbacks);

If there are two modules defining the same route, the last module in the array returned by module_implements() will override the value defined from the other modules.

The second parameter required by module_implements() is defined as:

$sort By default, modules are ordered by weight and filename, settings this option to TRUE, module list will be ordered by module name.

Since menu_router_build() doesn't pass the second parameter to menu_implements(), the function is using the default value for that parameter. This means the list of modules are ordered by their weight and filename; when two modules have the same weight, the first module that appears in the list is the one that alphabetically comes first.

Furthermore, any module implementing hook_module_implements_alter() can alter the order the hooks are invoked.

For this reason, you should not assume to know in which order the hooks are invoked.
If the purpose of the code is altering the route implemented by another module, for example because a route should be removed when a second module is installed and enabled, the code should use hook_menu_alter(). If you are trying to understand which module would "win" in the case of route conflicts, I would rather avoid such route conflict, and define a route that is not already defined from another module.

If then you are implementing hook_menu_alter(), and you want to be sure your module is executed last, to be the module that effectively overrides a route, you should implement hook_module_implements_alter() too.

function mymodule_module_implements_alter(&$implementations, $hook) {
  if ($hook == 'menu_alter') {
    // Move mymodule_menu_alter() to the end of the list. module_implements()
    // iterates through $implementations with a foreach loop which PHP iterates
    // in the order that the items were added, so to move an item to the end of
    // the array, we remove it and then add it.
    $group = $implementations['mymodule'];
    unset($implementations['mymodule']);
    $implementations['mymodule'] = $group;
  }
}

Whichever module has a lower weight value in the system table will be called first, so the module with the higher weight value will 'win' in this case.

If the weights are the same for two (or more) modules I believe there is no specific ordering done other than the ordering that comes directly from the MySQL table (I could be wrong about that though).

As the return results from the invoke of hook_menu are just put into a single array of menu items there will never be a 'conflict' as such, the results from later calls to hook_menu will simply override those from the earlier calls.

Tags:

Routes

7

Hooks