Alternativas a hook_init ()


8

Utilizo hook_init()para verificar el último tiempo de acceso de los usuarios. Si el último tiempo de acceso es ayer, incremente un contador y establezco algunas variables.

El problema es que a hook_init()veces se ejecuta más de una vez (puedo ver esto usando dsm()) para la misma carga de página, por lo que mi código se ejecuta varias veces dando como resultado variables incorrectas.

¿Por qué se hook_init()ejecuta más de una vez?
¿Cuál sería el mejor enfoque para mi problema? ¿Debo usar otro gancho?

Investigué un poco más sobre esto: busco llamadas al hook_init () (busqué cadena module_invoke_all('init');) pero encontré solo la llamada principal). No sé si esto se puede llamar de manera diferente.

Este es mi hook_init ()

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

y esta es la salida:

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

luego, cambió el mensaje dsm () dsm('2nd execution');y ejecutó nuevamente, esta es la salida:

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

Puede ver que el código se ejecuta dos veces. Sin embargo, la primera vez ejecuta una copia antigua del código y la segunda vez la copia actualizada. También hay una diferencia de tiempo de 2 segundos.

Esta es una versión d7 con php 5.3.10


Use ddebug_backtrace (), le dará la función de retroceso. Si realmente se llama varias veces, entonces esa función le dirá por quién.
Berdir

3
Tenga en cuenta que solo porque vea varios dsm (), no significa que el gancho se llame varias veces. También es posible que de hecho esté ejecutando múltiples solicitudes (por ejemplo, porque falta una imagen que da como resultado una página 404 manejada por Drupal)
Berdir

Para notar que entre 11:22:28 y 11:20:34 la diferencia es dos minutos, no dos segundos. En ese caso, el enlace no se ejecuta dos veces en la misma solicitud de página, o el valor para REQUEST_TIMEsería el mismo.
kiamlaluno

@kiamlaluno En la segunda ejecución, que es 2 minutos después de la primera, veo dos REQUEST_TIME, la hora actual y una hora anterior que es 2 segundos después de la primera solicitud. Esto me dice que el código se ejecuta dos veces. No puedo seguir tu lógica. ¿Por qué veo un REQUEST_TIME pasado para la solicitud actual?
Mike

No puedo contestar eso. Solo puedo decir que, si REQUEST_TIMEproviene de la misma solicitud de página, su valor es el mismo; Ni siquiera hay una diferencia de dos segundos. Verifique que no haya código que altere el valor de REQUEST_TIME.
kiamlaluno

Respuestas:


20

hook_init()Drupal invoca solo una vez para cada página solicitada; Es el último paso realizado en _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');
  }

Si hook_init()se ejecuta más de una vez, debe descubrir por qué sucede eso. Hasta donde puedo ver, ninguna de las hook_init()implementaciones en Drupal comprueba que se esté ejecutando dos veces (ver, por ejemplo, system_init () o update_init () ). Si eso es algo que normalmente puede suceder con Drupal, update_init()primero verificará si ya se ha ejecutado.

Si el contador es el número de días consecutivos que un usuario inició sesión, preferiría implementar un hook_init()código similar al siguiente.

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

Si hook_init()se invoca dos veces seguidas durante la misma solicitud de página, REQUEST_TIMEcontiene el mismo valor y la función volvería FALSE.

El código mymodule_increase_counter()no está optimizado; es solo para mostrar un ejemplo. En un módulo real, prefiero usar una tabla de base de datos donde se guardan el contador y las otras variables. La razón es que todas las variables de Drupal se cargan en la variable global $confcuando los bootstraps de Drupal (ver _drupal_bootstrap_variables () y variable_initialize () ); si usa variables de Drupal para eso, Drupal cargaría en la información de la memoria acerca de todos los usuarios para los que guardó información, cuando para cada página solicitada solo hay una cuenta de usuario guardada en la variable global $user.

Si está contando el número de páginas visitadas por los usuarios en días consecutivos, entonces implementaría el siguiente código.

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

Notarás que en mi código no uso $user->access. La razón es que $user->accesspodría actualizarse durante el arranque de Drupal, antes de que hook_init()se invoque. El controlador de escritura de sesión utilizado desde Drupal contiene el siguiente código. (Ver _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();
}

En cuanto a otro gancho que puede usar, con Drupal 7 puede usar hook_page_alter () ; simplemente no altera el contenido de $page, sino que aumenta su contador y cambia sus variables.
En Drupal 6, puede usar hook_footer () , el gancho llamado desde template_preprocess_page () . No devuelve nada, pero aumenta su contador y cambia sus variables.

En Drupal 6 y Drupal 7, puede usar hook_exit () . Tenga en cuenta que el gancho también se invoca cuando el bootstrap no está completo; el código no puede tener acceso a las funciones definidas desde los módulos u otras funciones de Drupal, y primero debe verificar que esas funciones estén disponibles. Algunas funciones siempre están disponibles hook_exit(), como las definidas en bootstrap.inc y cache.inc . La diferencia es que hook_exit()se invoca también para páginas en caché, mientras hook_init()que no se invoca para páginas en caché.

Finalmente, como ejemplo de código utilizado desde un módulo de Drupal, vea stats_exit () . El módulo Estadísticas registra las estadísticas de acceso de un sitio y, como puede ver, usa hook_exit()no hook_init(). Para poder llamar a las funciones necesarias, llama a drupal_bootstrap () pasando el parámetro correcto, como en el siguiente código.

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

Actualizar

Tal vez haya cierta confusión sobre cuándo hook_init()se invoca.

hook_init()se invoca para cada solicitud de página, si la página no está en caché. No se invoca una vez por cada solicitud de página que provenga del mismo usuario. Si visita, por ejemplo, http://example.com/admin/appearance/update , y luego http://example.com/admin/reports/status , hook_init()se invocará dos veces: una para cada página.
"El gancho se invoca dos veces" significa que hay un módulo que ejecuta el siguiente código, una vez que Drupal ha completado su arranque.

module_invoke_all('init');

Si ese es el caso, la siguiente implementación de hook_init()mostraría el mismo valor, dos veces.

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

Si su código se muestra para REQUEST_TIMEdos valores para los cuales la diferencia es de 2 minutos, como en su caso, el gancho no se invoca dos veces, sino que se invoca una vez para cada página solicitada, como debería suceder.

REQUEST_TIMEse define en bootstrap.inc con la siguiente línea.

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

Hasta que la página solicitada actualmente no se devuelva al navegador, el valor de REQUEST_TIMEno cambia. Si ve un valor diferente, está viendo el valor asignado en una página de solicitud diferente.


Hice algunas pruebas basadas en sus sugerencias. REQUEST_TIME no contiene el mismo valor que puede ver en la pregunta actualizada. Traté de encontrar invocaciones de hook_init () pero no encontré ninguna excepto una en el núcleo. Tal vez no estoy mirando de la manera correcta. Finalmente, hook_exit () parece hacer el truco, por lo que aceptaré esta respuesta. Sin embargo, estoy buscando respuestas sobre por qué se llama a hook_init () dos veces. Como pregunta adicional, sugiere utilizar una tabla de base de datos en lugar de variable_set / get. ¿Por qué no se recomienda esto? Variable_set / get usa una tabla db.
Mike

Las variables de Drupal usan una tabla de base de datos, pero se cargan todas en la memoria cuando Drupal arranca. Para cada página servida, Drupal arranca todo el tiempo, y solo hay una cuenta de usuario asociada con una solicitud de página. Si usa variables de Drupal, cargaría en la memoria información sobre cuentas de usuario que no son necesarias, ya que solo se usa una de las cuentas de usuario.
kiamlaluno

8

Recuerdo que esto sucedió mucho en Drupal 6 (no estoy seguro de si todavía sucede en Drupal 7), pero nunca supe por qué. Sin embargo, recuerdo haber visto en alguna parte que el núcleo de Drupal no llama a este gancho dos veces.

Siempre encontré que la forma más fácil de evitarlo era usar una variable estática para ver si el código ya se había ejecutado:

function MYMODULE_init() {
  static $code_run = FALSE;

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

Eso asegurará que solo se ejecute una vez en una sola carga de página.


Definitivamente, eso no es lo que hace Drupal.
kiamlaluno

2
No es lo que hace el núcleo , pero definitivamente sucede (acabo de confirmar que en tres sitios heredados de Drupal 6, todos ejecutan en su mayoría módulos de contribución diferentes). Es un verdadero rasguño de cabeza, pero no tengo tiempo para depurarlos en este momento. Sospecho que es uno de los módulos contrib más utilizados (tal vez pathauto o redirección global), pero no quiero señalar con el dedo. Sin embargo, no estoy muy seguro de por qué su respuesta fue rechazada (o la mía), me parece una buena información. He votado para restaurar un poco el equilibrio :)
Clive

Quiero decir que Drupal no tiene esa verificación en sus hook_init()implementaciones, y algunos de ellos evitarían ser ejecutados dos veces seguidas. También es probable que el OP quiera hook_init()ejecutarse una vez al día, si el contador cuenta el número de días consecutivos que los usuarios han iniciado sesión en el sitio.
kiamlaluno

1
Ah, ok, entiendo lo que quieres decir ahora, sí, el patrón estático anterior es el que he usado en el pasado para solucionar el problema de que se llame dos veces en la misma carga de página; no es lo ideal (lo ideal sería descubrir qué lo invoca la segunda vez) pero como solución rápida hará el truco. Lo que dices sobre los días consecutivos suena bien, probablemente mejor que el OP hook_initverifique si ya se ejecutó una vez por el día y rescata si lo ha hecho. Entonces, de todos modos
Clive

5

Es posible que se llame a hook_init () varias veces si ocurre algún AJAX en la página (o si está cargando imágenes desde un directorio privado, aunque no estoy tan seguro de eso). Hay algunos módulos que usan AJAX para ayudar a evitar el almacenamiento en caché de páginas para ciertos elementos, por ejemplo: la forma más fácil de verificar es abrir el monitor de red en el depurador de su elección (firefox o inspector web) y ver si hay alguna solicitud están hechos para desencadenar el proceso de arranque.

Sin embargo , solo obtendrá el dpm () en la carga de la página siguiente si se trata de una llamada AJAX. Supongamos que actualiza la página 5 minutos más tarde, recibirá la llamada AJAX del mensaje de inicio de hace 5 minutos, así como la nueva.

Una alternativa a hook_init () es hook_boot () que se llama antes de que se realice el almacenamiento en caché. Tampoco se han cargado módulos, por lo que realmente no tiene mucha potencia aquí, aparte de configurar variables globales y ejecutar algunas funciones de Drupal. Es útil para omitir el almacenamiento en caché de nivel regular (pero no omitirá el almacenamiento en caché agresivo).


1

En mi caso, este comportamiento fue causado por el módulo del Menú de Administración (admin_menu).

No se llamaba a hook_init en cada solicitud, pero el menú de administración provocaría que / js / admin_menu / cache / 94614e34b017b19a78878d7b96ccab55 fuera cargado por el navegador del usuario poco después de la solicitud principal, desencadenando otro arranque de drupal.

Habrá otros módulos que hacen cosas similares, pero admin_menu es probablemente uno de los más comúnmente implementados.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.