mcpField
D
drupal-expert

drupal-expert

github.com/madsnorgaard開発491.2k 回インストール6 週間前に更新
リポジトリ

drupal-expert について

drupal-expertは、Drupal 10および11の開発者向け開発ガイドです。独自実装の前に既存のコントリビュートモジュールを調査し、コーディング規約、テスト、セキュリティ、Drushによる生成手順を扱います。

主な機能

  1. 01コントリビュートモジュールの事前調査
  2. 02Drupal のコーディングと依存性注入
  3. 03ユニット、カーネル、機能テストの指針
  4. 04セキュリティ、キャッシュ、DB の実践
  5. 05Drush による生成と自動化

活用シーン

  1. 01既存 Drupal モジュールの拡張可否を判断
  2. 02モジュール、コントローラー、プラグインの生成
  3. 03Drupal 機能に適したテスト形式の選択
  4. 04Drush でフィールドを作成し設定をエクスポート

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/madsnorgaard/drupal-agent-resources --skill drupal-expert

インストール後、Claude Code で/drupal-expertを入力して読み込まれたか確認します。

詳細

Drupal 開発エキスパート

Drupal 10 と 11 に関する深い知識を持つ Drupal 開発エキスパートです。

調査優先の理念

重要:カスタムコードを書く前に、必ず既存の解決策を調査してください。

開発者から機能の実装を依頼された場合:

  1. 開発者に確認します:「これを解決する既存の contrib モジュールがないか、drupal.org で確認しましたか?」
  2. 調査を提案します:「カスタムコードを作成する前に、既存の解決策を探すお手伝いができます。」
  3. 適切な contrib モジュールが存在しないことを確認してから、カスタムコードの作成に進みます。
Contrib モジュールの調査方法

drupal.org/project/project_module で検索します。

次の点を確認して、モジュールの健全性を評価します:

  • Drupal 10/11 との互換性
  • セキュリティ対応(緑色の盾アイコン)
  • 最終コミット日(積極的にメンテナンスされていますか?)
  • 利用しているサイトの数
  • イシューキューへの対応状況
  • Drupal のセキュリティチームによるサポートの有無

次の点を確認します:

  • これに対応する、十分にメンテナンスされた contrib モジュールはありますか?
  • 一から作成するのではなく、既存のモジュールを拡張できますか?
  • この機能をまとめた Drupal Recipe(10.3+)はありますか?
  • カスタムコードより、既存のモジュールへのパッチのほうが適切ですか?

基本原則

1. Drupal コーディング規約に従う
  • src/ 内のすべてのクラスで PSR-4 オートロードを使用します
  • Drupal/DrupalPractice 標準で PHPCS を使用します
  • すべての関数とクラスに適切な DocBlock コメントを記述します
  • ユーザーに表示する文字列には、適切なプレースホルダーを指定して t() を使用します:
    • @variable - サニタイズ済みテキスト
    • %variable - サニタイズされ、強調表示されたテキスト
    • :variable - URL(サニタイズ済み)
2. 依存性注入を使用する
  • クラス内では \Drupal::service() を絶対に使用せず、コンストラクター経由で注入します
  • *.services.yml でサービスを定義します
  • フォームとコントローラーでは ContainerInjectionInterface を使用します
  • プラグインでは ContainerFactoryPluginInterface を使用します
// WRONG - static service calls
class MyController {
  public function content() {
    $user = \Drupal::currentUser();
  }
}

// CORRECT - dependency injection
class MyController implements ContainerInjectionInterface {
  public function __construct(
    protected AccountProxyInterface $currentUser,
  ) {}

  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('current_user'),
    );
  }
}
3. フックとイベントサブスクライバー

現代の Drupal では、どちらも有効です。状況に応じて選択してください。

次の場合は OOP フックを使用します:

  • Drupal コアや contrib の動作を変更する
  • コアの規約に従う
  • フックの順序(モジュールのウェイト)が重要

次の場合はイベントサブスクライバーを使用します:

  • サードパーティライブラリ(PSR-14)と連携する
  • 複数のカスタマイズをまとめた機能を構築する
  • Commerce など、イベントを多用するモジュールを扱う
// OOP Hook (Drupal 11+)
#[Hook('form_alter')]
public function formAlter(&$form, FormStateInterface $form_state, $form_id): void {
  // ...
}

// Event Subscriber
public static function getSubscribedEvents() {
  return [
    KernelEvents::REQUEST => ['onRequest', 100],
  ];
}
4. セキュリティを最優先する
  • ユーザー入力は決して信頼せず、必ずサニタイズします
  • パラメーター化されたデータベースクエリを使用します(連結は絶対に行いません)
  • アクセス権限を適切に確認します
  • Xss::filterAdmin() を使用した #markup、または #plain_text を使用します
  • Drupal 固有のリスクについて、OWASP Top 10 を確認します

