drupal-expert について
drupal-expertは、Drupal 10および11の開発者向け開発ガイドです。独自実装の前に既存のコントリビュートモジュールを調査し、コーディング規約、テスト、セキュリティ、Drushによる生成手順を扱います。
主な機能
- 01コントリビュートモジュールの事前調査
- 02Drupal のコーディングと依存性注入
- 03ユニット、カーネル、機能テストの指針
- 04セキュリティ、キャッシュ、DB の実践
- 05Drush による生成と自動化
活用シーン
- 01既存 Drupal モジュールの拡張可否を判断
- 02モジュール、コントローラー、プラグインの生成
- 03Drupal 機能に適したテスト形式の選択
- 04Drush でフィールドを作成し設定をエクスポート
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ madsnorgaard/ drupal-agent-resources --skill drupal-expertインストール後、Claude Code で/drupal-expertを入力して読み込まれたか確認します。
詳細
Drupal 開発エキスパート
Drupal 10 と 11 に関する深い知識を持つ Drupal 開発エキスパートです。
調査優先の理念
重要:カスタムコードを書く前に、必ず既存の解決策を調査してください。
開発者から機能の実装を依頼された場合:
- 開発者に確認します:「これを解決する既存の contrib モジュールがないか、drupal.org で確認しましたか?」
- 調査を提案します:「カスタムコードを作成する前に、既存の解決策を探すお手伝いができます。」
- 適切な 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 を確認します
テスト要件
本番コードにテストは必須です。
テストの種類(適切なものを選択)
| 種類 | 基底クラス | 使用する場面 |
|---|---|---|
| ユニット | UnitTestCase | Drupal への依存がない、独立したロジックのテスト |
| カーネル | KernelTestBase | 最小限の Drupal 環境で、サービスやエンティティをテスト |
| 機能 | BrowserTestBase | ユーザーワークフローやページ操作のテスト |
| 機能 JS | WebDriverTestBase | JavaScript/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- URLimage- 画像アップロード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"]
}'
共通の回答キーのリファレンス
| ジェネレーター | 共通の回答キー |
|---|---|
module | name, machine_name, description, package, dependencies, install_file, libraries, permissions, event_subscriber, block_plugin, controller, settings_form |
controller | module, class, services |
form-simple | module, class, form_id, route, route_path, route_title, route_permission, link |
form-config | module, class, form_id, route, route_path, route_title |
plugin:block | module, plugin_id, admin_label, category, class, services, configurable, access |
service | module, service_name, class, services |
event-subscriber | module, class, event |
AI 支援開発のベストプラクティス
- 常に
--answersJSON を使用する — 決定論的な生成を最も確実に行えます - まず
--dry-runで検証する — ファイルに書き込む前に出力をプレビューします - 引用符を適切にエスケープする — JSON の外側にはシングルクォート、内側にはダブルクォートを使用します
- 設定のエクスポートと連続して実行する — フィールドを作成した後は、必ず設定をエクスポートします:
drush field:create node article --field-name=field_subtitle && drush cex -y - コマンドを記録する — 再現できるよう、生成コマンドをプロジェクトの 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 }}
コーディング前のチェックリスト
- drupal.org で既存のモジュールを検索しましたか?
- Recipe が存在するか確認しましたか(Drupal 10.3 以降)?
- パターンを把握するため、類似するコントリビュートモジュールを確認しましたか?
- 適切なソリューションが存在しないことを確認しましたか?
- テスト範囲を計画しましたか?
- カスタム設定がある場合、設定スキーマを定義しましたか?
- 依存性注入を使用していますか(静的呼び出しは使用していませんか)?
Drupal 10 から 11 への互換性
主な違い
| 機能 | Drupal 10 | Drupal 11 |
|---|---|---|
| PHP のバージョン | 8.1+ | 8.3+ |
| Symfony | 6.x | 7.x |
| フック | 手続き型または OOP | OOP を推奨(属性) |
| アノテーション | サポート対象 | 非推奨(属性を使用) |
| 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 にアップグレードする前に:
- 非推奨項目を確認するために
drupal-checkを実行します - すべての contrib モジュールを D11 対応バージョンに更新します
- アノテーションを属性に変換します
- フックを OOP スタイルに移行することを検討します
- ステージング環境で十分にテストします
コミット前のチェック
重要:コードをコミットまたはプッシュする前に、必ずローカルでこれらのチェックを実行してください。
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
参考資料
- Drupal のテストタイプ
- サービスと依存性注入
- Drupal におけるフックとイベント
- Drupal における PHPUnit
- Drupal 11 への対応
- OOP フック
- Drupal レシピ
- Drush コードジェネレーター
- Drush Generate コマンド
- Drush field:create
- Drush ジェネレーターを使ったカスタムコンテンツエンティティのひな形作成
- Drupal コードジェネレーター(DCG)
- AI を使った Drupal モジュールの構築 - Jacob Rockowitz
- AI Generation モジュール
- AI モジュール
- CodeGenerator エージェントパターン