Drupal開発Tips 10選
X-tech推進本部 副本部長 新井Drupal未経験状態から、1案件の導入を経験した程度の初級者です。そんな私が、初Drupal導入において、有用であった開発者向けの情報をいくつかピックアップして整理してみました。これからDrupalを始める方々の参考になれば幸いです。
カスタムテーマ開発Tips
1. 開発環境をデバッグに最適化する
開発中の変更が即座に反映され、問題の原因を特定しやすい環境は、生産性を飛躍的に向上させます。
Twigデバッグモードを有効にする
HTMLソース内に、どのテンプレートファイルが使われているか、どのファイル名でオーバーライドできるかのヒントがコメント出力されます。テーマの構造理解やカスタマイズ箇所の特定に絶大な効果を発揮します。
- 設定方法
- sites/development.services.ymlを有効にし、- twig.configの- debugを- trueに設定します。- settings.phpや- settings.local.phpでこのファイルを読み込むことを忘れないようにしましょう。その後、キャッシュクリアが必要です。- parameters: twig.config: debug: true auto_reload: true cache: false
 
キャッシュを完全に無効化する
Twigファイル、CSS、プリプロセス関数などを変更しても、キャッシュが原因で反映されないことが頻繁にあります。開発中はキャッシュを無効化し、ストレスなく作業を進めましょう。
- 設定方法 - settings.local.phpなどで、以下の設定を有効にします。- // レンダリングキャッシュと動的ページキャッシュを無効化 $settings['cache']['bins']['render'] = 'cache.backend.null'; $settings['cache']['bins']['dynamic_page_cache'] = 'cache.backend.null'; // CSS/JSのアグリゲーション(結合・圧縮)を無効化 $config['system.performance']['css']['preprocess'] = FALSE; $config['system.performance']['js']['preprocess'] = FALSE;
 
- キャッシュクリア - 設定変更後や予期せぬ挙動があった際は、まず drush cr(またはdrush cache:rebuild) コマンドでキャッシュをクリアするのが定石です。
 
- 設定変更後や予期せぬ挙動があった際は、まず 
テンプレートファイル名の確認
Twigデバッグモードを有効にしてページのソースを表示すると、各パーツが以下のようなコメントで囲まれているのが見えます。
<!-- THEME DEBUG -->
<!-- THEME HOOK: 'node' -->
<!-- FILE NAME SUGGESTIONS:
   * node--2--full.html.twig
   * node--2.html.twig
   * node--article--full.html.twig
   x node--article.html.twig
   * node--full.html.twig
   * node.html.twig
-->
<!-- BEGIN OUTPUT from 'core/themes/olivero/templates/content/node.html.twig' -->
... (ここに実際のHTMLが出力される) ...
<!-- END OUTPUT from 'core/themes/olivero/templates/content/node.html.twig' -->このコメントには、大きく分けて3つの重要な情報が含まれています。
- THEME HOOK(テーマフック)- 'node'と表示されています。これは、Drupalがこの部分を描画するために- nodeというテーマフックを使っていることを示します。これにより、開発者は「これはノードを描画している部分だな」と根本的な種類を理解できます。
 
- BEGIN OUTPUT from ...(現在使用中のテンプレート)- 'core/themes/olivero/templates/content/node.html.twig'と表示されています。これは、今現在、実際にHTMLの生成に使われているテンプレートファイルのパスです。カスタマイズしたい場合、まずこのファイルをコピー元のベースとして考えます。
 
- FILE NAME SUGGESTIONS(テンプレート名の候補)- Drupalは、「もしこの部分をカスタマイズするためのテンプレートファイルを作りたいなら、これらの名前が使えますよ」という候補を優先順位の高い順にリストアップしてくれています。
- xが付いている- node--article.html.twigは、現在使用中のテンプレートを示します。(※バージョンの違いで- xではなく- BEGIN OUTPUTのファイル名が現在のものを示す場合もあります)
- *が付いているものは、使用可能な候補です。
 
