💧

Drupal 11.1 から一部クラスベースになる予定 の hook を試してみる

2024/12/04に公開

はじめに

Drupal 7 から Drupal 8 にアップグレードされたときに、多くのコードはクラスをベースとするコードに置き換わりました。しかし、hook に関しては Drupal 11.0 がリリースされている現在でも、.install や .module ファイルに hook の関数を書くコードが残っています。
ところが、2024年11月に Drupal コア向けの MR からたまたま、hook をクラスベースにするための大規模の変更があったことを発見し、クラスベースに hook の実装を変更する取り組みが Drupal コアの issue として作成されていました。
本記事では、クラスベースにならない hook を示した後、クラスベースでの hook 実装時の必要事項を示し、最後にクラスベースでの hook の実装コード例を示します。

クラスベースにならない hook

クラスベースにならない hook は 2024/12/01 現在の Drupal コアのコードでは、以下の通りです。(参考

  • モジュールインストールに関する hook
    • .install ファイル: hook_schema()
    • .install ファイル: hook_update_last_removed()
    • .install ファイル: hook_install()
    • .install ファイル: hook_uninstall()
    • .install ファイル: hook_requirements()
    • .install ファイル: hook_update_N()
    • .module ファイル: hook_module_implements_alter()
    • .module ファイル: hook_hook_info()
  • レンダリング、テンプレート関係
    • .module ファイル: hook_theme_suggestions_HOOK()
      • hook_theme_suggestions_HOOK_alter() はクラスベースの実装が可能
    • .module ファイル: template_preprocess_HOOK()
    • .module ファイル: hook_preprocess_HOOK()
    • .module ファイル: hook_preprocess_views_view()
    • .module ファイル: hook_preprocess_views_view_fields()

いずれ、これらの hook もクラスベースの実装になるとは思いますが、Drupal 11.1 時点では対応は不要そうです。

クラスベースでの hook の実装

クラスベースで hook を実装する場合、モジュールの /src/Hook ディレクトリにクラスファイルを格納する必要があります。同ディレクトリに格納されていればファイル名は何でも良いようですが、Drupal コアではモジュール名で始まるファイル名になっています。
ファイルの数もディレクトリ内に複数有っても動作するため、用途別にクラスのファイルを用意しても問題ありません。Drupal コアでは、Token 周りや Views 周りは別ファイルに分けていることが多いです。

アトリビュートの設定

Drupal にクラス内のメソッドが hook だと認識させるため、メソッドの前に #[Hook('hook_something')] というアトリビュートを記述する必要があります。hook の種類が help なら #[Hook('help')] に、node_presave なら #[Hook('node_presave')] になります。

メソッドの命名

.module ファイルなどに hook 関数を書いていたとき、モジュール名が awesome_module でテーマの hook なら function awesome_module_theme() と決まっていました。
では、クラスベースでの hook のメソッド名はどのような命名規則があるかというと、アトリビュートが合っていればどのような命名でも問題ないようです。Drupal コアでは、実装する hook の種類をキャメルケースにしたメソッド名にしているようです。(例:user_presave なら userPresave、views_query_substitutions なら viewsQuerySubstitutions)
ただし、node モジュールではユーザアカウントのキャンセル時に実行される user_cancel の hook は同じ hook で二種類の動作を定義するため、この命名規則は当てはまりません

クラスベースでの hook の実装コード例

クラスベースで hook を実装したコードは下のようになります。(サンプルモジュールのレポジトリはこちら

<?php

namespace Drupal\awesome_module\Hook;

use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountProxyInterface;

/**
 * Hook implement testing class.
 */
class AwesomeModuleHooks {

  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected AccountProxyInterface $currentUser;

  public function __construct() {
    $container = \Drupal::getContainer();
    $this->currentUser = $container->get('current_user');
  }

  /**
   * Implements hook_help().
   */
  #[Hook('help')]
  public function help($route_name, RouteMatchInterface $route_match) {
    if ($route_name == 'help.page.awesome_module') {
      return 'Awesome module help page.';
    }
    return '';
  }

  /**
   * Implements hook_theme().
   */
  #[Hook('theme')]
  public function theme($existing, $type, $theme, $path) : array {
    return [
      'awesome_module' => [
        'template' => 'awesome_module',
        'variables' => [
          'type' => $type,
          'theme' => $theme,
          'path' => $path,
        ]
      ]
    ];
  }

  /**
   * Implements hook_form_FORM_ID_alter().
   */
  #[Hook('form_user_form_alter')]
  public function formUserFormAlter(array &$form, FormStateInterface $form_state) : array {
    $logged_in_user = $this->currentUser->getAccountName();

    $form['awesome_module'] = [
      '#type' => 'container',
      '#weight' => -100,
    ];

    $form['awesome_module']['title'] = [
      '#markup' => '<h2>Awesome user</h2>',
    ];

    $form['awesome_module']['name'] = [
      '#plain_text' => $logged_in_user,
    ];

    return $form;
  }

}

大半の場合は、hook_something で書いていたコードを持ってくれば正しく動くと思います。もし、サービスコンテナにある特定のサービスを hook のクラスで使用する場合は、コンストラクタで protected プロパティに代入する形にしたり、hook メソッド内でサービスコンテナを呼び出す形にすれば良いと思います。

おわりに

本記事では、Drupal 11.1 から入る予定のクラスベースでの hook のコードについて記述しました。
.module ファイルに書いていた hook 関数のコードはクラスベースの hook になったとしても、大きな修正を加えることなく、実装できることを示しました。
2026年初頭まで Drupal 10 系もサポートバージョンである関係上、Drupal 12 がリリースされるまでは、正式リリースされている Drupal のバージョンには旧来の .module ファイルに hook 関数を書く仕組みも残るとは思います。
とはいえ、2026年初頭の Drupal 12 リリース時点で、Drupal 11 にのみ .module ファイルに hook 関数を書いて動かす仕組みはあっても、Drupal 12 では廃止されている可能性もあるため、その辺は今後の動きにも注目が必要だと思います。

本記事は、Drupal Advent Calendar 2024 に向けに作成した記事です。

Discussion