Alternatif untuk hook_init ()

8

Saya gunakan hook_init()untuk memeriksa waktu akses terakhir pengguna. Jika waktu akses terakhir kemarin, saya menambah penghitung dan menetapkan beberapa variabel.

Masalahnya adalah bahwa hook_init()kadang-kadang dieksekusi lebih dari sekali (saya bisa melihat ini menggunakan dsm()) untuk memuat halaman yang sama, jadi kode saya dieksekusi beberapa kali menghasilkan variabel yang salah.

Mengapa hook_init()dieksekusi lebih dari satu kali?
Apa pendekatan terbaik untuk masalah saya? Haruskah saya menggunakan pengait lain?

Saya melakukan beberapa penggalian lebih lanjut tentang ini: Saya mencari panggilan ke hook_init () (mencari string module_invoke_all('init');) tetapi hanya menemukan panggilan inti). Saya tidak tahu apakah ini bisa disebut berbeda.

Ini adalah hook_init saya ()

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

dan ini adalah output:

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

kemudian, ubah pesan dsm () menjadi dsm('2nd execution');dan dieksekusi lagi, ini adalah output:

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

Anda dapat melihat bahwa kode dieksekusi dua kali. Namun, pertama kali mengeksekusi salinan kode yang lama, dan yang kedua salinan yang diperbarui. Ada juga perbedaan waktu 2 detik.

Ini adalah versi d7 dengan php 5.3.10

Mike
sumber
Gunakan ddebug_backtrace (), itu akan memberi Anda fungsi backtrace. Jika benar-benar dipanggil beberapa kali, maka fungsi itu akan memberi tahu Anda oleh siapa.
Berdir
3
Perlu diingat bahwa hanya karena Anda melihat banyak dsm (), itu tidak berarti bahwa pengait disebut beberapa kali. Mungkin juga Anda sebenarnya menjalankan beberapa permintaan (mis. Karena gambar hilang yang menghasilkan halaman 404 yang ditangani oleh Drupal)
Berdir
Untuk memperhatikan bahwa antara 11:22:28 dan 11:20:34 perbedaannya adalah dua menit, bukan dua detik. Dalam hal ini, pengait tidak dieksekusi dua kali dalam permintaan halaman yang sama, atau nilai REQUEST_TIMEuntuknya akan sama.
kiamlaluno
@kiamlaluno Pada eksekusi kedua yaitu 2 menit setelah yang pertama, saya melihat dua REQUEST_TIME, waktu saat ini dan waktu yang lebih lama yang kebetulan 2detik setelah permintaan pertama. Ini memberitahu saya bahwa kode dieksekusi dua kali. Tidak bisa mengikuti logika Anda. Mengapa saya melihat REQUEST_TIME yang lalu untuk permintaan saat ini?
Mike
Saya tidak bisa menjawabnya. Saya hanya bisa mengatakan bahwa, jika REQUEST_TIMEberasal dari permintaan halaman yang sama, nilainya sama; bahkan tidak ada perbedaan dua detik. Periksa tidak ada kode yang mengubah nilai REQUEST_TIME.
kiamlaluno

Jawaban:

20

hook_init()dipanggil oleh Drupal hanya sekali untuk setiap halaman yang diminta; itu adalah langkah terakhir yang dilakukan di _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');
  }

Jika hook_init()dieksekusi lebih dari sekali, Anda harus mengetahui mengapa itu terjadi. Sejauh yang saya bisa lihat, tidak ada hook_init()implementasi dalam Drupal yang memeriksa dieksekusi dua kali (lihat misalnya system_init () , atau update_init () ). Jika itu adalah sesuatu yang biasanya dapat terjadi dengan Drupal, maka update_init()pertama-tama akan memeriksa apakah sudah dijalankan.

Jika penghitung adalah jumlah hari berturut-turut pengguna login, saya lebih suka mengimplementasikan hook_init()dengan kode yang mirip dengan yang berikut.

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

Jika hook_init()dipanggil dua kali berturut-turut selama permintaan halaman yang sama, REQUEST_TIMEberisi nilai yang sama, dan fungsinya akan kembali FALSE.

Kode dalam mymodule_increase_counter()tidak dioptimalkan; itu hanya untuk menunjukkan contoh. Dalam modul nyata, saya lebih suka menggunakan tabel database tempat penghitung, dan variabel lainnya disimpan. Alasannya adalah bahwa variabel Drupal semua dimuat dalam variabel global $confketika Drupal bootstraps (lihat _drupal_bootstrap_variables () , dan variable_initialize () ); jika Anda menggunakan variabel Drupal untuk itu, Drupal akan memuat informasi memori tentang semua pengguna yang Anda simpan informasinya, ketika untuk setiap halaman yang diminta hanya ada satu akun pengguna yang disimpan dalam variabel global $user.

