it-swarm.com.de

Alternativen zu hook_init ()

Ich benutze hook_init(), um die letzte Zugriffszeit der Benutzer zu überprüfen. Wenn die letzte Zugriffszeit gestern ist, erhöhe ich einen Zähler und setze einige Variablen.

Das Problem ist, dass hook_init() manchmal mehr als einmal ausgeführt wird (ich kann dies mit dsm() sehen), wenn dieselbe Seite geladen wird, sodass mein Code mehrmals ausgeführt wird, was zu falschen Variablen führt.

Warum wird hook_init() mehrmals ausgeführt?
Was wäre der beste Ansatz für mein Problem? Soll ich einen anderen Haken verwenden?

Ich habe etwas mehr darüber gegraben : Ich suche nach Aufrufen von hook_init () (gesucht nach Zeichenfolge module_invoke_all('init');), habe aber nur den Kernaufruf gefunden). Ich weiß nicht, ob dies anders genannt werden kann.

Das ist mein 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.')');
}

und das ist die Ausgabe:

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

dann wurde die Nachricht dsm () in dsm('2nd execution'); geändert und erneut ausgeführt. Dies ist die Ausgabe:

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

Sie können sehen, dass der Code zweimal ausgeführt wird. Beim ersten Mal wird jedoch eine alte Kopie des Codes ausgeführt, beim zweiten Mal die aktualisierte Kopie. Es gibt auch einen Zeitunterschied von 2 Sekunden.

Dies ist eine d7-Version mit PHP 5.3.10

8
Mike

hook_init() wird von Drupal nur einmal für jede angeforderte Seite aufgerufen; Dies ist der letzte Schritt in _ 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');
  }

Wenn hook_init() mehrmals ausgeführt wird, sollten Sie herausfinden, warum dies geschieht. Soweit ich sehen kann, überprüft keine der Implementierungen von hook_init() in Drupal, dass sie zweimal ausgeführt wird (siehe zum Beispiel system_init () oder - pdate_init () ). Wenn dies normalerweise bei Drupal der Fall ist, prüft update_init() zunächst, ob es bereits ausgeführt wurde.

Wenn der Zähler die Anzahl der aufeinanderfolgenden Tage ist, an denen sich ein Benutzer angemeldet hat, würde ich lieber hook_init() mit einem Code implementieren, der dem folgenden ähnlich ist.

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

Wenn hook_init() während derselben Seitenanforderung zweimal hintereinander aufgerufen wird, enthält REQUEST_TIME Diesen Wert und die Funktion würde FALSE zurückgeben.

Der Code in mymodule_increase_counter() ist nicht optimiert; Es soll nur ein Beispiel zeigen. In einem realen Modul würde ich lieber eine Datenbanktabelle verwenden, in der der Zähler und die anderen Variablen gespeichert sind. Der Grund ist, dass Drupal Variablen alle in die globale Variable $conf Geladen werden, wenn Drupal Bootstraps (siehe _ drupal_bootstrap_variables () und - variable_initialize () ); Wenn Sie dafür Drupal -Variablen verwenden, lädt Drupal Informationen über alle Benutzer, für die Sie Informationen gespeichert haben, in den Speicher, wenn für jede angeforderte Seite nur ein Benutzerkonto im gespeichert ist globale Variable $user.

Wenn Sie die Anzahl der von den Benutzern an aufeinanderfolgenden Tagen besuchten Seiten zählen, würde ich den folgenden Code implementieren.

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

Sie werden feststellen, dass ich in meinem Code nicht $user->access Verwende. Der Grund ist, dass $user->access Während des Bootstraps Drupal aktualisiert werden kann, bevor hook_init() aufgerufen wird. Der von Drupal verwendete Sitzungsschreibhandler enthält den folgenden Code. (Siehe _ 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();
}

Für einen anderen Hook, den Sie verwenden können, können Sie mit Drupal 7 hook_page_alter () verwenden; Sie ändern nur nicht den Inhalt von $page, sondern erhöhen Ihren Zähler und ändern Ihre Variablen.
Auf Drupal 6 können Sie hook_footer () verwenden, den Hook, der von template_preprocess_page () aufgerufen wird. Sie geben nichts zurück, sondern erhöhen Ihren Zähler und ändern Ihre Variablen.

Auf Drupal 6 und Drupal 7 können Sie hook_exit () verwenden. Beachten Sie, dass der Hook auch aufgerufen wird, wenn bootstrap nicht vollständig ist. Der Code konnte keinen Zugriff auf Funktionen haben, die von Modulen oder anderen Drupal -Funktionen definiert wurden, und Sie sollten zuerst überprüfen, ob diese Funktionen verfügbar sind. Einige Funktionen sind immer über hook_exit() verfügbar, z. B. die in bootstrap.inc und cache.inc definierten. Der Unterschied besteht darin, dass hook_exit() auch für zwischengespeicherte Seiten aufgerufen wird, während hook_init() für zwischengespeicherte Seiten nicht aufgerufen wird.

