作者:老葛,北京亚艾元软件有限责任公司,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。
我们这里访问的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的值为NULL,menu_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核心的源代码,进一步了解到,核心的具体实现办法。