2. Twigテンプレートで利用可能な変数を探る
「この部分のテキストを変えたい」「新しい情報を追加したい」という時、どの変数を使えばよいかを知る必要があります。
dump() 関数や kint() を使う
Twigテンプレートファイル内で {{ dump() }} や {{ kint() }} を記述すると、そのテンプレートで利用可能な全ての変数を画面上に展開して確認できます。
- 使い方
- 例えば node.html.twigの中で{{ dump(content) }}と書けば、content変数の中身を詳細に確認できます。
 
- 例えば 
- kint()の利点- Develモジュールを有効にすると使える- kint()は、- dump()よりも階層構造が見やすく、インタラクティブにドリルダウンできるため特におすすめです。
 
3. プリプロセス関数でテンプレートに渡す変数を加工する
ロジックを含む複雑な処理は、Twigテンプレート内ではなくPHPで行うのがDrupalの作法です。プリプロセス関数は、テンプレートがレンダリングされる直前に変数を追加・変更・削除するための強力な仕組みです。
THEME_preprocess_HOOK() を活用する
テーマのルートにある *.theme ファイル(例: mytheme.theme)に記述します。HOOK の部分には、対象となるテンプレートの種類が入ります(例: node, page, block)。
- 具体例 - 記事(article)タイプのノードにだけ特別なCSSクラスを追加したい場合 - /** * Implements hook_preprocess_node(). */ function mytheme_preprocess_node(&$variables) { $node = $variables['node']; if ($node->bundle() === 'article') { $variables['attributes']->addClass('my-special-article'); } }
- 主な用途 - 特定の条件に応じたCSSクラスの追加
- 日付フォーマットの変更や、複数のフィールドを組み合わせた新しい変数の作成
- 複雑なレンダー配列の構築
 
 
4. CSS/JSはライブラリとして管理する
CSSやJavaScriptは、*.libraries.yml ファイルで「ライブラリ」として定義し、必要な場所でだけ読み込むのが基本です。これにより、アセットの依存関係が明確になり、ページの表示パフォーマンスが向上します。
mytheme.libraries.yml を定義する
テーマのルートに mytheme.libraries.yml を作成し、アセットをグループ化します。
# mytheme.libraries.yml
global-styling:
  css:
    theme:
      css/style.css: {}
slick-carousel:
  js:
    js/slick.min.js: {}
  css:
    component:
      css/slick.css: {}
  dependencies:
    - core/drupal
    - core/drupalSettingsライブラリをアタッチ(読み込み)する
- サイト全体で読み込む - mytheme.info.ymlに記述します。- libraries: - mytheme/global-styling
 
- 特定のTwigテンプレートでのみ読み込む - テンプレートファイルの上部で - attach_libraryを使います。- {{ attach_library('mytheme/slick-carousel') }} <div class="carousel">...</div>
 
カスタムモジュール開発Tips
5. コードジェネレータで雛形を高速作成する
モジュール、プラグイン、サービスなど、Drupalの各コンポーネントには規約(ディレクトリ構造、ファイル名、クラスの継承など)があります。これらを手作業で作るのは非効率で、間違いの元です。
Drush を活用する
このコマンドラインツールには、対話形式で必要なファイルやコードの雛形を自動生成する機能があります。
- モジュールの雛形作成: drush generate module
- ブロックプラグインの作成: drush generate plugin-block
- コントローラの作成: drush generate controller
これにより、*.info.yml ファイルや適切なディレクトリ構造、クラスファイルなどが自動で生成され、開発者はロジックの実装に集中できます。
6. Xdebugによるステップ実行でデバッグを極める
Develモジュールの dpm() や kint() は手軽ですが、複雑な処理の流れや変数の変化を追うには限界があります。本格的な開発には、XdebugとIDE(PhpStormなど)を連携させたステップ実行が不可欠です。
Xdebugのメリット
- コードの任意の行で処理を一時停止(ブレークポイント)できる。
- その時点での全ての変数の状態を詳細に確認できる。
- 処理を1行ずつ進めながら(ステップ実行)、コードがどのように実行されるかを正確に追跡できる。
特に、複雑なフックの連鎖やサービスの依存関係、予期せぬ挙動の原因を解明する際に絶大な効果を発揮します。
7. サービスと依存性注入(DI)を徹底活用する
Drupal 8以降、主要な機能は再利用可能な「サービス」として提供されています。自作のモジュールでも、ロジックはサービスとして実装し、必要な場所で呼び出すのがモダンな開発スタイルです。
グローバルな静的呼び出し \Drupal::service() を避ける
この方法は手軽ですが、「サービスロケータ」というアンチパターンであり、コードの依存関係が不明確になるためユニットテストが困難になります。
依存性注入(Dependency Injection)を使う
クラスが必要とするサービスは、コンストラクタなどを通じて外部から注入(inject)します。これにより、クラスは特定のサービス実装に依存しなくなり(疎結合)、再利用性やテスト性が格段に向上します。
実装方法1: サービスやコントローラの場合(最も一般的な方法)
カスタムサービスやコントローラなど、多くのクラスでは *.services.yml ファイルで依存性を定義します。
- my_module.services.ymlでサービスを定義し、依存性を指定する- モジュールのルートに - my_module.services.ymlを作成します。- argumentsに- @を付けて、利用したいサービスのIDを記述します。- # my_module.services.yml services: # 自身のサービスの定義 my_module.my_logic: class: Drupal\my_module\MyLogicService # このサービスが依存する(必要とする)サービスをここに列挙する arguments: ['@entity_type.manager', '@logger.factory']
 
