Các lựa chọn thay thế cho hook_init ()


8

Tôi sử dụng hook_init()để kiểm tra thời gian truy cập cuối cùng của người dùng. Nếu thời gian truy cập cuối cùng là ngày hôm qua, tôi tăng bộ đếm và đặt một số biến.

Vấn đề là hook_init()đôi khi được thực thi nhiều hơn một lần (tôi có thể thấy điều này bằng cách sử dụng dsm()) cho cùng một tải trang, do đó mã của tôi được thực thi nhiều lần dẫn đến các biến sai.

Tại sao được hook_init()thực hiện nhiều lần?
Cách tiếp cận tốt nhất cho vấn đề của tôi là gì? Tôi có nên sử dụng một cái móc khác?

Tôi đã thực hiện thêm một số nghiên cứu về điều này: Tôi tìm kiếm các cuộc gọi đến hook_init () (tìm kiếm chuỗi module_invoke_all('init');) nhưng chỉ tìm thấy cuộc gọi cốt lõi). Tôi không biết nếu điều này có thể được gọi khác nhau.

Đây là hook_init () của tôi

function episkeptis_achievements_init(){
    dsm('1st execution');
    dsm('REQUEST_TIME: '.format_date(REQUEST_TIME, 'custom', 'd/m/Y H:i:s').' ('.REQUEST_TIME.')');
}

và đây là đầu ra:

1st execution
REQUEST_TIME: 09/07/2012 11:20:32 (1341822032)

sau đó, thay đổi thông báo dsm () thành dsm('2nd execution');và thực hiện lại, đây là đầu ra:

1st execution
REQUEST_TIME: 09/07/2012 11:20:34 (1341822034)
2nd execution
REQUEST_TIME: 09/07/2012 11:22:28 (1341822148)

Bạn có thể thấy rằng mã được thực thi hai lần. Tuy nhiên, lần đầu tiên thực thi một bản sao cũ của mã và lần thứ hai là bản sao được cập nhật. Ngoài ra còn có chênh lệch thời gian 2 giây.

Đây là phiên bản d7 với php 5.3.10


Sử dụng ddebug_backtrace (), nó sẽ cung cấp cho bạn backtrace chức năng. Nếu nó thực sự được gọi nhiều lần, thì hàm đó sẽ cho bạn biết bởi ai.
Berdir

3
Hãy nhớ rằng chỉ vì bạn thấy nhiều dsm (), điều đó không có nghĩa là hook được gọi nhiều lần. Thực tế cũng có thể là bạn đang thực hiện nhiều yêu cầu (ví dụ: vì một hình ảnh bị thiếu dẫn đến trang 404 được xử lý bởi Drupal)
Berdir

Để ý rằng giữa 11:22:28 và 11:20:34 sự khác biệt là hai phút, không phải hai giây. Trong trường hợp đó, hook không được thực hiện hai lần trong cùng một yêu cầu trang hoặc giá trị cho REQUEST_TIMEsẽ giống nhau.
kiamlaluno

@kiamlaluno Ở lần thực hiện thứ hai là 2 phút sau lần đầu tiên, tôi thấy hai REQUEST_TIME, thời gian hiện tại và thời gian cũ hơn xảy ra là 2 giây sau yêu cầu đầu tiên. Điều này cho tôi biết rằng mã được thực thi hai lần. Không thể theo logic của bạn. Tại sao tôi thấy quá khứ REQUEST_TIME cho yêu cầu hiện tại?
Mike

Tôi không thể trả lời điều đó. Tôi chỉ có thể nói rằng, nếu REQUEST_TIMExuất phát từ cùng một yêu cầu trang, giá trị của nó là như nhau; thậm chí không có sự khác biệt hai giây. Kiểm tra không có mã làm thay đổi giá trị của REQUEST_TIME.
kiamlaluno

Câu trả lời:


20

hook_init()được Drupal gọi chỉ một lần cho mỗi trang được yêu cầu; đây là bước cuối cùng được thực hiện trong _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');
  }