テスト要件

本番コードにテストは必須です。

テストの種類(適切なものを選択)
種類基底クラス使用する場面
ユニットUnitTestCaseDrupal への依存がない、独立したロジックのテスト
カーネルKernelTestBase最小限の Drupal 環境で、サービスやエンティティをテスト
機能BrowserTestBaseユーザーワークフローやページ操作のテスト
機能 JSWebDriverTestBaseJavaScript/AJAX 機能のテスト
テストファイルの配置
my_module/
└── tests/
    └── src/
        ├── Unit/           # Fast, isolated tests
        ├── Kernel/         # Service/entity tests
        └── Functional/     # Full browser tests
各種類のテストを作成する場面
  • ユニットテスト:純粋な PHP ロジック、ユーティリティ関数、データ変換
  • カーネルテスト:サービス、データベースクエリ、エンティティ操作、フック
  • 機能テスト:フォーム、コントローラー、アクセス制御、ユーザーフロー
  • 機能 JS テスト:動的フォーム、AJAX、JavaScript の動作
テストの実行
# Run specific test
./vendor/bin/phpunit modules/custom/my_module/tests/src/Unit/MyTest.php

# Run all module tests
./vendor/bin/phpunit modules/custom/my_module

# Run with coverage
./vendor/bin/phpunit --coverage-html coverage modules/custom/my_module

モジュール構成

my_module/
├── my_module.info.yml
├── my_module.module           # Hooks only (keep thin)
├── my_module.services.yml     # Service definitions
├── my_module.routing.yml      # Routes
├── my_module.permissions.yml  # Permissions
├── my_module.libraries.yml    # CSS/JS libraries
├── config/
│   ├── install/               # Default config
│   ├── optional/              # Optional config (dependencies)
│   └── schema/                # Config schema (REQUIRED for custom config)
├── src/
│   ├── Controller/
│   ├── Form/
│   ├── Plugin/
│   │   ├── Block/
│   │   └── Field/
│   ├── Service/
│   ├── EventSubscriber/
│   └── Hook/                  # OOP hooks (Drupal 11+)
├── templates/                 # Twig templates
└── tests/
    └── src/
        ├── Unit/
        ├── Kernel/
        └── Functional/

よくあるパターン

サービス定義
services:
  my_module.my_service:
    class: Drupal\my_module\Service\MyService
    arguments: ['@entity_type.manager', '@current_user', '@logger.factory']
権限付きルート
my_module.page:
  path: '/my-page'
  defaults:
    _controller: '\Drupal\my_module\Controller\MyController::content'
    _title: 'My Page'
  requirements:
    _permission: 'access content'
プラグイン(ブロックの例)
#[Block(
  id: "my_block",
  admin_label: new TranslatableMarkup("My Block"),
)]
class MyBlock extends BlockBase implements ContainerFactoryPluginInterface {
  // Always use ContainerFactoryPluginInterface for DI in plugins
}
設定スキーマ(必須!)
# config/schema/my_module.schema.yml
my_module.settings:
  type: config_object
  label: 'My Module settings'
  mapping:
    enabled:
      type: boolean
      label: 'Enabled'
    limit:
      type: integer
      label: 'Limit'

データベースクエリ

常にデータベース抽象化レイヤーを使用します:

// CORRECT - parameterized query
$query = $this->database->select('node', 'n');
$query->fields('n', ['nid', 'title']);
$query->condition('n.type', $type);
$query->range(0, 10);
$results = $query->execute();

// NEVER do this - SQL injection risk
$result = $this->database->query("SELECT * FROM node WHERE type = '$type'");

キャッシュメタデータ

レンダー配列には必ずキャッシュメタデータを追加します:

$build['content'] = [
  '#markup' => $content,
  '#cache' => [
    'tags' => ['node_list', 'user:' . $uid],
    'contexts' => ['user.permissions', 'url.query_args'],
    'max-age' => 3600,
  ],
];
キャッシュタグの規約
  • node:123 - 特定のノード
  • node_list - すべてのノードリスト
  • user:456 - 特定のユーザー
  • config:my_module.settings - 設定

CLI ファーストの開発ワークフロー

カスタムコードを書く前に、Drush ジェネレーターを使用してボイラープレートコードを生成してください。

Drush のコード生成機能は Drupal のベストプラクティスとコーディング規約に従っており、エラーを減らし、開発を加速します。標準的な Drupal 構造を作成する場合は、手動でファイルを作成するよりも、常に CLI ツールを優先してください。

コンテンツタイプとフィールド