- クラスのコンストラクタでサービスを受け取る - YAMLファイルで指定したサービスが、クラスの - __construct()メソッドの引数として渡されます。これを受け取り、クラスのプロパティに保存して利用します。- // src/MyLogicService.php namespace Drupal\my_module; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Logger\LoggerChannelFactoryInterface; /** * 独自のロジックを提供するサービスクラス */ class MyLogicService { protected $entityTypeManager; protected $logger; /** * コンストラクタ * services.ymlのargumentsで指定したサービスがここに注入される */ public function __construct(EntityTypeManagerInterface $entity_type_manager, LoggerChannelFactoryInterface $logger_factory) { $this->entityTypeManager = $entity_type_manager; $this->logger = $logger_factory->get('my_module'); // ログチャンネルを取得 } /** * 注入したサービスを利用するメソッド */ public function someMethod() { // エンティティマネージャーを使ってノードストレージを取得 $node_storage = $this->entityTypeManager->getStorage('node'); // ロガーを使ってメッセージを記録 $this->logger->info('MyLogicService was called.'); } }
 
実装方法2: プラグイン(ブロック、エンティティなど)の場合
ブロックプラグインなど、Drupal自身がインスタンス化を管理するクラスでは、ContainerFactoryPluginInterface を実装してDIを行います。
- create()メソッドでサービスを取得する- create()静的メソッド内でコンテナから必要なサービスを取得します。
 
- __construct()メソッドでサービスをインスタンスに渡す- 取得したサービスを - __construct()メソッドに渡し、クラスのプロパティに保存します。- // src/Plugin/Block/MyBlock.php // (前略) use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\Core\Session\AccountInterface; class MyBlock extends BlockBase implements ContainerFactoryPluginInterface { protected $currentUser; // (中略) /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static( $configuration, $plugin_id, $plugin_definition, $container->get('current_user') // コンテナからcurrent_userサービスを取得 ); } /** * {@inheritdoc} */ public function __construct(array $configuration, $plugin_id, $plugin_definition, AccountInterface $current_user) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->currentUser = $current_user; // サービスをプロパティに格納 } /** * {@inheritdoc} */ public function build() { // 注入したサービスを利用 $name = $this->currentUser->getAccountName(); return [ '#markup' => $this->t('Hello @name!', ['@name' => $name]), ]; } }
 
8. フック(Hook)とイベント(Event)を正しく使い分ける
Drupalには、システムの動作に介入するための仕組みとして、伝統的な「フック」とモダンな「イベント」の2種類があります。
- フック (Hook)
- 手続き的な関数として実装します (my_module_form_alter()など)。
- 主に既存の配列データ(フォーム、レンダー配列など)を変更・追加する _alter系の処理で今も広く使われます。
- 特定の処理の「後」に何かを追加する、という場面で有効です。
- 将来的にはイベントへ置き換えが進んでいくと言われています。
 