Nếu hook_init()đang được thực hiện nhiều lần, bạn nên khám phá lý do tại sao điều đó xảy ra. Theo như tôi có thể thấy, không có cách hook_init()triển khai nào trong Drupal kiểm tra nó đang được thực thi hai lần (xem ví dụ system_init () hoặc update_init () ). Nếu đó là điều thường có thể xảy ra với Drupal, thì update_init()trước tiên hãy kiểm tra xem nó đã được thực hiện chưa.

Nếu bộ đếm là số ngày liên tiếp người dùng đăng nhập, tôi thà thực hiện hook_init()với mã tương tự như sau.

// 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;
}

Nếu hook_init()được gọi hai lần liên tiếp trong cùng một yêu cầu trang, REQUEST_TIMEchứa cùng một giá trị và hàm sẽ trả về FALSE.

Mã trong mymodule_increase_counter()không được tối ưu hóa; nó chỉ là một ví dụ Trong một mô-đun thực, tôi muốn sử dụng bảng cơ sở dữ liệu trong đó bộ đếm và các biến khác được lưu. Lý do là tất cả các biến Drupal được tải trong biến toàn cục $confkhi bootstraps Drupal (xem _drupal_bootstrap_variables ()biến_initialize () ); nếu bạn sử dụng biến Drupal cho điều đó, Drupal sẽ tải thông tin bộ nhớ về tất cả người dùng mà bạn đã lưu thông tin, khi mỗi trang được yêu cầu chỉ có một tài khoản người dùng được lưu trong biến toàn cục $user.

Nếu bạn đang đếm số lượng trang được truy cập từ người dùng trong những ngày liên tiếp, thì tôi sẽ triển khai mã sau đây.

// 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;
}

Bạn sẽ nhận thấy rằng trong mã của tôi, tôi không sử dụng $user->access. Lý do là $user->accesscó thể được cập nhật trong bootstrap Drupal, trước khi hook_init()được gọi. Trình xử lý ghi phiên được sử dụng từ Drupal chứa mã sau đây. (Xem _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();
}

Đối với một hook khác mà bạn có thể sử dụng, với Drupal 7, bạn có thể sử dụng hook_page_alter () ; bạn chỉ không thay đổi nội dung của $page, nhưng tăng bộ đếm của bạn và thay đổi các biến của bạn.
Trên Drupal 6, bạn có thể sử dụng hook_footer () , hook được gọi từ template_pre process_page () . Bạn không trả lại bất cứ điều gì, nhưng tăng bộ đếm của bạn và thay đổi các biến của bạn.

Trên Drupal 6 và Drupal 7, bạn có thể sử dụng hook_exit () . Hãy nhớ rằng hook cũng được gọi khi bootstrap chưa hoàn thành; mã không thể có quyền truy cập vào các chức năng được xác định từ các mô-đun hoặc các chức năng Drupal khác và trước tiên bạn nên kiểm tra các chức năng đó có sẵn. Một số hàm luôn có sẵn từ hook_exit(), chẳng hạn như các hàm được xác định trong bootstrap.inccache.inc . Sự khác biệt là hook_exit()cũng được gọi cho các trang được lưu trong bộ nhớ cache, trong khi hook_init()không được gọi cho các trang được lưu trong bộ nhớ cache.

Cuối cùng, như ví dụ về mã được sử dụng từ mô-đun Drupal, xem stats_exit () . Mô-đun Thống kê ghi lại số liệu thống kê truy cập cho một trang web và như bạn thấy, nó sử dụng hook_exit()chứ không phải hook_init(). Để có thể gọi các hàm cần thiết, nó gọi drupal_bootstrap () truyền tham số chính xác, chẳng hạn như trong đoạn mã sau.

  // 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();
  }

Cập nhật

Có thể có một số nhầm lẫn về khi hook_init()được viện dẫn.

