Drupal 7 で hook_boot() と hook_init() と hook_preprocess_html() の特徴と使いどころ

今回も Drupal 7 のマニアックなお話です。 今回は「すべてのページリクエストに対してある処理を行いたい場合、どの関数を使うべきか」の判断ポイントについてまとめてみたいと思います。

Drupal 7 では、原則すべてのページリクエストにおいて実行される関数として次の 3 つがあります。

  • hook_boot()
  • hook_init()
  • hook_preprocess_html()

名前もリファレンスの説明文もよく似ていて見分けがつきにくいのですが、実行タイミングや特徴が少しずつ異な李ます。 そのちがいをきちんと把握しておけば使うべき関数を適材適所で正しく見極められるようになるかと思います。

今回は使い分けという視点でこの 3 つのフック関数を見比べてみましょう。

まずは hook_boot() から。

hook_boot()

hook_boot() は Drupal が処理を始める「ブートストラップフェーズ」の最中に呼び出される関数です。 順番としては hook_init()hook_preprocess_html() よりも先に呼ばれます。

drupal.org のドキュメントには次のような説明がなされています。

Perform setup tasks for all page requests.

This hook is run at the beginning of the page request. It is typically used to set up global parameters that are needed later in the request.

Only use this hook if your code must run even for cached page views. This hook is called before the theme, modules, or most include files are loaded into memory. It happens while Drupal is still in bootstrap mode.

意訳:

すべてのページリクエストに対するセットアップタスクを実行します。

このフックはページリクエストの最初に実行されます。 典型的な使用方法は、リクエストの後の方で必要となるグローバルパラメータの準備です。

このフックはキャッシュ済みページの閲覧でも実行される必要のある場合にのみ使用しましょう。 このフックはテーマやモジュール、インクルードファイルがメモリに読み込まれる前に実行されます。 Drupal がまだブートストラップモードのときに呼び出されます。

意訳終わり。

ということで、 hook_boot() は要は「 モジュールが読み込まれる前に呼ばれるフック 」とのこと。 ですので、「 Drupal API やモジュールのコードに依存しない生の PHP で書く処理を 」「 Drupal のプロセスの最初期に 」実行したいときにこのフックを使うとよいでしょう。

私の個人的な印象では、このフックの使用シーンはあまりなくて、ごくごく限られた場面でのみ使うようなイメージです。 hook_boot() が必要になるのはかなり特殊な状況なので、 hook_boot() を書く必要があると思える場合にはその処理が本当に必要かどうかをよく見極めた方がよいかと思います。 開発者がその特徴をよく理解しないまま使う可能性もあるため注意が必要です。

ちなみに、フック関数の呼び出し元は通常 module_invoke()module_invoke_all() ですが、このフック関数は bootstrap_invoke_all() という特殊な関数で呼び出されています。

つづいて hook_init() について。

hook_init()

hook_init() は Drupal のブートストラップ処理が終わり、さらに、すべてのモジュールの読み込みが終わった直後に呼び出される関数です。 順番としては「 hook_boot() よりも後、 hook_preprocess_html() よりも前」というタイミングで呼び出されます。

drupal.org には次のように説明されています。

Perform setup tasks for non-cached page requests.

This hook is run at the beginning of the page request. It is typically used to set up global parameters that are needed later in the request. When this hook is called, the theme and all modules are already loaded in memory.

This hook is not run on cached pages.

To add CSS or JS that should be present on all pages, modules should not implement this hook, but declare these files in their .info file.

意訳:

キャッシュされていないページリクエストのセットアップタスクを実行します。

このフックはページリクエストの最初に実行されます。 典型的な使用方法は、リクエストの後の方で必要となるグローバルパラメータの準備です。 このフックが呼ばれたとき、テーマとすべてのモジュールはすでにメモリ内に読み込まれています。

このフックはキャッシュ済みのページでは実行されません。

すべてのページで必要となる CSS や JS を追加するには、モジュールにこのフックを実装させてはいけません。 それらのファイルは .info ファイルで宣言するようにしましょう。

