导航

Drupal 7.23:函数drupal_alter()注释

Posted on 2013-08-13 14:53  eastson  阅读(818)  评论(0编辑  收藏  举报
  1 /**
  2  * Passes alterable variables to specific hook_TYPE_alter() implementations.
  3  *
  4  * This dispatch function hands off the passed-in variables to type-specific
  5  * hook_TYPE_alter() implementations in modules. It ensures a consistent
  6  * interface for all altering operations.
  7  *
  8  * A maximum of 2 alterable arguments is supported (a third is supported for
  9  * legacy reasons, but should not be used in new code). In case more arguments
 10  * need to be passed and alterable, modules provide additional variables
 11  * assigned by reference in the last $context argument:
 12  * @code
 13  *   $context = array(
 14  *     'alterable' => &$alterable,
 15  *     'unalterable' => $unalterable,
 16  *     'foo' => 'bar',
 17  *   );
 18  *   drupal_alter('mymodule_data', $alterable1, $alterable2, $context);
 19  * @endcode
 20  *
 21  * Note that objects are always passed by reference in PHP5. If it is absolutely
 22  * required that no implementation alters a passed object in $context, then an
 23  * object needs to be cloned:
 24  * @code
 25  *   $context = array(
 26  *     'unalterable_object' => clone $object,
 27  *   );
 28  *   drupal_alter('mymodule_data', $data, $context);
 29  * @endcode
 30  *
 31  * @param $type
 32  *   A string describing the type of the alterable $data. 'form', 'links',
 33  *   'node_content', and so on are several examples. Alternatively can be an
 34  *   array, in which case hook_TYPE_alter() is invoked for each value in the
 35  *   array, ordered first by module, and then for each module, in the order of
 36  *   values in $type. For example, when Form API is using drupal_alter() to
 37  *   execute both hook_form_alter() and hook_form_FORM_ID_alter()
 38  *   implementations, it passes array('form', 'form_' . $form_id) for $type.
 39  * @param $data
 40  *   The variable that will be passed to hook_TYPE_alter() implementations to be
 41  *   altered. The type of this variable depends on the value of the $type
 42  *   argument. For example, when altering a 'form', $data will be a structured
 43  *   array. When altering a 'profile', $data will be an object.
 44  * @param $context1
 45  *   (optional) An additional variable that is passed by reference.
 46  * @param $context2
 47  *   (optional) An additional variable that is passed by reference. If more
 48  *   context needs to be provided to implementations, then this should be an
 49  *   associative array as described above.
 50  * @param $context3
 51  *   (optional) An additional variable that is passed by reference. This
 52  *   parameter is deprecated and will not exist in Drupal 8; consequently, it
 53  *   should not be used for new Drupal 7 code either. It is here only for
 54  *   backwards compatibility with older code that passed additional arguments
 55  *   to drupal_alter().
 56  */
 57 function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL, &$context3 = NULL) {
 58   // Use the advanced drupal_static() pattern, since this is called very often.
 59   static $drupal_static_fast;
 60   if (!isset($drupal_static_fast)) {
 61     $drupal_static_fast['functions'] = &drupal_static(__FUNCTION__);
 62   }
 63   $functions = &$drupal_static_fast['functions'];
 64 
 65   // Most of the time, $type is passed as a string, so for performance,
 66   // normalize it to that. When passed as an array, usually the first item in
 67   // the array is a generic type, and additional items in the array are more
 68   // specific variants of it, as in the case of array('form', 'form_FORM_ID').
 69   if (is_array($type)) {
 70     $cid = implode(',', $type);
 71     $extra_types = $type;
 72     $type = array_shift($extra_types);
 73     // Allow if statements in this function to use the faster isset() rather
 74     // than !empty() both when $type is passed as a string, or as an array with
 75     // one item.
 76     if (empty($extra_types)) {
 77       unset($extra_types);
 78     }
 79   }
 80   else {
 81     $cid = $type;
 82   }
 83 
 84   // Some alter hooks are invoked many times per page request, so statically
 85   // cache the list of functions to call, and on subsequent calls, iterate
 86   // through them quickly.
 87   if (!isset($functions[$cid])) {
 88     $functions[$cid] = array();
 89     $hook = $type . '_alter';
 90     $modules = module_implements($hook);
 91     if (!isset($extra_types)) {
 92       // For the more common case of a single hook, we do not need to call
 93       // function_exists(), since module_implements() returns only modules with
 94       // implementations.
 95       foreach ($modules as $module) {
 96         $functions[$cid][] = $module . '_' . $hook;
 97       }
 98     }
 99     else {
100       // For multiple hooks, we need $modules to contain every module that
101       // implements at least one of them.
102       $extra_modules = array();
103       foreach ($extra_types as $extra_type) {
104         $extra_modules = array_merge($extra_modules, module_implements($extra_type . '_alter'));
105       }
106       // If any modules implement one of the extra hooks that do not implement
107       // the primary hook, we need to add them to the $modules array in their
108       // appropriate order. module_implements() can only return ordered
109       // implementations of a single hook. To get the ordered implementations
110       // of multiple hooks, we mimic the module_implements() logic of first
111       // ordering by module_list(), and then calling
112       // drupal_alter('module_implements').
113       if (array_diff($extra_modules, $modules)) {
114         // Merge the arrays and order by module_list().
115         $modules = array_intersect(module_list(), array_merge($modules, $extra_modules));
116         // Since module_implements() already took care of loading the necessary
117         // include files, we can safely pass FALSE for the array values.
118         $implementations = array_fill_keys($modules, FALSE);
119         // Let modules adjust the order solely based on the primary hook. This
120         // ensures the same module order regardless of whether this if block
121         // runs. Calling drupal_alter() recursively in this way does not result
122         // in an infinite loop, because this call is for a single $type, so we
123         // won't end up in this code block again.
124         drupal_alter('module_implements', $implementations, $hook);
125         $modules = array_keys($implementations);
126       }
127       foreach ($modules as $module) {
128         // Since $modules is a merged array, for any given module, we do not
129         // know whether it has any particular implementation, so we need a
130         // function_exists().
131         $function = $module . '_' . $hook;
132         if (function_exists($function)) {
133           $functions[$cid][] = $function;
134         }
135         foreach ($extra_types as $extra_type) {
136           $function = $module . '_' . $extra_type . '_alter';
137           if (function_exists($function)) {
138             $functions[$cid][] = $function;
139           }
140         }
141       }
142     }
143     // Allow the theme to alter variables after the theme system has been
144     // initialized.
145     global $theme, $base_theme_info;
146     if (isset($theme)) {
147       $theme_keys = array();
148       foreach ($base_theme_info as $base) {
149         $theme_keys[] = $base->name;
150       }
151       $theme_keys[] = $theme;
152       foreach ($theme_keys as $theme_key) {
153         $function = $theme_key . '_' . $hook;
154         if (function_exists($function)) {
155           $functions[$cid][] = $function;
156         }
157         if (isset($extra_types)) {
158           foreach ($extra_types as $extra_type) {
159             $function = $theme_key . '_' . $extra_type . '_alter';
160             if (function_exists($function)) {
161               $functions[$cid][] = $function;
162             }
163           }
164         }
165       }
166     }
167   }
168 
169   foreach ($functions[$cid] as $function) {
170     $function($data, $context1, $context2, $context3);
171   }
172 }

 