hook_init()được gọi cho mỗi yêu cầu trang, nếu trang không được lưu trữ. Nó không được gọi một lần cho mỗi yêu cầu trang đến từ cùng một người dùng. Ví dụ: nếu bạn truy cập http://example.com/admin/appparent/update và sau đó http://example.com/admin/reports/status , hook_init()sẽ được gọi hai lần: một cho mỗi trang.
"Hook được gọi hai lần" có nghĩa là có một mô-đun thực thi mã sau đây, khi Drupal đã hoàn thành bootstrap của nó.

module_invoke_all('init');

Nếu đó là trường hợp, thì việc thực hiện sau đây hook_init()sẽ hiển thị cùng một giá trị, hai lần.

function mymodule_init() {
  watchdog('mymodule', 'Request time: !timestamp', array('!timestamp' => REQUEST_TIME), WATCHDOG_DEBUG);
}

Nếu mã của bạn hiển thị cho REQUEST_TIMEhai giá trị có chênh lệch là 2 phút, như trong trường hợp của bạn, thì hook không được gọi hai lần, nhưng nó được gọi một lần cho mỗi trang được yêu cầu, vì nó sẽ xảy ra.

REQUEST_TIMEđược định nghĩa trong bootstrap.inc với dòng sau.

define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);

Cho đến khi trang hiện được yêu cầu không được trả về trình duyệt, giá trị của REQUEST_TIMEkhông thay đổi. Nếu bạn thấy một giá trị khác, thì bạn đang xem giá trị được chỉ định trong một trang yêu cầu khác.


Tôi đã làm một số thử nghiệm dựa trên đề xuất của bạn. REQUEST_TIME không chứa cùng giá trị như bạn có thể thấy trong câu hỏi được cập nhật. Tôi đã cố gắng tìm các dẫn xuất của hook_init () nhưng không tìm thấy ngoại trừ một trong lõi. Có lẽ tôi không nhìn đúng cách. Cuối cùng, hook_exit () dường như thực hiện thủ thuật, vì vậy tôi sẽ chấp nhận câu trả lời này. Tuy nhiên tôi đang tìm kiếm câu trả lời về lý do tại sao hook_init () được gọi hai lần. Là một câu hỏi phụ, bạn đề nghị sử dụng bảng cơ sở dữ liệu thay vì biến_set / get. Tại sao điều này không được khuyến nghị, biến_set / get sử dụng bảng db.
Mike

Các biến Drupal sử dụng bảng cơ sở dữ liệu, nhưng chúng được tải tất cả trong bộ nhớ khi bootupps Drupal. Đối với mỗi trang được phục vụ, Drupal bootstraps mọi lúc và chỉ có một tài khoản người dùng được liên kết với một yêu cầu trang. Nếu bạn sử dụng các biến Drupal, bạn sẽ tải thông tin bộ nhớ về các tài khoản người dùng không cần thiết, vì chỉ một trong các tài khoản người dùng được sử dụng.
kiamlaluno

8

Tôi nhớ điều này xảy ra rất nhiều trong Drupal 6 (không chắc nó vẫn xảy ra trong Drupal 7), nhưng tôi không bao giờ tìm hiểu tại sao. Tôi dường như nhớ rằng đã thấy ở đâu đó rằng lõi Drupal không gọi móc này hai lần.

Tôi luôn tìm thấy cách dễ nhất xung quanh nó là sử dụng biến tĩnh để xem mã đã được chạy chưa:

function MYMODULE_init() {
  static $code_run = FALSE;

  if (!$code_run) {
    run_some_code();
    $code_run = TRUE;
  }
}

Điều đó sẽ đảm bảo nó chỉ được chạy một lần trong một lần tải trang.


Chắc chắn, đó không phải là những gì Drupal làm.
kiamlaluno