Als Beispiel für Code, der von einem Drupal -Modul verwendet wird, siehe statistics_exit () . Das Statistikmodul protokolliert Zugriffsstatistiken für eine Site und verwendet, wie Sie sehen, hook_exit() und nicht hook_init(). Um die erforderlichen Funktionen aufrufen zu können, wird drupal_bootstrap () aufgerufen, wobei der richtige Parameter übergeben wird, wie im folgenden Code.

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

Aktualisieren

Möglicherweise gibt es einige Verwirrung darüber, wann hook_init() aufgerufen wird.

hook_init() wird für jede Seitenanforderung aufgerufen, wenn die Seite nicht zwischengespeichert ist. Es wird nicht einmal für jede Seitenanforderung aufgerufen, die vom selben Benutzer stammt. Wenn Sie beispielsweise http://example.com/admin/appearance/update und dann http://example.com/admin/reports/status besuchen , hook_init() wird zweimal aufgerufen: eine für jede Seite.
"Der Hook wird zweimal aufgerufen" bedeutet, dass es ein Modul gibt, das den folgenden Code ausführt, sobald Drupal seinen Bootstrap abgeschlossen hat.

module_invoke_all('init');

Wenn dies der Fall ist, würde die folgende Implementierung von hook_init() zweimal denselben Wert anzeigen.

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

Wenn Ihr Code für REQUEST_TIME Zwei Werte anzeigt, für die die Differenz wie in Ihrem Fall 2 Minuten beträgt, wird der Hook nicht zweimal aufgerufen, sondern einmal für jede angeforderte Seite, wie es passieren sollte.

REQUEST_TIME Ist in bootstrap.inc mit der folgenden Zeile definiert.

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

Bis die aktuell angeforderte Seite nicht an den Browser zurückgegeben wird, ändert sich der Wert von REQUEST_TIME Nicht. Wenn Sie einen anderen Wert sehen, beobachten Sie den auf einer anderen Anforderungsseite zugewiesenen Wert.

20
kiamlaluno

Ich erinnere mich, dass dies in Drupal 6) häufig passiert ist (ich bin mir nicht sicher, ob dies in Drupal 7) immer noch der Fall ist, aber ich habe nie herausgefunden, warum. Ich scheine mich zu erinnern irgendwo zu sehen, dass Drupal Core diesen Hook jedoch nicht zweimal aufruft.

Ich fand es immer am einfachsten, eine statische Variable zu verwenden, um festzustellen, ob der Code bereits ausgeführt wurde:

function MYMODULE_init() {
  static $code_run = FALSE;

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

Dadurch wird sichergestellt, dass es beim Laden einer einzelnen Seite nur einmal ausgeführt wird.

8
Clive

Möglicherweise wird hook_init () mehrmals aufgerufen, wenn auf der Seite AJAX) passiert (oder Sie Bilder aus einem privaten Verzeichnis laden - obwohl ich ' Ich bin mir da nicht so sicher.) Es gibt einige Module, die AJAX] verwenden, um das Seiten-Caching für bestimmte Elemente zu umgehen. Die einfachste Möglichkeit, dies zu überprüfen, besteht darin, den Netzmonitor in Ihrem zu öffnen Debugger Ihrer Wahl (Firefox oder Web Inspector) und prüfen, ob Anforderungen gestellt wurden, die den Prozess bootstrap) auslösen könnten.

Sie erhalten das dpm () nur beim Laden der nächsten Seite, wenn es sich um einen AJAX -Aufruf) handelt. Nehmen wir also an, Sie aktualisieren die Seite 5 Minuten später. Ich erhalte den AJAX -Aufruf aus der Init-Nachricht von vor 5 Minuten sowie den neuen.

Eine Alternative zu hook_init () ist hook_boot (), die aufgerufen wird, bevor überhaupt ein Caching durchgeführt wird. Es sind auch noch keine Module geladen, so dass Sie hier wirklich nicht viel Leistung haben, außer globale Variablen festzulegen und einige Drupal -Funktionen auszuführen. Es ist nützlich, um das reguläre Level-Caching zu umgehen (aber gewonnen) t aggressives Caching umgehen).

5
Marton Bodonyi

In meinem Fall wurde dieses Verhalten durch das Verwaltungsmenü-Modul (admin_menu) verursacht.

hook_init wurde nicht bei jeder Anfrage aufgerufen, aber das Admin-Menü führte dazu, dass/js/admin_menu/cache/94614e34b017b19a78878d7b96ccab55 sehr kurz nach der Hauptanforderung vom Browser des Benutzers geladen wurde und einen weiteren drupal bootstrap) auslöste.

Es wird andere Module geben, die ähnliche Aufgaben ausführen, aber admin_menu ist wahrscheinlich eines der am häufigsten bereitgestellten.

1
Rimu Atkinson