重要:手動の設定や PHP コードではなく、CLI コマンドを使用してコンテンツタイプとフィールドを作成してください。

コンテンツタイプを作成する
# Interactive mode - Drush prompts for all details
drush generate content-entity

# Create via PHP eval (for scripts/automation)
drush php:eval "
\$type = \Drupal\node\Entity\NodeType::create([
  'type' => 'article',
  'name' => 'Article',
  'description' => 'Articles with images and tags',
  'new_revision' => TRUE,
  'display_submitted' => TRUE,
  'preview_mode' => 1,
]);
\$type->save();
echo 'Content type created.';
"
フィールドを作成する
# Interactive mode (recommended for first-time use)
drush field:create

# Non-interactive mode with all parameters
drush field:create node article \
  --field-name=field_subtitle \
  --field-label="Subtitle" \
  --field-type=string \
  --field-widget=string_textfield \
  --is-required=0 \
  --cardinality=1

# Create a reference field
drush field:create node article \
  --field-name=field_tags \
  --field-label="Tags" \
  --field-type=entity_reference \
  --field-widget=entity_reference_autocomplete \
  --cardinality=-1 \
  --target-type=taxonomy_term

# Create an image field
drush field:create node article \
  --field-name=field_image \
  --field-label="Image" \
  --field-type=image \
  --field-widget=image_image \
  --is-required=0 \
  --cardinality=1

一般的なフィールドタイプ:

  • string - プレーンテキスト
  • string_long - 長文テキスト(テキストエリア)
  • text_long - 書式付きテキスト
  • text_with_summary - 要約付き本文フィールド
  • integer - 整数
  • decimal - 小数
  • boolean - チェックボックス
  • datetime - 日付/時刻
  • email - メールアドレス
  • link - URL
  • image - 画像アップロード
  • file - ファイルアップロード
  • entity_reference - 他のエンティティへの参照
  • list_string - 選択リスト
  • telephone - 電話番号

一般的なフィールドウィジェット:

  • string_textfield - 1 行のテキスト
  • string_textarea - 複数行のテキスト
  • text_textarea - 書式付きテキストエリア
  • text_textarea_with_summary - 要約付き本文
  • number - 数値入力
  • checkbox - 単一のチェックボックス
  • options_select - ドロップダウン選択
  • options_buttons - ラジオボタン/チェックボックス
  • datetime_default - 日付ピッカー
  • email_default - メール入力
  • link_default - URL 入力
  • image_image - 画像アップロード
  • file_generic - ファイルアップロード
  • entity_reference_autocomplete - オートコンプリート参照
フィールドを管理する
# List all fields on a content type
drush field:info node article

# List available field types
drush field:types

# List available field widgets
drush field:widgets

# List available field formatters
drush field:formatters

# Delete a field
drush field:delete node.article.field_subtitle
モジュールのひな形を生成する
# Generate a complete module
drush generate module
# Prompts for: module name, description, package, dependencies

# Generate a controller
drush generate controller
# Prompts for: module, class name, route path, services to inject

# Generate a simple form
drush generate form-simple
# Creates form with submit/validation, route, and menu link

# Generate a config form
drush generate form-config
# Creates settings form with automatic config storage

# Generate a block plugin
drush generate plugin:block
# Creates block plugin with dependency injection support

# Generate a service
drush generate service
# Creates service class and services.yml entry

# Generate a hook implementation
drush generate hook
# Creates hook in .module file or OOP hook class (D11)

# Generate an event subscriber
drush generate event-subscriber
# Creates subscriber class and services.yml entry
エンティティタイプを生成する
# Generate a custom content entity
drush generate entity:content
# Creates entity class, storage, access control, views integration

# Generate a config entity
drush generate entity:configuration
# Creates config entity with list builder and forms
一般的なパターンを生成する
# Generate a plugin (various types)
drush generate plugin:field:formatter
drush generate plugin:field:widget
drush generate plugin:field:type
drush generate plugin:block
drush generate plugin:condition
drush generate plugin:filter

# Generate a Drush command
drush generate drush:command-file

# Generate a test
drush generate test:unit
drush generate test:kernel
drush generate test:browser
テストコンテンツを作成する

手動入力ではなく、テストデータには Devel Generate を使用してください。

# Generate 50 nodes
drush devel-generate:content 50 --bundles=article,page --kill

# Generate taxonomy terms
drush devel-generate:terms 100 tags --kill

# Generate users
drush devel-generate:users 20

# Generate media entities
drush devel-generate:media 30 --bundles=image,document
ワークフローのベストプラクティス

1. 必ずジェネレーターから始めてください:

# Create module structure first
drush generate module

# Then generate specific components
drush generate controller
drush generate form-config
drush generate service

2. フィールドの追加にはすべて field:create を使用してください:

# Never manually create field config files
# Use drush field:create instead
drush field:create node article --field-name=field_subtitle

3. CLI による変更後は設定をエクスポートしてください:

# After creating fields/content types via CLI
drush config:export -y

4. ひな形の生成内容を README に記録してください:

## Regenerating Module Structure

This module was scaffolded with:
- drush generate module
- drush generate controller
- drush field:create node article --field-name=field_custom
よくある間違いを避ける

手動で作成しないでください:

  • コンテンツタイプの設定ファイル(node.type.*.yml)
  • フィールド設定ファイル(field.field.*.yml、field.storage.*.yml)
  • ビューモードの設定(core.entity_view_display.*.yml)
  • フォームモードの設定(core.entity_form_display.*.yml)

CLI コマンドを使用してください:

  • コードのひな形生成には drush generate
  • フィールドの作成には drush field:create
  • コンテンツタイプの作成には drush php:eval
  • 変更の保存には drush config:export
DDEV/Docker との統合
# When using DDEV
ddev drush generate module
ddev drush field:create node article

# When using Docker Compose
docker compose exec php drush generate module
docker compose exec php drush field:create node article

# When using DDEV with custom commands
ddev exec drush generate controller
自動化と AI エージェントのための非対話モード

重要:Drush ジェネレーターはデフォルトで対話形式です。自動化、CI/CD パイプライン、AI 支援開発でプロンプトを回避するには、次の方法を使用してください。

方法 1:JSON を使用した --answers(推奨)

すべての回答を JSON オブジェクトとして渡します。完全な自動化には、これが最も信頼性の高い方法です。

# Generate a complete module non-interactively
drush generate module --answers='{
  "name": "My Custom Module",
  "machine_name": "my_custom_module",
  "description": "A custom module for specific functionality",
  "package": "Custom",
  "dependencies": "",
  "install_file": "no",
  "libraries": "no",
  "permissions": "no",
  "event_subscriber": "no",
  "block_plugin": "no",
  "controller": "no",
  "settings_form": "no"
}'

# Generate a controller non-interactively
drush generate controller --answers='{
  "module": "my_custom_module",
  "class": "MyController",
  "services": ["entity_type.manager", "current_user"]
}'

# Generate a form non-interactively
drush generate form-simple --answers='{
  "module": "my_custom_module",
  "class": "ContactForm",
  "form_id": "my_custom_module_contact",
  "route": "yes",
  "route_path": "/contact-us",
  "route_title": "Contact Us",
  "route_permission": "access content",
  "link": "no"
}'
方法 2:--answer フラグを順番に指定する

より単純なジェネレーターでは、複数の --answer(または -a)フラグを順番に使用します。

# Answers are consumed in order of the prompts
drush generate controller --answer="my_module" --answer="PageController" --answer=""

# Short form
drush gen controller -a my_module -a PageController -a ""
方法 3:必要な回答を確認する

詳細出力を有効にして --dry-run を使用すると、すべてのプロンプトとその期待値を確認できます。

# Preview generation and see all prompts
drush generate module -vvv --dry-run

# This shows you exactly what answers are needed
# Then re-run with --answers JSON
方法 4:デフォルト値を自動承認する

すべてのデフォルト値を受け入れるには、-y または --yes を使用します(デフォルト値で問題ない場合に便利です)。

# Accept all defaults
drush generate module -y

# Combine with some answers to override specific defaults
drush generate module --answer="My Module" -y
非対話形式の完全な例

ブロックプラグインを生成する:

drush generate plugin:block --answers='{
  "module": "my_custom_module",
  "plugin_id": "my_custom_block",
  "admin_label": "My Custom Block",
  "category": "Custom",
  "class": "MyCustomBlock",
  "services": ["entity_type.manager"],
  "configurable": "no",
  "access": "no"
}'

サービスを生成する:

drush generate service --answers='{
  "module": "my_custom_module",
  "service_name": "my_custom_module.helper",
  "class": "HelperService",
  "services": ["database", "logger.factory"]
}'

イベントサブスクライバーを生成する:

drush generate event-subscriber --answers='{
  "module": "my_custom_module",
  "class": "MyEventSubscriber",
  "event": "kernel.request"
}'

Drush コマンドを生成する:

drush generate drush:command-file --answers='{
  "module": "my_custom_module",
  "class": "MyCommands",
  "services": ["entity_type.manager"]
}'
共通の回答キーのリファレンス
ジェネレーター共通の回答キー
modulename, machine_name, description, package, dependencies, install_file, libraries, permissions, event_subscriber, block_plugin, controller, settings_form
controllermodule, class, services
form-simplemodule, class, form_id, route, route_path, route_title, route_permission, link
form-configmodule, class, form_id, route, route_path, route_title
plugin:blockmodule, plugin_id, admin_label, category, class, services, configurable, access
servicemodule, service_name, class, services
event-subscribermodule, class, event
AI 支援開発のベストプラクティス
  1. 常に --answers JSON を使用する — 決定論的な生成を最も確実に行えます
  2. まず --dry-run で検証する — ファイルに書き込む前に出力をプレビューします
  3. 引用符を適切にエスケープする — JSON の外側にはシングルクォート、内側にはダブルクォートを使用します
  4. 設定のエクスポートと連続して実行する — フィールドを作成した後は、必ず設定をエクスポートします:
    drush field:create node article --field-name=field_subtitle && drush cex -y
    
  5. コマンドを記録する — 再現できるよう、生成コマンドをプロジェクトの README に保存します
トラブルシューティング

「必須の回答がありません」エラー:

# Use -vvv to see which answer is missing
drush generate module -vvv --answers='{"name": "Test"}'

JSON の解析エラー:

# Ensure proper escaping - use single quotes outside, double inside
drush generate module --answers='{"name": "Test Module"}'  # Correct
drush generate module --answers="{"name": "Test Module"}"  # Wrong - shell interprets braces

対話型プロンプトが引き続き表示される場合:

# Some prompts may not have defaults - provide all required answers
# Use --dry-run first to identify all prompts
drush generate module -vvv --dry-run 2>&1 | grep -E "^\s*\?"

必須の Drush コマンド

drush cr                    # Clear cache
drush cex -y                # Export config
drush cim -y                # Import config
drush updb -y               # Run updates
drush en module_name        # Enable module
drush pmu module_name       # Uninstall module
drush ws --severity=error   # Watch logs
drush php:eval "code"       # Run PHP

# Code generation (see CLI-First Development above)
drush generate              # List all generators
drush gen module            # Generate module (gen is alias)
drush field:create          # Create field (fc is alias)
drush entity:create         # Create entity content

翻訳

ユーザーに表示される文字列はすべて、Drupal の翻訳 API を使用する必要があります。文字列をそのまま出力しないでください。

コンテキスト正しい記述
PHP(サービス/コントローラー/フォーム)$this->t('Hello @name', ['@name' => $name])
PHP(静的コンテキスト)t('Hello @name', ['@name' => $name])
プラグイン属性new TranslatableMarkup('My Block')
Twig{% trans %}Hello {{ name }}{% endtrans %}
プレースホルダーの種類
  • @variable — エスケープされたテキスト
  • %variable — エスケープされ、強調表示される(<em> で囲まれる)
  • :variable — URL(エスケープされる)
翻訳サービスの注入
public function __construct(
  protected TranslationInterface $translation,
) {}

// Then use:
$this->translation->translate('Some string');
// Or the shorthand via StringTranslationTrait:
$this->t('Some string');

完全な DI を使わずに $this->t() を必要とするクラスには、use StringTranslationTrait; を追加します。

してはいけないこと
// Wrong — raw string
return ['#markup' => 'Submit form'];

// Wrong — hardcoded non-English
return ['#markup' => 'Indsend formular'];

// Correct
return ['#markup' => $this->t('Submit form')];

Twig のベストプラクティス

  • 変数は自動的にエスケープされるため、|escape は不要です
  • 翻訳可能な文字列には {% trans %} を使用します
  • CSS/JS には attach_library を使用し、インラインでは記述しません
  • 開発時には Twig のデバッグを有効にします
  • デバッグには {{ dump(variable) }} を使用します
{# Correct - uses translation #}
{% trans %}Hello {{ name }}{% endtrans %}

{# Attach library #}
{{ attach_library('my_module/my-library') }}

{# Safe markup (already sanitized) #}
{{ content|raw }}

コーディング前のチェックリスト

  1. drupal.org で既存のモジュールを検索しましたか?
  2. Recipe が存在するか確認しましたか(Drupal 10.3 以降)?
  3. パターンを把握するため、類似するコントリビュートモジュールを確認しましたか?
  4. 適切なソリューションが存在しないことを確認しましたか?
  5. テスト範囲を計画しましたか?
  6. カスタム設定がある場合、設定スキーマを定義しましたか?
  7. 依存性注入を使用していますか(静的呼び出しは使用していませんか)?

Drupal 10 から 11 への互換性

主な違い
機能Drupal 10Drupal 11
PHP のバージョン8.1+8.3+
Symfony6.x7.x
フック手続き型または OOPOOP を推奨(属性)
アノテーションサポート対象非推奨(属性を使用)
jQuery同梱オプション
互換性のあるコードの作成(D10.3+ および D11)

プラグインには PHP 属性を使用します(D10.2+ で動作し、D11 では必須のスタイルです):

#[Block(
  id: 'my_block',
  admin_label: new TranslatableMarkup('My Block'),
)]
class MyBlock extends BlockBase {}

OOP フックを使用します(D10.3+):

// Modern OOP hooks (D10.3+)
// src/Hook/MyModuleHooks.php
namespace Drupal\my_module\Hook;

use Drupal\Core\Hook\Attribute\Hook;

final class MyModuleHooks {

  #[Hook('form_alter')]
  public function formAlter(&$form, FormStateInterface $form_state, $form_id): void {
    // ...
  }

  #[Hook('node_presave')]
  public function nodePresave(NodeInterface $node): void {
    // ...
  }

}

フッククラスを services.yml に登録します:

services:
  Drupal\my_module\Hook\MyModuleHooks:
    autowire: true

手続き型フックも引き続き動作しますが、後方互換性のため、.module ファイル内だけで使用してください。

避けるべき非推奨 API
// DEPRECATED - don't use
drupal_set_message()           // Use messenger service
format_date()                  // Use date.formatter service
entity_load()                  // Use entity_type.manager
db_select()                    // Use database service
drupal_render()                // Use renderer service
\Drupal::l()                   // Use Link::fromTextAndUrl()
非推奨項目の確認
# Run deprecation checks
./vendor/bin/drupal-check modules/custom/

# Or with PHPStan
./vendor/bin/phpstan analyze modules/custom/ --level=5
info.yml の互換性
# Support both D10 and D11
core_version_requirement: ^10.3 || ^11

# D11 only
core_version_requirement: ^11
Recipes(D10.3+)

Drupal Recipes は、再利用可能な設定パッケージを提供します:

# Apply a recipe
php core/scripts/drupal recipe core/recipes/standard

# Community recipes
composer require drupal/recipe_name
php core/scripts/drupal recipe recipes/contrib/recipe_name

Recipes とモジュールの使い分け:

  • Recipes:設定のみ、サイト構築、コンテンツタイプ、ビュー
  • モジュール:カスタム PHP コード、新機能、API
互換性のテスト
# Test against both versions in CI
jobs:
  test-d10:
    env:
      DRUPAL_CORE: ^10.3
  test-d11:
    env:
      DRUPAL_CORE: ^11
移行計画

D10 → D11 にアップグレードする前に:

  1. 非推奨項目を確認するために drupal-check を実行します
  2. すべての contrib モジュールを D11 対応バージョンに更新します
  3. アノテーションを属性に変換します
  4. フックを OOP スタイルに移行することを検討します
  5. ステージング環境で十分にテストします

コミット前のチェック

重要:コードをコミットまたはプッシュする前に、必ずローカルでこれらのチェックを実行してください。

CI パイプラインの失敗は見栄えが悪く、時間の無駄になります。まずローカルで問題を見つけてください。

必須:コーディング規約(PHPCS)
# Check for coding standard violations
./vendor/bin/phpcs -p --colors modules/custom/

# Auto-fix what can be fixed
./vendor/bin/phpcbf modules/custom/

# Check specific file
./vendor/bin/phpcs path/to/MyClass.php

注意すべき一般的な PHPCS エラー:

  • 複数行の関数宣言で末尾のカンマがない
  • ? 型ヒントのない nullable パラメーター
  • DocBlock がない
  • スペースやインデントが不適切
DDEV のショートカット
# Run inside DDEV
ddev exec ./vendor/bin/phpcs -p modules/custom/
ddev exec ./vendor/bin/phpcbf modules/custom/
推奨:コミット前の完全チェックリスト
# 1. Coding standards
./vendor/bin/phpcs -p modules/custom/

# 2. Static analysis (if configured)
./vendor/bin/phpstan analyze modules/custom/

# 3. Deprecation checks
./vendor/bin/drupal-check modules/custom/

# 4. Run tests
./vendor/bin/phpunit modules/custom/my_module/tests/
Git のコミット前フック(任意)

.git/hooks/pre-commit を作成します:

#!/bin/bash
./vendor/bin/phpcs --standard=Drupal,DrupalPractice modules/custom/ || exit 1

実行可能にします:chmod +x .git/hooks/pre-commit

Drupal 規約を使用した PHPCS のインストール
composer require --dev drupal/coder
./vendor/bin/phpcs --config-set installed_paths vendor/drupal/coder/coder_sniffer

AI 支援開発のパターン

このセクションでは、Drupal コミュニティの AI ツールのパターンに基づき、AI 支援による効果的な Drupal 開発の方法論について説明します。

コンテキスト優先のアプローチ

重要:コードを生成する前に、必ずコンテキストを収集してください。 プロジェクトに既存のパターンがあると理解している場合、AI は大幅に優れた出力を生成します。

ステップ 1:類似ファイルを見つける

新しいコードを生成する前に、コードベース内で類似した実装を見つけます:

# Find similar services
find modules/custom -name "*.services.yml" -exec grep -l "entity_type.manager" {} \;

# Find similar forms
find modules/custom -name "*Form.php" -type f

# Find similar controllers
find modules/custom -path "*/Controller/*.php" -type f

# Find similar plugins
find modules/custom -path "*/Plugin/Block/*.php" -type f

これが重要な理由: 既存のコードパターンを AI に見せると、次のようにできます:

  • 命名規則を合わせる
  • 同じ依存性注入パターンを使用する
  • プロジェクトのアーキテクチャスタイルに従う
  • 既存コードと一貫性のある形で統合する
ステップ 2:プロジェクトのパターンを把握する

コード生成を依頼する前に、次を特定します:

1. **Naming patterns**
   - Service naming: `my_module.helper` vs `my_module_helper`
   - Class naming: `MyModuleHelper` vs `HelperService`
   - File organization: flat vs nested directories

2. **Dependency patterns**
   - Which services are commonly injected?
   - How is logging handled?
   - How are entities loaded?

3. **Configuration patterns**
   - Where is config stored?
   - How are settings forms structured?
   - What schema patterns are used?
ステップ 3:依頼にコンテキストを含める

明示的なコンテキストを含めて依頼を構成します:

**Bad request:**
"Create a service that processes nodes"

**Good request:**
"Create a service that processes article nodes.

Context:
- See existing service pattern in modules/custom/my_module/src/ArticleManager.php
- Inject entity_type.manager and logger.factory (like other services in this module)
- Follow the naming pattern: my_module.article_processor
- Add config schema following modules/custom/my_module/config/schema/*.yml pattern"
Drupal タスク向けの構造化プロンプト

複雑な生成タスクには階層型プロンプトを使用してください。 Jacob Rockowitz が文書化したこのアプローチでは、安定して優れた結果が得られます。

プロンプトテンプレートの構造
## Task
[One sentence describing what you want to create]

## Module Context
- Module name: my_custom_module
- Module path: modules/custom/my_custom_module
- Drupal version: 10.3+ / 11
- PHP version: 8.2+

## Requirements
- [Specific requirement 1]
- [Specific requirement 2]
- [Specific requirement 3]

## Code Standards
- Use constructor property promotion
- Use PHP 8 attributes for plugins
- Inject all dependencies (no \Drupal::service())
- Include proper docblocks
- Follow Drupal coding standards

## Similar Files (for reference)
- [Path to similar implementation]
- [Path to similar implementation]

## Expected Output
- [File 1]: [Description]
- [File 2]: [Description]
例:ブロックプラグインの作成
## Task
Create a block that displays recent articles with a configurable limit.

## Module Context
- Module name: my_articles
- Module path: modules/custom/my_articles
- Drupal version: 10.3+
- PHP version: 8.2+

## Requirements
- Display recent article nodes (type: article)
- Configurable number of items (default: 5)
- Show title, date, and teaser
- Cache per page with article list tag
- Access: view published content permission

## Code Standards
- Use #[Block] attribute (not annotation)
- Inject entity_type.manager and date.formatter
- Use ContainerFactoryPluginInterface
- Include config schema

## Similar Files
- modules/custom/my_articles/src/Plugin/Block/FeaturedArticleBlock.php

## Expected Output
- src/Plugin/Block/RecentArticlesBlock.php
- config/schema/my_articles.schema.yml (update)
内側から外側へのアプローチ

Drupal AI CodeGenerator のパターンに基づくこの方法論では、複雑なタスクを決定論的なステップに分解します:

フェーズ 1:タスクの分類

依頼されたタスクの種類を判断します:

種類説明アプローチ
作成新しいファイル/コンポーネントが必要DCG で生成してからカスタマイズ
編集既存のコードを変更まず読み込み、その後、対象を絞って変更
情報コード/アーキテクチャに関する質問検索して説明
複合複数のステップが必要分解して順番に実行
フェーズ 2:実行可能性の確認

生成する前に、次を確認します:

✓ Required dependencies available?
✓ Target directory exists and is writable?
✓ No conflicting files/classes?
✓ All referenced services/classes exist?
✓ Compatible with Drupal version?
フェーズ 3:まずスキャフォールディングを行う

DCG を使用してスキャフォールディングを行い、その後カスタマイズします。 これにより、Drupal のベストプラクティスに沿った実装になります:

# 1. Generate base structure
drush generate plugin:block --answers='{
  "module": "my_module",
  "plugin_id": "recent_articles",
  "admin_label": "Recent Articles",
  "class": "RecentArticlesBlock"
}'

# 2. Review generated code
cat modules/custom/my_module/src/Plugin/Block/RecentArticlesBlock.php

# 3. Customize with specific requirements
# (AI edits the generated file to add business logic)
フェーズ 4:テストを自動生成する

コードと一緒に必ずテストを生成してください:

# Generate kernel test for the new functionality
drush generate test:kernel --answers='{
  "module": "my_module",
  "class": "RecentArticlesBlockTest"
}'
反復型開発ワークフロー

AI が生成したコードは 80% の完成度を想定してください。 改善のサイクルを計画します。

現実的なワークフロー
┌─────────────────────────────────────────────────────────────┐
│  1. GATHER CONTEXT                                          │
│     - Find similar files                                    │
│     - Understand patterns                                   │
│     - Document requirements                                 │
├─────────────────────────────────────────────────────────────┤
│  2. GENERATE (AI does ~80%)                                 │
│     - Use structured prompt                                 │
│     - Scaffold with DCG                                     │
│     - Generate business logic                               │
├─────────────────────────────────────────────────────────────┤
│  3. REVIEW & REFINE (Human does ~20%)                       │
│     - Check security (XSS, SQL injection, access)           │
│     - Verify DI compliance                                  │
│     - Validate config schema                                │
│     - Run PHPCS and fix issues                              │
├─────────────────────────────────────────────────────────────┤
│  4. TEST                                                    │
│     - Run generated tests                                   │
│     - Add edge case tests                                   │
│     - Manual smoke testing                                  │
├─────────────────────────────────────────────────────────────┤
│  5. ITERATE (if needed)                                     │
│     - Fix failing tests                                     │
│     - Address review feedback                               │
│     - Refine based on testing                               │
└─────────────────────────────────────────────────────────────┘
よくある改善タスク
問題解決策
PHPCS エラー自動修正には phpcbf を実行し、複雑な問題は手動で修正します
DI がないコンストラクターに追加し、create() メソッドを更新します
キャッシュメタデータがないタグ、コンテキスト、最大有効期間を指定して #cache を追加します
アクセスチェックがない権限チェックまたはアクセスハンドラーを追加します
設定スキーマがない設定構造に合ったスキーマファイルを作成します
文字列がハードコードされている適切なプレースホルダーを使用して $this->t() でラップします
Drupal AI モジュールとの統合

AI モジュールを利用できる場合は、迅速なプロトタイピングに drush aigen を活用します:

# Check if AI Generation is available
drush pm:list --filter=ai_generation

# Generate a complete content type
drush aigen "Create a content type called 'Event' with fields: title, date (datetime), location (text), description (formatted text), image (media reference)"

# Generate a view
drush aigen "Create a view showing upcoming events sorted by date with a calendar display"

# Generate a custom module
drush aigen "Create a module that sends email notifications when new events are created"

重要: AI が生成したコードは必ずレビューしてください。AI Generation モジュールは実験的なもので、開発専用です。

一般的なタスク向けのプロンプトパターン
フィールドを持つコンテンツタイプ
Create a content type for [purpose].

Content type:
- Machine name: [machine_name]
- Label: [Human Label]
- Description: [Description]
- Publishing options: published by default, create new revision
- Display author and date: no

Fields:
1. [field_name] ([field_type]): [description] - [required/optional]
2. [field_name] ([field_type]): [description] - [required/optional]

After creation, export config with: drush cex -y
カスタムサービス
Create a service for [purpose].

Service:
- Name: [module].service_name
- Class: Drupal\[module]\[ServiceClass]
- Inject: [service1], [service2]

Methods:
- methodName(params): return_type - [description]
- methodName(params): return_type - [description]

Include:
- Interface definition
- services.yml entry
- PHPDoc with @param and @return
イベントサブスクライバー
Create an event subscriber for [purpose].

Subscriber:
- Class: Drupal\[module]\EventSubscriber\[ClassName]
- Event: [event.name]
- Priority: [0-100]

Behavior:
- [Describe what should happen when event fires]

Include:
- services.yml entry with tags
- Proper type hints
AI が生成したコードのデバッグ

生成されたコードが動作しない場合:

# 1. Check for PHP syntax errors
php -l modules/custom/my_module/src/MyClass.php

# 2. Clear all caches
drush cr

# 3. Check service container
drush devel:services | grep my_module

# 4. Check for missing use statements
grep -n "^use" modules/custom/my_module/src/MyClass.php

# 5. Verify class is autoloaded
drush php:eval "class_exists('Drupal\my_module\MyClass') ? print 'Found' : print 'Not found';"

# 6. Check logs
drush ws --severity=error --count=20

参考資料

コメント