Jika Anda menghitung jumlah halaman yang dikunjungi dari pengguna dalam beberapa hari berturut-turut, maka saya akan menerapkan kode berikut.

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

Anda akan melihat bahwa dalam kode saya saya tidak menggunakan $user->access. Alasannya adalah bahwa $user->accessdapat diperbarui selama bootstrap Drupal, sebelum hook_init()dipanggil. Pegangan menulis sesi yang digunakan dari Drupal berisi kode berikut. (Lihat _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();
}

Adapun hook lain yang dapat Anda gunakan, dengan Drupal 7 Anda dapat menggunakan hook_page_alter () ; Anda hanya tidak mengubah konten $page, tetapi menambah penghitung Anda, dan mengubah variabel Anda.
Pada Drupal 6, Anda bisa menggunakan hook_footer () , hook yang dipanggil dari template_preprocess_page () . Anda tidak mengembalikan apa pun, tetapi tingkatkan penghitung Anda, dan ubah variabel Anda.

Pada Drupal 6, dan Drupal 7, Anda bisa menggunakan hook_exit () . Ingatlah bahwa kait juga dipanggil saat bootstrap tidak lengkap; kode tidak dapat memiliki akses ke fungsi yang ditentukan dari modul, atau fungsi Drupal lainnya, dan Anda harus terlebih dahulu memeriksa fungsi-fungsi yang tersedia. Beberapa fungsi selalu tersedia hook_exit(), seperti yang didefinisikan di bootstrap.inc , dan cache.inc . Perbedaannya adalah yang hook_exit()dipanggil juga untuk halaman yang di-cache, sementara hook_init()itu tidak dipanggil untuk halaman yang di-cache.

Akhirnya, sebagai contoh kode yang digunakan dari modul Drupal, lihat stats_exit () . Modul Statistik mencatat statistik akses untuk suatu situs, dan seperti yang Anda lihat, ia menggunakan hook_exit(), bukan hook_init(). Agar dapat memanggil fungsi yang diperlukan, ia memanggil drupal_bootstrap () yang melewati parameter yang benar, seperti dalam kode berikut.

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

Memperbarui

Mungkin ada beberapa kebingungan tentang kapan hook_init()dipanggil.

hook_init()dipanggil untuk setiap permintaan halaman, jika halaman tersebut tidak di-cache. Itu tidak dipanggil sekali untuk setiap permintaan halaman yang berasal dari pengguna yang sama. Jika Anda mengunjungi, misalnya, http://example.com/admin/appearance/update , lalu http://example.com/admin/reports/status , hook_init()akan dipanggil dua kali: satu untuk setiap halaman.
"Hook dipanggil dua kali" berarti ada modul yang mengeksekusi kode berikut, setelah Drupal menyelesaikan bootstrapnya.

module_invoke_all('init');

Jika itu masalahnya, maka implementasi berikut hook_init()akan menunjukkan nilai yang sama, dua kali.

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

Jika kode Anda ditampilkan untuk REQUEST_TIMEdua nilai yang perbedaannya adalah 2 menit, seperti dalam kasus Anda, maka kail tidak dipanggil dua kali, tetapi dipanggil sekali untuk setiap halaman yang diminta, seperti yang seharusnya terjadi.

REQUEST_TIMEdidefinisikan dalam bootstrap.inc dengan baris berikut.

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

Sampai halaman yang diminta saat ini tidak dikembalikan ke browser, nilai REQUEST_TIMEtidak berubah. Jika Anda melihat nilai yang berbeda, maka Anda menonton nilai yang ditetapkan di halaman permintaan yang berbeda.

kiamlaluno
sumber
Saya melakukan beberapa pengujian berdasarkan saran Anda. REQUEST_TIME tidak mengandung nilai yang sama seperti yang Anda lihat di pertanyaan yang diperbarui. Saya mencoba mencari doa hook_init () tetapi tidak menemukan satu pun kecuali di inti. Mungkin saya tidak melihat jalan yang benar. Akhirnya, hook_exit () tampaknya melakukan trik, jadi saya akan menerima jawaban ini. Namun saya mencari jawaban mengapa hook_init () dipanggil dua kali. Sebagai pertanyaan sampingan, Anda menyarankan untuk menggunakan tabel database alih-alih variable_set / get. Mengapa ini tidak disarankan, variabel_set / gunakan tabel db.
Mike
Variabel Drupal menggunakan tabel database, tetapi mereka dimuat semua dalam memori ketika Drupal bootstraps. Untuk setiap halaman yang ditayangkan, Drupal bootstraps sepanjang waktu, dan hanya ada akun pengguna yang terkait dengan permintaan halaman. Jika Anda menggunakan variabel Drupal, Anda akan memuat informasi memori tentang akun pengguna yang tidak perlu, karena hanya satu akun pengguna yang digunakan.
kiamlaluno
8