drupal_alter()函数提供一个对钩子系统的调用,实现对系统变量的自定义修改。例如:当系统处于维护状态时,网站应该只允许管理员访问,一般访问者只能看到“系统处于维护状态”这样的提示信息。一般访问者访问网站时,若是在变更维护状态之前已经登录网站,这时user模块提供一个钩子,实现访问者的自动注销。

// menu.inc的501-508行
$site_status = variable_get('maintenance_mode') ? MENU_SITE_OFFLINE : MENU_SITE_ONLINE;
drupal_alter('menu_site_status', $site_status , $_GET['q']);

drupal_alter()会调用形如hook_TYPE_alter这样的钩子,此例子中是user模块的user_menu_site_status_alter()函数:

// user.module的1834-1874行
function user_menu_site_status_alter(&$menu_site_status, $path) {
  if ($menu_site_status == MENU_SITE_OFFLINE) {
    // If the site is offline, log out unprivileged users.
    if (user_is_logged_in() && !user_access('access site in maintenance mode')) {
      module_load_include('pages.inc', 'user', 'user');
      user_logout();
    }

    ... ...
  }

  ...
}

 

第57行:注意参数$data,是引用定义的,也可以理解为传址。再看一下user_menu_site_status_alter()钩子的参数,这里的$menu_site_status也是引用定义,和$data刚好对应。也就是说,在user_menu_site_status_alter()中修改$menu_site_status的值,也就是在修改$data的值。
以此类推,drupal_alter()定义了三个context参数,也都是引用定义的。所以,在钩子函数中如果有需要,也可以修改这三个context参数的值:

function hook_menu_site_status_alter(&$menu_site_status, &$context1, &$context2, &$context3) {
  $context1 = 'new value';
  ... ...
}

 

第143-144行:drupal_alter()被调用时,theme系统有可能准备就绪,也有可能尚未就绪。通过全局变量$theme可以判断theme系统是否就绪:

global $theme;
if (isset($theme)) {
  // theme系统准备就绪
} else {
  // theme系统尚未就绪
}

 

除了module可以提供钩子(格式如:module_TYPE_alter)修改变量值以外,theme也可以提供钩子(格式:theme_TYPE_alter)修改变量值。所以,drupal必须要求模块和主题命名的唯一。