- 手続き的な関数として実装します (
- イベント (Event)
- Symfony由来のオブジェクト指向な仕組みです。
- 特定の出来事(例: ユーザーログイン、設定保存)が発生したことをシステム全体に通知(ディスパッチ)し、それに関心のある「サブスクライバー」が処理を実行します。
- 処理の順序(優先度)を明確に定義できるため、複数のモジュールが連携する場合に堅牢です。
 
使い分けの目安
- フォームの変更など、伝統的にフックが使われてきた場面では フック を使います。
- 状態の変化(エンティティの保存、ユーザーのログインなど)をトリガーに、独立したロジックを実行したい場合は イベント を使います。
フック関数の見つけ方
対象モジュールのディレクトリ配下の *.api.php ファイルに、利用可能な関数とその仕様が記載されています。
イベント(ディスパッチ)の見つけ方
- コアの主要イベントクラスを確認する
- \Drupal\Core\KernelEvents- リクエスト〜レスポンスのライフサイクルに関するイベント。ページの表示、リダイレクト、例外処理などに介入したい場合は、まずここを見ます。
- KernelEvents::REQUEST: リクエスト受信時
- KernelEvents::RESPONSE: レスポンス送信前
- KernelEvents::EXCEPTION: 例外発生時 など
 
 
- リクエスト〜レスポンスのライフサイクルに関するイベント。ページの表示、リダイレクト、例外処理などに介入したい場合は、まずここを見ます。
- \Drupal\Core\Config\ConfigEvents- 設定の保存・削除に関するイベント。管理画面で何かが保存されたタイミングで処理したい場合に利用します。
- ConfigEvents::SAVE: 設定保存時
- ConfigEvents::DELETE: 設定削除時 など
 
 
- 設定の保存・削除に関するイベント。管理画面で何かが保存されたタイミングで処理したい場合に利用します。
 
- モジュール内の *Event.phpファイルを探す (モジュール特有のイベント)- コアの主要イベント以外で、特定のモジュールが独自のイベントをディスパッチする場合、そのイベント自身がクラスとして定義されているのが一般的です。
- イベントクラスは、モジュールの src/Event/ディレクトリに置かれ、*Event.phpというファイル名になっています。
 
- コード内で ->dispatch()を検索する- イベントが「実際に発火する場所」を直接見つける方法です。
 
9. 設定管理(Configuration Management)を理解する
モジュールが管理画面から設定できる項目を持つ場合、その設定値は設定管理システムを通じて保存・デプロイされるべきです。これにより、開発環境から本番環境へ設定を安全に反映できます。
設定スキーマを定義する
config/schema/*.schema.yml ファイルで、モジュールが保存する設定のデータ型や構造を定義します。これにより、設定値の妥当性検証や多言語対応が可能になります。
デフォルト設定を提供する
config/install/*.settings.yml ファイルに、モジュール有効化時のデフォルト設定を記述します。
設定フォームを作成する
FormBase ではなく ConfigFormBase を継承したクラスで設定フォームを作成します。これにより、フォームの送信と設定の保存がスムーズに連携されます。
10. Composerでプロジェクト全体の依存関係を管理する
Drupalプロジェクトの基盤はComposerによって管理されるのが標準です。Drupalコア、コントリビュートモジュール/テーマ、外部PHPライブラリなど、プロジェクトを構成する全ての要素を一元管理します。
なぜComposerが重要か?
- 再現性
- composer.jsonと- composer.lockファイルがあれば、誰でも、いつでも同じバージョンのコンポーネントで環境を再現できます。
 
- 依存関係の解決
- モジュールAがライブラリXを必要とし、モジュールBがライブラリYを必要とする場合、Composerが自動でそれらをダウンロードし、適切に配置してくれます。
 
- セキュリティアップデート
- composer outdated "drupal/*"で更新が必要なパッケージを確認し、- composer updateコマンドで安全にアップデートを適用できます。
 
基本的な使い方
- プロジェクトの開始
- composer create-project drupal/recommended-project my_site
 
- モジュールの追加
- composer require drupal/module_name
 
- パッチの適用
- cweagans/composer-patches
- プラグインを使い、コントリビュートモジュールのバグ修正などを宣言的に管理できます。
 