Saya ingat ini sering terjadi di Drupal 6 (tidak yakin apakah masih di Drupal 7), tetapi saya tidak pernah tahu mengapa. Saya sepertinya ingat melihat suatu tempat bahwa inti Drupal tidak memanggil kait ini dua kali.

Saya selalu menemukan cara termudah untuk menggunakan variabel statis untuk melihat apakah kode tersebut sudah dijalankan:

function MYMODULE_init() {
  static $code_run = FALSE;

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

Itu akan memastikan itu hanya dijalankan sekali dalam satu halaman memuat.

Clive
sumber
Secara definitif, bukan itu yang Drupal lakukan.
kiamlaluno
2
Ini bukan inti apa yang dilakukan, tetapi itu pasti terjadi (saya baru saja mengkonfirmasi bahwa pada tiga situs Drupal 6 lama, semua menjalankan modul contrib yang sebagian besar berbeda). Ini benar-benar head-scratcher tapi saya tidak punya waktu untuk men-debug mereka saat ini. Saya menduga itu adalah salah satu modul contrib yang lebih sering digunakan (mungkin pathauto atau redirect global), tetapi saya tidak ingin menunjukkan jari. Tidak begitu yakin mengapa jawaban Anda diturunkan (atau milik saya dalam hal ini), sepertinya informasi yang baik bagi saya. Saya telah memilih untuk mengembalikan saldo sedikit :)
Clive
Maksud saya bahwa Drupal tidak memiliki pemeriksaan seperti itu dalam hook_init()implementasinya, dan beberapa dari mereka akan dengan senang hati tidak dieksekusi dua kali berturut-turut. Mungkin juga OP ingin hook_init()dieksekusi sekali per hari, jika penghitung menghitung jumlah hari berturut-turut pengguna telah login di situs.
kiamlaluno
1
Ah ok, saya mengerti maksud Anda sekarang, ya pola statis di atas hanyalah yang saya gunakan di masa lalu untuk menyiasati masalah itu dipanggil dua kali di halaman yang sama; itu tidak ideal (ideal adalah untuk mencari tahu apa yang memintanya untuk kedua kalinya) tetapi sebagai perbaikan cepat itu akan melakukan trik. Apa yang Anda katakan tentang hari-hari berturut-turut kedengarannya benar, mungkin lebih baik dari OP yang hook_initmemeriksa untuk melihat apakah OP sudah berjalan sekali untuk hari itu dan menebusnya jika sudah. Kemudian semuanya menjadi non-masalah
Clive
5

Anda mungkin menemukan hook_init () dipanggil beberapa kali jika ada AJAX yang terjadi pada halaman (atau Anda memuat gambar dari direktori pribadi - meskipun saya tidak begitu yakin tentang itu). Ada beberapa modul yang menggunakan AJAX untuk membantu memotong caching halaman untuk elemen tertentu misalnya - cara termudah untuk memeriksa adalah dengan membuka monitor bersih di debugger pilihan Anda (firefox atau inspektur web) dan melihat apakah ada permintaan dibuat yang bisa memicu proses bootstrap.

Anda hanya akan mendapatkan dpm () pada pemuatan halaman berikutnya jika itu adalah panggilan AJAX. Jadi misalkan Anda me-refresh halaman 5 menit kemudian, Anda akan mendapatkan panggilan AJAX dari 5 menit lalu dengan pesan init dan yang baru.

Alternatif untuk hook_init () adalah hook_boot () yang dipanggil sebelum caching dilakukan. Tidak ada modul yang dimuat, jadi Anda benar-benar tidak memiliki banyak daya di sini selain mengatur variabel global dan menjalankan beberapa fungsi Drupal. Ini berguna untuk melewati caching level reguler (tetapi tidak akan memotong caching agresif).

Marton Bodonyi
sumber
1

Dalam kasus saya, perilaku ini disebabkan oleh modul Menu Administrasi (admin_menu).

hook_init tidak dipanggil setiap permintaan tetapi menu admin akan menyebabkan / js / admin_menu / cache / 94614e34b017b19a78878d7b96ccab55 untuk dimuat oleh browser pengguna tidak lama setelah permintaan utama, memicu bootstrap drupal lainnya.

Akan ada modul lain yang melakukan hal serupa tetapi admin_menu mungkin salah satu yang lebih umum digunakan.

Rimu Atkinson
sumber