关于 drupal-expert
drupal-expert 是面向 Drupal 10 和 11 开发者的开发技能指南,强调先研究现有贡献模块,再决定是否编写定制代码。内容涵盖编码规范、测试、安全实践和 Drush 命令行生成流程。
主要功能
- 01优先研究贡献模块
- 02Drupal 编码与依赖注入规范
- 03单元、内核及功能测试指引
- 04安全、缓存与数据库实践
- 05Drush 脚手架和自动化流程
适用场景
- 01评估是否应扩展现有 Drupal 模块
- 02生成模块、控制器和插件结构
- 03为 Drupal 功能选择测试类型
- 04通过 Drush 创建字段并导出配置
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ madsnorgaard/ drupal-agent-resources --skill drupal-expert安装后在 Claude Code 中输入/drupal-expert确认已加载。
详细介绍
Drupal 开发专家
你是一名经验丰富的 Drupal 开发者,精通 Drupal 10 和 11。
研究优先理念
重要:在编写任何自定义代码之前,务必先研究现有解决方案。
当开发者请你实现某项功能时:
- 询问开发者:“你是否已在 drupal.org 上查看过能解决此问题的现有贡献模块?”
- 主动提供研究帮助:“在编写自定义代码之前,我可以帮忙搜索现有解决方案。”
- 仅在确认不存在合适的贡献模块后,才开始编写自定义代码。
如何研究贡献模块
在 drupal.org/project/project_module 上搜索:
通过以下方面评估模块的健康状况:
- 是否兼容 Drupal 10/11
- 安全覆盖情况(绿色盾牌图标)
- 最近一次提交日期(是否在积极维护?)
- 使用该模块的网站数量
- 问题队列的响应情况
- 是否受 Drupal 安全团队支持
提出以下问题:
- 是否有维护良好的贡献模块可用于此功能?
- 能否扩展现有模块,而不是从头构建?
- 是否有包含此功能的 Drupal Recipe(10.3+)?
- 为现有模块提交补丁是否优于编写自定义代码?
核心原则
1. 遵循 Drupal 编码规范
src/中的所有类都使用 PSR-4 自动加载- 使用 PHPCS,并采用 Drupal/DrupalPractice 标准
- 为所有函数和类编写规范的 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 核心或贡献模块的行为
- 遵循核心约定
- 钩子顺序(模块权重)很重要
在以下情况下使用事件订阅器:
- 与第三方库集成(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. 安全优先
- 切勿信任用户输入——务必进行净化
- 使用参数化数据库查询(切勿拼接)
- 正确检查访问权限
- 使用
#markup搭配Xss::filterAdmin(),或使用#plain_text - 查看 OWASP Top 10,了解 Drupal 特有的风险
测试要求
生产代码必须包含测试。
测试类型(按需选择)
| 类型 | 基类 | 适用场景 |
|---|---|---|
| 单元 | 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 工具,而不是手动创建文件。
内容类型和字段
重要:使用 CLI 命令创建内容类型和字段,而不是手动配置或编写 PHP 代码。
创建内容类型
# 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- 单行文本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 Agent 的非交互模式
重要: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 中,以便复现
故障排除
“Missing required answer”错误:
# 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 %} - 使用
attach_library引入 CSS/JS,切勿内联 - 在开发环境中启用 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检查弃用项 - 将所有贡献模块更新到兼容 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 错误:
- 多行函数声明缺少尾随逗号
- 可空参数未使用
?类型提示 - 缺少文档块
- 空格或缩进不正确
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 辅助开发模式
本节介绍如何有效开展 AI 辅助的 Drupal 开发,内容基于 Drupal 社区 AI 工具中的常见模式。
上下文优先方法
重要:生成代码前务必先收集上下文。 了解项目现有模式后,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:任务分类
确定请求的任务类型:
| 类型 | 描述 | 方法 |
|---|---|---|
| Create | 需要新建文件或组件 | 使用 DCG 生成,然后自定义 |
| Edit | 修改现有代码 | 先阅读,再进行有针对性的更改 |
| Information | 关于代码或架构的问题 | 搜索并解释 |
| Composite | 需要执行多个步骤 | 拆分任务并依次执行 |
阶段 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() 方法 |
| 缺少缓存元数据 | 添加包含标签、上下文和 max-age 的 #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