hook_init()
对于每个请求的页面,Drupal仅调用一次;这是_drupal_bootstrap_full()中完成的最后一步。
// Drupal 6
//
// Let all modules take action before menu system handles the request
// We do not want this while running update.php.
if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
module_invoke_all('init');
}
// Drupal 7
//
// Let all modules take action before the menu system handles the request.
// We do not want this while running update.php.
if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
// Prior to invoking hook_init(), initialize the theme (potentially a custom
// one for this page), so that:
// - Modules with hook_init() implementations that call theme() or
// theme_get_registry() don't initialize the incorrect theme.
// - The theme can have hook_*_alter() implementations affect page building
// (e.g., hook_form_alter(), hook_node_view_alter(), hook_page_alter()),
// ahead of when rendering starts.
menu_set_custom_theme();
drupal_theme_initialize();
module_invoke_all('init');
}
如果hook_init()
执行不止一次,则应该找出原因。据我所知hook_init()
,Drupal中的任何实现都没有对其执行两次进行检查(例如,请参见system_init()或update_init())。如果那是Drupal通常会发生的事情,那么update_init()
将首先检查它是否已经执行过。
如果计数器是用户登录的连续天数,我宁愿hook_init()
使用类似于以下代码的代码来实现。
// Drupal 7
function mymodule_init() {
global $user;
$result = mymodule_increase_counter($user->uid);
if ($result[0]) {
// Increase the counter; set the other variables.
}
elseif ($result[1] > 86400) {
// The user didn't log in yesterday.
}
}
function mymodule_date($timestamp) {
$date_time = date_create('@' . $timestamp);
return date_format($date_time, 'Ymd');
}
function mymodule_increase_counter($uid) {
$last_timestamp = variable_get("mymodule_last_timestamp_$uid", 0);
if ($last_timestamp == REQUEST_TIME) {
return array(FALSE, 0);
}
$result = array(
mymodule_date($last_timestamp + 86400) == mymodule_date(REQUEST_TIME),
REQUEST_TIME - $last_timestamp,
);
variable_set("mymodule_last_timestamp_$uid", REQUEST_TIME);
return $result;
}
// Drupal 6
function mymodule_init() {
global $user;
$result = mymodule_increase_counter($user->uid);
if ($result[0]) {
// Increase the counter; set the other variables.
}
elseif ($result[1] > 86400) {
// The user didn't log in yesterday.
}
}
function mymodule_increase_counter($uid) {
$last_timestamp = variable_get("mymodule_last_timestamp_$uid", 0);
$result = array(FALSE, time() - $last_timestamp);
if (time() - $last_timestamp < 20) {
return $result;
}
$result[0] = (mymodule_date($last_timestamp + 86400) == mymodule_date(REQUEST_TIME));
variable_set("mymodule_last_timestamp_$uid", time());
return $result;
}
如果hook_init()
在同一页面请求期间连续两次调用if ,则REQUEST_TIME
包含相同的值,并且该函数将返回FALSE
。
mymodule_increase_counter()
未优化代码;这只是为了展示一个例子。在实际的模块中,我宁愿使用数据库表来保存计数器以及其他变量。原因是$conf
在Drupal引导时,会将 Drupal变量全部加载到全局变量中(请参见_drupal_bootstrap_variables()和variable_initialize());如果为此使用Drupal变量,则Drupal会在内存中加载有关您为其保存信息的所有用户的信息,而对于每个请求的页面,在全局变量中仅保存一个用户帐户$user
。
如果您要计算连续几天用户访问的页面数,那么我将实现以下代码。
// Drupal 7
function mymodule_init() {
global $user;
$result = mymodule_increase_counter($user->uid);
if ($result[0]) {
// Increase the counter; set the other variables.
}
elseif ($result[1] > 86400) {
// The user didn't log in yesterday.
}
}
function mymodule_date($timestamp) {
$date_time = date_create('@' . $timestamp);
return date_format($date_time, 'Ymd');
}
function mymodule_increase_counter($uid) {
$last_timestamp = variable_get("mymodule_last_timestamp_$uid", 0);
if ($last_timestamp == REQUEST_TIME) {
return array(FALSE, 0);
}
$result = array(
mymodule_date($last_timestamp + 86400) == mymodule_date(REQUEST_TIME),
REQUEST_TIME - $last_timestamp,
);
variable_set("mymodule_last_timestamp_$uid", REQUEST_TIME);
return $result;
}
// Drupal 6
function mymodule_init() {
global $user;
$result = mymodule_increase_counter($user->uid);
if ($result[0]) {
// Increase the counter; set the other variables.
}
elseif ($result[1] > 86400) {
// The user didn't log in yesterday.
}
}
function mymodule_increase_counter($uid) {
$last_timestamp = variable_get("mymodule_last_timestamp_$uid", 0);
$result = array(FALSE, time() - $last_timestamp);
if (time() - $last_timestamp < 20) {
return $result;
}
$result[0] = (mymodule_date($last_timestamp + 86400) == mymodule_date(REQUEST_TIME));
variable_set("mymodule_last_timestamp_$uid", time());
return $result;
}
您会注意到在我的代码中我没有使用$user->access
。原因是$user->access
可以在hook_init()
调用之前在Drupal引导期间进行更新。Drupal使用的会话写处理程序包含以下代码。(请参见_drupal_session_write()。)
// Likewise, do not update access time more than once per 180 seconds.
if ($user->uid && REQUEST_TIME - $user->access > variable_get('session_write_interval', 180)) {
db_update('users')
->fields(array(
'access' => REQUEST_TIME,
))
->condition('uid', $user->uid)
->execute();
}
至于可以使用的另一个钩子,在Drupal 7中可以使用hook_page_alter();您只是不更改的内容$page
,而是增加计数器,并更改变量。
在Drupal 6上,可以使用hook_footer(),这是从template_preprocess_page()调用的钩子。您什么都不返回,但是增加计数器,并更改变量。
在Drupal 6和Drupal 7上,可以使用hook_exit()。请记住,当引导程序未完成时,也会调用该挂钩。代码无法访问从模块定义的功能或其他Drupal功能,因此您应该首先检查那些功能是否可用。有些功能始终可用hook_exit()
,例如bootstrap.inc和cache.inc中定义的功能。不同之处在于,hook_exit()
还为缓存页面hook_init()
调用了,而没有为缓存页面调用。
最后,作为Drupal模块中使用的代码示例,请参见statistics_exit()。统计信息模块记录站点的访问统计信息,如您所见,它使用hook_exit()
,而不是hook_init()
。为了能够调用必要的函数,它调用了drupal_bootstrap()并传递了正确的参数,例如下面的代码。
// When serving cached pages with the 'page_cache_without_database'
// configuration, system variables need to be loaded. This is a major
// performance decrease for non-database page caches, but with Statistics
// module, it is likely to also have 'statistics_enable_access_log' enabled,
// in which case we need to bootstrap to the session phase anyway.
drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES);
if (variable_get('statistics_enable_access_log', 0)) {
drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION);
// For anonymous users unicode.inc will not have been loaded.
include_once DRUPAL_ROOT . '/includes/unicode.inc';
// Log this page access.
db_insert('accesslog')
->fields(array(
'title' => truncate_utf8(strip_tags(drupal_get_title()), 255),
'path' => truncate_utf8($_GET['q'], 255),
'url' => isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '',
'hostname' => ip_address(),
'uid' => $user->uid,
'sid' => session_id(),
'timer' => (int) timer_read('page'),
'timestamp' => REQUEST_TIME,
))
->execute();
}
更新资料
关于何时hook_init()
调用,可能有些混乱。
hook_init()
如果未缓存页面,则为每个页面请求调用。不会为来自同一用户的每个页面请求调用一次。例如,如果您访问http://example.com/admin/appearance/update,然后http://example.com/admin/reports/status,hook_init()
则会被调用两次:每页一个。
“挂钩被调用两次”表示一旦Drupal完成其引导程序,就有一个模块将执行以下代码。
module_invoke_all('init');
如果是这样,则以下的实现hook_init()
将显示相同的值两次。
function mymodule_init() {
watchdog('mymodule', 'Request time: !timestamp', array('!timestamp' => REQUEST_TIME), WATCHDOG_DEBUG);
}
如果您的代码显示了REQUEST_TIME
两个值(相差2分钟)(如您的情况),则该钩子不会被调用两次,而是应为每个请求的页面调用一次。
REQUEST_TIME
在bootstrap.inc中用以下行定义。
define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);
在当前请求的页面没有返回到浏览器之前,的值REQUEST_TIME
不变。如果看到不同的值,那么您正在查看在不同请求页面中分配的值。