Umi Blog
ウミブログ

こんにちは。 スタジオ・ウミの南です。
Drupalのhookは数が多く、どれから覚えるべきか迷いがちです。 そこで、当社が手がけたDrupalサイトのカスタムコードを調べ、よく使われているhookを数えました。 結果、上位3つはいずれも画面の出力に手を入れるhookでした。
Drupalでの構築経験があり、hookをひととおり知っている方に向けて、どのhookから押さえるとよいかの目安を紹介します。
ランキングの前に、どのサイトを対象に、何をどう数えたのかを説明します。 数字の受け取り方に関わるため、結果だけを知りたい方も目を通してみてください。
hook_update_N・hook_install・hook_theme のように、必要になれば必ず書く定型のhookはランキングから除外サイト数を主にしたのは、1つのプロジェクトで同じhookを多用しているだけの偏りを避けるためです。
hookの実装は、手続き型の関数とクラスベースのメソッドの両方を拾いました。 どちらか一方だけでは実態を見誤るためです。 具体的には、次の手順で抽出しています。
modules/custom・themes/custom・profiles/custom 配下のファイルだけを取得する.module・.theme などのファイルから関数の定義を抜き出す。 関数名が「モジュールまたはテーマの機械名+_」で始まるものを候補にする。 機械名は、同じモジュールやテーマにある *.info.yml のファイル名から読み取る(mytheme.info.yml なら mytheme).php ファイルから #[Hook('...')] 属性を抜き出し、引数に書かれたhook名を候補にする*.api.php に定義されている hook_〇〇 の一覧と照合し、一致したものだけを数える。 たとえば手続き型の mytheme_preprocess_node は、機械名を除いた preprocess_node を照合する。 クラスベースの #[Hook('preprocess_node')] は、引数の preprocess_node をそのまま照合するform_user_login_form_alter のようにフォームIDやテーマフック名が入るものは、hook_form_FORM_ID_alter などにまとめて数える(元の名前は内訳として残す)4の照合により、コントリビュートモジュールが定義するhookや、フォームの送信処理のようにたまたま機械名で始まるだけの関数は数えていません。 そのため、この記事のランキングはコアが定義するhookに限ったものです。
hookは、Drupalが処理の要所で「何か変更や処理の追加をしたいモジュールはある?」と呼びかけ、各機能が「じゃあここ変更するね!」と割り込む仕組みです。 コアモジュールやコントリビュートモジュールなどの既存のコードを書き換えずに動作を変えられます。
長らく手続き型の関数で書くのが当たり前でしたが、Drupal 11系からは #[Hook] 属性を付けたクラスのメソッドとしても書けるようになりました。
手続き型のサポートは、早くてもDrupal 13で削除される見込みです(変更記録)。 今回の調査でも、クラスで書かれた実装が8サイト・54件ありました。 このうち7サイトはDrupal 11系です。 残る1サイトはDrupal 10系で、手続き型の関数からクラスのメソッドを呼びだす、Drupal 11系への移行を見越した書き方でした。 構築日の近いプロジェクトほど、この書き方に移っています。
集計する前に、次のように予想していました。
hook_preprocess_HOOKhook_form_FORM_ID_alterhook_theme_suggestions_HOOK2位と3位はどちらが多いか見当がつきませんでしたが、1位は大差になるだろうと考えていました。 結果は最後に振り返ります。
サイト上のすべてのフォーム構築時に割り込むhookです。 フォームが組み立てられた直後に呼ばれ、項目の追加や削除、表示条件の変更ができます。 対象を絞らないぶん、条件分岐を自分で書く必要があります。
使われ方としては、複数のフォームに同じ調整をまとめて入れる場面が目立ちました。
1つのフォームだけが対象なら、hook_form_FORM_ID_alter のほうが適しています。
hook_page_attachments_alter は、ページに読み込まれるCSS・JavaScriptや head 内のメタ情報を、HTML出力の直前に変更・削除するためのhookです。 他モジュールが読み込んだ不要なアセットを取り除いたり、既存の設定を上書きする際に使用します。
18サイトで使われている一方、実装は33件と少なく、ほとんどのサイトでは1〜2か所でした(11か所あったのは、兄弟サイト群をまとめて1サイトとして数えたものです)。 用途は次のようなものです。
いずれも多くのサイトに1つか2つある「全体に関わる決め事」で、それを書く場所として使われています。
テンプレートの候補名を増やしたり並べ替えたりするhookです。 候補を増やすことで、node--news--teaser.html.twig のように条件を絞ったテンプレートファイルを使えるようになります。 Twig側の条件分岐(if文)を最小限に抑えられるのが最大のメリットです。
名前のよく似た hook_theme_suggestions_HOOK もあります。 こちらは自分のモジュールから候補を提案するものです。 一方、今回の3位である _alter が付くほうは、集まった候補の一覧を受け取って並べ替えたり削除したりできます。 他のモジュールやテーマが出した候補にも手を入れられるぶん、実務では _alter のほうがよく使われていました。
対象の内訳は次のとおりです。
フォームごとに異なるマークアップを当てたい、同じブロックでも配置場所によって見た目を変えたい、といった要望に応えるための使い方です。 デザインの都合をテンプレートの出し分けで解決する場面が多く見られました。
フォームIDを指定して、そのフォームだけに手を入れるhookです。 第5位の hook_form_alter と役割は同じですが、対象が1つに絞られるぶん、条件分岐が不要になり、コードの可読性やメンテナンス性が高まります。 また、対象フォームの生成時のみ実行されるため効率的です。
対象の内訳は次のとおりです。
views_exposed_form):20件検索や一覧の絞り込みの表示を整える、編集画面の項目の並びを実務に合わせる、といった調整が中心でした。
なお、120件のうち11件はすでに #[Hook] 属性を使ったクラスとして書かれており、ベスト5のなかでは新しい書き方への移行が最も進んでいます。
Twigテンプレートに渡す変数を、描画の直前に用意・加工するためのhookです。 テンプレート側のロジックを減らし、表示に必要な値をPHP側で整えられます。
31サイト中28サイトで使われ、実装数は392件と、2位の3倍を超えました。 予想どおり、大差での1位です。 対象の内訳は次のとおりです。
page:33件html:31件views_view:26件node:25件breadcrumb:21件具体的には、次のような用途で使われていました。
どれもテンプレートだけでは書きにくく、かといって独自のブロックやフィールドを作るほどではない、という隙間を埋めています。 この「隙間を埋められるちょうどよさ」が実装数の多さにつながっているのだと感じました。
ベスト5は次のとおりでした。
| 順位 | hook | サイト数 | 実装数 |
|---|---|---|---|
| 1 | hook_preprocess_HOOK | 28 / 31 | 392 |
| 2 | hook_form_FORM_ID_alter | 23 / 31 | 120 |
| 3 | hook_theme_suggestions_HOOK_alter | 19 / 31 | 97 |
| 4 | hook_page_attachments_alter | 18 / 31 | 33 |
| 5 | hook_form_alter | 12 / 31 | 23 |
上位3つは、テンプレートに渡す変数、フォームの中身、テンプレートの選び方と、いずれも画面の出力に手を入れるhookでした。 コンテンツの構造や項目はサイトビルディングで組み立て、最後の見え方の調整にhookを使う、という進め方が数字にも表れています。 逆に、エンティティの保存時に処理を挟む hook_ENTITY_TYPE_presave のようなhookは8サイトにとどまりました。
予想と照らすと、1位から3位まではほぼ的中でした。 「ほぼ」というのは、3位に挙げていたのが hook_theme_suggestions_HOOK で、実際に多かったのは _alter が付くほうだったためです。 1位が大差になるという見立ても、実装数で2位の3倍を超えたという形で当たっています。 一方で4位以下は予想できていませんでしたが、hook_page_attachments_alter が18サイトと上位に入ったのは意外でした。 ほとんどのサイトで1〜2か所しか書かないものが、これだけ多くのサイトで必要とされているということです。
hookをひととおり覚えようとすると数の多さに気圧されますが、実務で繰り返し登場するものは限られます。 まずは上位3つを押さえておくと、たいていの場面で足りるはずです。
なお、新しくhookを実装する際は、使っているDrupalのバージョンでの対応状況を確かめたうえで、#[Hook] 属性を使ったクラスでの実装も検討してみてください。

Masters of Drupal Engineering.
人に、