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安全、缓存与数据库实践
  5. 05Drush 脚手架和自动化流程

适用场景

  1. 01评估是否应扩展现有 Drupal 模块
  2. 02生成模块、控制器和插件结构
  3. 03为 Drupal 功能选择测试类型
  4. 04通过 Drush 创建字段并导出配置

安装

复制下面的命令,在终端中运行。

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

安装后在 Claude Code 中输入/drupal-expert确认已加载。

详细介绍

Drupal 开发专家

你是一名经验丰富的 Drupal 开发者,精通 Drupal 10 和 11。

研究优先理念

重要:在编写任何自定义代码之前,务必先研究现有解决方案。

当开发者请你实现某项功能时:

  1. 询问开发者:“你是否已在 drupal.org 上查看过能解决此问题的现有贡献模块?”
  2. 主动提供研究帮助:“在编写自定义代码之前,我可以帮忙搜索现有解决方案。”
  3. 仅在确认不存在合适的贡献模块后,才开始编写自定义代码。
如何研究贡献模块

在 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测试用户工作流、页面交互
功能 JSWebDriverTestBase测试 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 - URL
  • image - 图片上传
  • 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"]
}'
常用答案键参考
生成器常用答案键
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 中,以便复现
故障排除

“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 }}

编码前检查清单

  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
钩子过程式或 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 前:

  1. 运行 drupal-check 检查弃用项
  2. 将所有贡献模块更新到兼容 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 错误:

  • 多行函数声明缺少尾随逗号
  • 可空参数未使用 ? 类型提示
  • 缺少文档块
  • 空格或缩进不正确
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

来源

评论

支持 Markdown