You are here

12 菜单回调机制

admin 的头像
Submitted by admin on 星期五, 2015-07-24 09:57

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

打开menu.inc文件,找到menu_execute_active_handler函数,阅读这个函数的源代码。

/**

 * Execute the page callback associated with the current path.

 *

 * @param $path

 *   The drupal path whose handler is to be be executed. If set to NULL, then

 *   the current path is used.

 * @param $deliver

 *   (optional) A boolean to indicate whether the content should be sent to the

 *   browser using the appropriate delivery callback (TRUE) or whether to return

 *   the result to the caller (FALSE).

 */

function menu_execute_active_handler($path = NULL, $deliver = TRUE) {

  // Check if site is offline.

  $page_callback_result = _menu_site_is_offline() ? MENU_SITE_OFFLINE : MENU_SITE_ONLINE;

 

  // Allow other modules to change the site status but not the path because that

  // would not change the global variable. hook_url_inbound_alter() can be used

  // to change the path. Code later will not use the $read_only_path variable.

  $read_only_path = !empty($path) ? $path : $_GET['q'];

  drupal_alter('menu_site_status', $page_callback_result, $read_only_path);

 

  // Only continue if the site status is not set.

  if ($page_callback_result == MENU_SITE_ONLINE) {

    if ($router_item = menu_get_item($path)) {

      if ($router_item['access']) {

        if ($router_item['include_file']) {

          require_once DRUPAL_ROOT . '/' . $router_item['include_file'];

        }

        $page_callback_result = call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);

      }

      else {

        $page_callback_result = MENU_ACCESS_DENIED;

      }

    }

    else {

      $page_callback_result = MENU_NOT_FOUND;

    }

  }

 

  // Deliver the result of the page callback to the browser, or if requested,

  // return it raw, so calling code can do more processing.

  if ($deliver) {

    $default_delivery_callback = (isset($router_item) && $router_item) ? $router_item['delivery_callback'] : NULL;

    drupal_deliver_page($page_callback_result, $default_delivery_callback);

  }

  else {

    return $page_callback_result;

  }

}

这个函数,执行当前路径的对应页面回调,向浏览器返回具体的页面。这个函数包含两个参数$path$deliver,不过这两个参数都是可选的;在index.php里面调用这个函数的时候,也没有传递参数,所以这两个参数现在是默认值。

第一行代码,是检查当前站点是否是在线状态。Drupal支持离线状态,什么时候用到离线状态,网站升级的时候,通常把站点配置为离线状态。离线配置地址位于admin/config/development/maintenance

1.png 

我们这里访问的node/1,当前状态为在线状态。_menu_site_is_offline是一个帮助函数,用来检查站点是否离线。

接下来的代码:

drupal_alter('menu_site_status', $page_callback_result, $read_only_path)

用来允许其它模块修改站点状态,这里面只允许修改站点状态,不允许修改当前路径。如果要修改路径的话,可以使用hook_url_inbound_alter

再往下是if语句,我们的站点当前为在线,所以进入了if语句里面,里面还是一个if语句:

if ($router_item = menu_get_item($path)) {

在我们这里,$path的值为NULLmenu_get_item会获取当前路径对应的菜单项。当前路径为node/1,对应的菜单项node/%,这个是在node_menu里面定义的,我们打开node.module文件,找到node_menu,找到node/%对应的菜单项。对应的定义如下:

  $items['node/%node'] = array(

    'title callback' => 'node_page_title',

    'title arguments' => array(1),

    // The page callback also invokes drupal_set_title() in case

    // the menu router's title is overridden by a menu link.

    'page callback' => 'node_page_view',

    'page arguments' => array(1),

    'access callback' => 'node_access',

    'access arguments' => array('view', 1),

  );

menu_get_item里面返回的信息,和hook_menu里面定义的菜单项,存在着一一对应的关系,当然menu_get_item里面包含更多一些属性。

此时,$router_item不为空,所以我们继续往下执行。接下来检查路由项的访问权限,看当前用户是否有权限访问这个页面。

if ($router_item['access'])

我们这里是有权限的,所以继续执行里面的代码:

        if ($router_item['include_file']) {

          require_once DRUPAL_ROOT . '/' . $router_item['include_file'];

        }

        $page_callback_result = call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);

$router_item['include_file']指的是什么?我们看node_menu,找到下面的代码:

  $items['node/%node/edit'] = array(

    'title' => 'Edit',

    'page callback' => 'node_page_edit',

    'page arguments' => array(1),

    'access callback' => 'node_access',

    'access arguments' => array('update', 1),

    'weight' => 0,

    'type' => MENU_LOCAL_TASK,

    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,

    'file' => 'node.pages.inc',

  );

  $items['node/%node/delete'] = array(

    'title' => 'Delete',

    'page callback' => 'drupal_get_form',

    'page arguments' => array('node_delete_confirm', 1),

    'access callback' => 'node_access',

    'access arguments' => array('delete', 1),

    'weight' => 1,

    'type' => MENU_LOCAL_TASK,

    'context' => MENU_CONTEXT_INLINE,

    'file' => 'node.pages.inc',

  );

这里的file键,就对应于$router_item['include_file'],如果路由里面指定了这个文件,Drupal在执行回调函数之前,会尝试加载这个文件。比如我们访问node/1/edit,对应的菜单项为'node/%node/edit',在菜单项的定义里面,存在'file' => 'node.pages.inc',当程序执行到这里的时候,Drupal就会尝试加载文件node.pages.inc

这样做有什么好处?好处,就是将页面回调函数的逻辑代码,从module文件中分离出来,使得module文件尽可能的小。Drupal是很吃内存的,其中的一个重要的原因,就是Drupal启动后,会加载所有的module文件,如果每个module文件都比较小的话,那么消耗的内存就比较小。我们在think in Drupal的第一集里面,介绍过这个问题,我们今天通过阅读Drupal核心的源代码,进一步了解到,核心的具体实现办法。


Drupal版本: