web-dev-qa-db-fra.com

Alternatives à hook_init ()

J'utilise hook_init() pour vérifier le dernier temps d'accès des utilisateurs. Si le dernier temps d'accès est hier, j'incrémente un compteur et définit quelques variables.

Le problème est que hook_init() est parfois exécuté plus d'une fois (je peux le voir en utilisant dsm()) pour le même chargement de page, donc mon code est exécuté plusieurs fois, ce qui entraîne des variables erronées.

Pourquoi hook_init() est-il exécuté plus d'une fois?
Quelle serait la meilleure approche de mon problème? Dois-je utiliser un autre crochet?

J'ai encore creusé: Je recherche des appels à hook_init () (j'ai recherché la chaîne module_invoke_all('init');) mais je n'ai trouvé que l'appel principal). Je ne sais pas si cela peut être appelé différemment.

C'est mon 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.')');
}

et voici la sortie:

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

puis, changé le message dsm () en dsm('2nd execution'); et exécuté à nouveau, voici la sortie:

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

Vous pouvez voir que le code est exécuté deux fois. Cependant, la première fois exécute une ancienne copie du code et la deuxième fois la copie mise à jour. Il y a aussi un décalage horaire de 2 secondes.

Ceci est une version d7 avec php 5.3.10

8
Mike

hook_init() est invoquée par Drupal une seule fois pour chaque page demandée; c'est la dernière étape effectuée dans _ 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() est exécuté plusieurs fois, vous devriez découvrir pourquoi cela se produit. Pour autant que je sache, aucune des implémentations de hook_init() dans Drupal vérifie qu'il est exécuté deux fois (voir par exemple system_init () , ou pdate_init () ). Si c'est quelque chose qui peut normalement arriver avec Drupal, alors update_init() vérifiera d'abord s'il a déjà été exécuté.

Si le compteur est le nombre de jours consécutifs pendant lesquels un utilisateur s'est connecté, je préfère implémenter hook_init() avec un code similaire au suivant.

// 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() est invoqué deux fois de suite lors de la même demande de page, REQUEST_TIME Contient la même valeur et la fonction renverra FALSE.

Le code dans mymodule_increase_counter() n'est pas optimisé; c'est juste pour montrer un exemple. Dans un vrai module, je préfère utiliser une table de base de données où le compteur et les autres variables sont enregistrés. La raison en est que Drupal sont toutes chargées dans la variable globale $conf Lorsque Drupal bootstraps (voir _ drupal_bootstrap_variables () , et variable_initialize () ); si vous utilisez Drupal variables pour cela, Drupal chargerait en mémoire des informations sur tous les utilisateurs pour lesquels vous avez enregistré des informations, alors que pour chaque page demandée, il n'y a qu'un seul compte d'utilisateur enregistré dans la variable globale $user.

Si vous comptez le nombre de pages visitées par les utilisateurs au cours des jours consécutifs, j'implémenterais le code suivant.

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

Vous remarquerez que dans mon code, je n'utilise pas $user->access. La raison en est que $user->access Pourrait être mis à jour pendant Drupal bootstrap, avant l'appel de hook_init(). Le gestionnaire d'écriture de session utilisé à partir de Drupal contient le code suivant. (Voir _ 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 ce qui concerne un autre hook que vous pouvez utiliser, avec Drupal 7 vous pouvez utiliser hook_page_alter () ; vous ne modifiez simplement pas le contenu de $page, mais augmentez votre compteur et changez vos variables.
Sur Drupal 6, vous pouvez utiliser hook_footer () , le hook appelé depuis template_preprocess_page () . Vous ne le faites pas ' t ne renvoie rien, mais augmentez votre compteur et modifiez vos variables.

Sur Drupal 6 et Drupal 7, vous pouvez utiliser hook_exit () . Gardez à l'esprit que le crochet est également appelé lorsque le bootstrap n'est pas complet; le code n'a pas pu accéder aux fonctions définies à partir des modules, ou à d'autres fonctions Drupal, et vous devez d'abord vérifier que ces fonctions sont disponibles . Certaines fonctions sont toujours disponibles à partir de hook_exit(), telles que celles définies dans bootstrap.inc et cache.inc . La différence est que hook_exit() est également invoquée pour les pages en cache, tandis que hook_init() n'est pas invoquée pour les pages en cache.

Enfin, comme exemple de code utilisé à partir d'un module Drupal, voir statistics_exit () . Le module Statistics enregistre les statistiques d'accès pour un site, et comme vous le voyez, il utilise hook_exit(), pas hook_init(). Pour pouvoir appeler les fonctions nécessaires, il appelle drupal_bootstrap () en passant le paramètre correct, comme dans le code suivant.

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

Mise à jour

Il y a peut-être une certaine confusion au sujet de l'appel de hook_init().

hook_init() est invoquée pour chaque demande de page, si la page n'est pas mise en cache. Elle n'est pas invoquée une fois pour chaque demande de page provenant du même utilisateur. Si vous visitez, par exemple, http://example.com/admin/appearance/update , puis http://example.com/admin/reports/status , hook_init() sera invoqué deux fois: un pour chaque page.
"Le hook est appelé deux fois" signifie qu'il existe un module qui exécute le code suivant, une fois Drupal a terminé son bootstrap.

module_invoke_all('init');

Si tel est le cas, l'implémentation suivante de hook_init() afficherait la même valeur, deux fois.

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

Si votre code affiche pour REQUEST_TIME Deux valeurs pour lesquelles la différence est de 2 minutes, comme dans votre cas, alors le hook n'est pas appelé deux fois, mais il est appelé une fois pour chaque page demandée, comme cela devrait arriver.

REQUEST_TIME Est défini dans bootstrap.inc avec la ligne suivante.

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

Tant que la page actuellement demandée n'est pas renvoyée au navigateur, la valeur de REQUEST_TIME Ne change pas. Si vous voyez une valeur différente, vous regardez la valeur attribuée dans une autre page de demande.

20
kiamlaluno

Je me souviens que cela se passait beaucoup en Drupal 6 (je ne sais pas si c'est toujours le cas en Drupal 7), mais je n'ai jamais su pourquoi. Je semble me souvenir voir quelque part que Drupal core n'appelle pas ce hook deux fois cependant.

J'ai toujours trouvé que le moyen le plus simple était d'utiliser une variable statique pour voir si le code avait déjà été exécuté:

function MYMODULE_init() {
  static $code_run = FALSE;

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

Cela garantira qu'il ne sera exécuté qu'une seule fois dans un seul chargement de page.

8
Clive

Vous pourriez trouver hook_init () est appelé plusieurs fois s'il y a un AJAX passe sur la page (ou vous chargez des images à partir d'un répertoire privé - bien que je ' Je ne suis pas sûr de cela.) Il y a quelques modules qui utilisent AJAX pour aider à contourner la mise en cache des pages pour certains éléments par exemple - la façon la plus simple de vérifier est d'ouvrir le moniteur net dans votre débogueur de choix (firefox ou inspecteur web) et regarder pour voir si des requêtes sont faites qui pourraient déclencher le processus bootstrap.

Vous n'obtiendrez que dpm () lors du prochain chargement de la page, mais s'il s'agit d'un appel AJAX. Donc, dites que vous actualisez la page 5 minutes plus tard, vous ' ll obtiendra AJAX appel du message init il y a 5 minutes ainsi que le nouveau.

Une alternative à hook_init () est hook_boot () qui est appelée avant toute mise en cache. Aucun module n'est chargé pour le moment non plus, donc vous n'avez vraiment pas beaucoup de pouvoir ici, à part définir des variables globales et exécuter quelques fonctions Drupal. Il est utile pour contourner la mise en cache de niveau normal (mais gagné) t contourner la mise en cache agressive).

5
Marton Bodonyi

Dans mon cas, ce comportement est dû au module du menu d'administration (admin_menu).

hook_init n'était pas appelé à chaque demande, mais le menu admin entraînerait le chargement de/js/admin_menu/cache/94614e34b017b19a78878d7b96ccab55 par le navigateur de l'utilisateur très peu de temps après la demande principale, déclenchant un autre drupal bootstrap.

Il y aura d'autres modules qui font des choses similaires, mais admin_menu est probablement l'un des plus couramment déployés.

1
Rimu Atkinson