2
Đó không phải là những gì cốt lõi làm, nhưng nó chắc chắn xảy ra (tôi đã xác nhận rằng trên ba trang web Drupal 6 cũ, tất cả đều chạy các mô-đun đóng góp khác nhau). Đó là một kẻ đầu trọc thực sự nhưng tôi không có thời gian để gỡ lỗi chúng vào lúc này. Tôi nghi ngờ đây là một trong những mô-đun đóng góp được sử dụng thường xuyên hơn (có thể là pathauto hoặc chuyển hướng toàn cầu), nhưng tôi không muốn chỉ tay. Không thực sự chắc chắn lý do tại sao câu trả lời của bạn đã bị hạ thấp mặc dù (hoặc của tôi cho vấn đề đó), nó có vẻ như là thông tin tốt cho tôi. Tôi đã nâng cấp để khôi phục số dư một chút :)
Clive

Ý tôi là Drupal không kiểm tra như vậy trong quá trình hook_init()triển khai và một số trong số họ sẵn sàng tránh bị xử tử hai lần liên tiếp. Cũng có khả năng OP muốn hook_init()được thực thi một lần mỗi ngày, nếu bộ đếm đếm số ngày liên tiếp mà người dùng đã đăng nhập trên trang web.
kiamlaluno

1
À, tôi hiểu ý của bạn bây giờ, vâng, mẫu tĩnh ở trên chỉ là mẫu tôi đã sử dụng trong quá khứ để giải quyết vấn đề về nó được gọi hai lần trong cùng một trang tải; nó không lý tưởng (lý tưởng sẽ là tìm ra thứ gì gọi nó lần thứ hai) nhưng như một cách khắc phục nhanh, nó sẽ thực hiện được mẹo. Những gì bạn nói về những ngày liên tiếp nghe có vẻ đúng, có lẽ tốt hơn là OP sẽ hook_initkiểm tra xem liệu nó có chạy một lần trong ngày hay không và bảo lãnh nếu nó có. Sau đó, toàn bộ vấn đề trở thành không vấn đề
Clive

5

Bạn có thể tìm thấy hook_init () được gọi nhiều lần nếu có bất kỳ AJAX nào xảy ra trên trang (hoặc bạn đang tải hình ảnh từ một thư mục riêng - mặc dù tôi không chắc lắm về điều đó). Ví dụ, có một vài mô-đun sử dụng AJAX để giúp bỏ qua bộ đệm ẩn trang cho một số yếu tố nhất định - cách dễ nhất để kiểm tra là mở màn hình mạng trong trình gỡ lỗi bạn chọn (firefox hoặc trình kiểm tra web) và xem thử nếu có yêu cầu được thực hiện có thể kích hoạt quá trình bootstrap.

Bạn sẽ chỉ nhận được dpm () khi tải trang tiếp theo nếu đó là cuộc gọi AJAX. Vì vậy, giả sử bạn làm mới trang 5 phút sau, bạn sẽ nhận được cuộc gọi AJAX từ tin nhắn init của 5 phút trước cũng như tin nhắn mới.

Một thay thế cho hook_init () là hook_boot () được gọi trước khi thực hiện bất kỳ bộ nhớ đệm nào. Chưa có mô-đun nào được tải, vì vậy bạn thực sự không có nhiều sức mạnh ở đây ngoài việc đặt các biến toàn cục và chạy một số chức năng Drupal. Nó rất hữu ích để bỏ qua bộ nhớ đệm cấp độ thông thường (nhưng sẽ không bỏ qua bộ nhớ đệm áp lực).


1

Trong trường hợp của tôi, hành vi này là do mô-đun Menu Quản trị (admin_menu) gây ra.

hook_init không được gọi mỗi yêu cầu nhưng menu quản trị sẽ khiến / js / admin_menu / cache / 94614e34b017b19a78878d7b96ccab55 được trình duyệt của người dùng tải ngay sau yêu cầu chính, kích hoạt một bootstrap khác.

Sẽ có các mô-đun khác làm những việc tương tự nhưng admin_menu có lẽ là một trong những mô-đun được triển khai phổ biến hơn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.