意訳終わり。

ということなので、つまり hook_init() は「 モジュールなどがメモリに読み込まれた直後に実行されるフック 」です。 上述のとおり、 Drupal コアや他のモジュールの API を利用したい場合には hook_boot() は原則使えません。 そのような場合は hook_init() を使うとよいでしょう。

ただし、 hook_init() の場合に注意すべきは上の説明書きにもある「キャッシュ済みのページではスキップされる」という特徴です。 匿名ユーザが多く利用するサイト(世の中のサイトの大多数)ではキャッシュを効かせる場合がほとんどかと思いますので、 hook_init() を使う場合にはこの点に注意が必要です。 hook_init() の処理結果がページに反映されてページキャッシュに取り込まれるような場合にはよいのですが、リクエストやコンテクストによって処理を切り替えたりデータベースに変更を加えたりするような場合には望みどおりの結果は得られないでしょう。 こちらもその特性をよく理解して使う必要があります。

最後に hook_preprocess_html() です。

hook_preprocess_html()

hook_preprocess_html() はページ全体のテンプレートである html というパーツを描画する直前に呼び出される関数です。 大きなくくりでいうと、 theme() 関数のシステムの中の hook_preprocess_HOOK() の実装のうちのひとつ、という位置づけにあります。 今回取り上げた 3 つのフック関数の中ではいちばん最後に呼び出されます。

drupal.org の hook_preprocess_HOOK() のページには次のように説明されています。

Preprocess theme variables for a specific theme hook.

This hook allows modules to preprocess theme variables for a specific theme hook. It should only be used if a module needs to override or add to the theme preprocessing for a theme hook it didn't define.

For more detailed information, see theme().

意訳:

特定の theme フックの theme 変数の前処理を行う。

このフックを使うとモジュールが特定の theme フックの theme 変数を前処理することができます。 これは、モジュールが自身では定義していない theme フックの前処理としてテーマの上書きや追加の必要がある場合にのみ使用すべきものです。

詳しくは theme() のページをご覧ください。

意訳終わり。

とのことなのでこちらはそのまま「 html というパーツのプリプロセス処理を行うフック 」ということになります。 (言い方がややこしいですが) html というパーツは Drupal が生成する HTML レスポンスの中には必ず埋め込まれるパーツなので、 hook_init() と同様、この関数も未キャッシュのページでは必ず実行されます。

hook_init() との大きなちがいとして、こちらには引数 $vars が与えられる点があります。 こちらは html というパーツのテンプレート変数を格納した連想配列です。 この中の値を利用して処理を切り替えると効果的な場合には hook_init() ではなく積極的にこちらの hook_preprocess_html() を使うとよいでしょう。

まとめ

今回は「すべてのページリクエストに処理をひっかけたいとき」に有用な 3 つのフック関数 hook_boot() hook_init() hook_preprocess_html() についてかんたんに比較してみました。

それぞれの特徴をふまえて「つかいどころ」をまとめてみると次のようになるでしょうか。

  • hook_boot(): キャッシュ済みのページも含めてすべてのページで処理を実行したいとき(ただし、 Drupal の API は原則使えないので通常の PHP を利用する必要あり)。
  • hook_init(): Drupal の API を利用して処理を実行したいとき(ただし、キャッシュ済のページではスキップされるので、そのことを理解した上で利用する/あるいは利用しない必要あり)。ページのリクエスト処理、レスポンス生成の前に処理を実行したいとき。
  • hook_preprocess_html(): Drupal の API と html テンプレート変数を利用して処理を実行したいとき(ただし、 hook_init() と同様キャッシュ済のページではスキップされることを理解した上で利用する必要あり)。

boot やら init やら preprocess やら、いずれも早期に実行されそうな名前はついていますが、実際の動きは名前だけではなかなか判断がつかないので、まとまて暗記する、あるいはこのようにメモに残しておくとよいかと思います。

以上です。