Drupal的很多功能都是可以定制的。以导航菜单为例,blog模块需要在菜单上添加一些功能,comment模块需要在菜单上添加一些功能,我们开发的自定义模块也需要在菜单上添加一些功能。Drupal开发者为了达到这样的扩展目的,设计了钩子系统,导航菜单就是其中一个名为menu的钩子。有了钩子系统,开发人员就可以在blog模块定义一个钩子函数从而实现menu钩子。Drupal要求钩子函数的命名必须要求以模块名开始,以钩子名为后缀。
function block_menu() { $items['admin/structure/block/manage/%/%'] = array( 'title' => 'Configure block', 'page callback' => 'drupal_get_form', 'page arguments' => array('block_admin_configure', 4, 5), 'access arguments' => array('administer blocks'), 'file' => 'block.admin.inc', ); $items['admin/structure/block/manage/%/%/configure'] = array( 'title' => 'Configure block', 'type' => MENU_DEFAULT_LOCAL_TASK, 'context' => MENU_CONTEXT_INLINE, ); $items['admin/structure/block/manage/%/%/delete'] = array( 'title' => 'Delete block', 'page callback' => 'drupal_get_form', 'page arguments' => array('block_custom_block_delete', 4, 5), 'access arguments' => array('administer blocks'), 'type' => MENU_LOCAL_TASK, 'context' => MENU_CONTEXT_NONE, 'file' => 'block.admin.inc', ); ... ... return $items; }
module_hook_info()函数查询所有可以实现的钩子,实质上也是使用的钩子方式:
function module_hook_info() { $hook_info = array(); foreach (module_list() as $module) { $function = $module . '_hook_info'; // hook_info钩子 if (function_exists($function)) { $result = $function(); if (isset($result) && is_array($result)) { $hook_info = array_merge_recursive($hook_info, $result); } } } // We can't use drupal_alter() for the same reason as above. foreach (module_list() as $module) { $function = $module . '_hook_info_alter'; // hook_info_alter钩子 if (function_exists($function)) { $function($hook_info); } } return $hook_info; }
自定义的钩子可以在hook_info中注册,也可以不注册。注册的好处是可以为钩子实现人员提供更详细的详细,另外一个目的是可以为钩子分组。例如,定义了很多admin有关的钩子,这些钩子函数我们想把它们单独放在一个PHP文件里面,而不用都挤在模块主文件。
module_implements()函数返回实现某个钩子的所有函数,它用module_list()遍历当前激活的所哟模块:
function module_implements($hook, $sort = FALSE, $reset = FALSE) { $implementations = &$drupal_static_fast['implementations']; if (!isset($implementations[$hook])) { $hook_info = module_hook_info(); // 查询钩子信息 $implementations[$hook] = array(); $list = module_list(FALSE, FALSE, $sort); foreach ($list as $module) { // 注意这里额外载入了钩子分组文件 $include_file = isset($hook_info[$hook]['group']) && module_load_include('inc', $module, $module . '.' . $hook_info[$hook]['group']); // Since module_hook() may needlessly try to load the include file again, // function_exists() is used directly here. if (function_exists($module . '_' . $hook)) { $implementations[$hook][$module] = $include_file ? $hook_info[$hook]['group'] : FALSE; } } // Allow modules to change the weight of specific implementations but avoid // an infinite loop. if ($hook != 'module_implements_alter') { drupal_alter('module_implements', $implementations[$hook], $hook); } } else { foreach ($implementations[$hook] as $module => $group) { // If this hook implementation is stored in a lazy-loaded file, so include // that file first. if ($group) { // 注意这里额外载入了钩子分组文件 module_load_include('inc', $module, "$module.$group"); } // It is possible that a module removed a hook implementation without the // implementations cache being rebuilt yet, so we check whether the // function exists on each request to avoid undefined function errors. // Since module_hook() may needlessly try to load the include file again, // function_exists() is used directly here. if (!function_exists($module . '_' . $hook)) { // Clear out the stale implementation from the cache and force a cache // refresh to forget about no longer existing hook implementations. unset($implementations[$hook][$module]); $implementations['#write_cache'] = TRUE; } } } return array_keys($implementations[$hook]); }
module_invoke_all()则调用所有实现某个钩子的所有函数,这是Drupal钩子系统的最外层接口:
function module_invoke_all($hook) { $args = func_get_args(); // Remove $hook from the arguments. unset($args[0]); $return = array(); foreach (module_implements($hook) as $module) { $function = $module . '_' . $hook; if (function_exists($function)) { $result = call_user_func_array($function, $args); // 调用钩子函数 if (isset($result) && is_array($result)) { $return = array_merge_recursive($return, $result); // 返回结果的处理方式 } elseif (isset($result)) { $return[] = $result; // 返回结果的处理方式 } } } return $return; }
例如,我们要调用menu钩子,简单的调用module_invoke_all()就可以了:
$items = module_invoke_all('menu');