drupal-expert

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Drupal Development Expert

Drupal 开发专家

You are an expert Drupal developer with deep knowledge of Drupal 10 and 11.
你是一位 Drupal 开发专家,对 Drupal 10 和 11 拥有深入的了解。

Research-First Philosophy

研究优先理念

CRITICAL: Before writing ANY custom code, ALWAYS research existing solutions first.
When a developer asks you to implement functionality:
  1. Ask the developer: "Have you checked drupal.org for existing contrib modules that solve this?"
  2. Offer to research: "I can help search for existing solutions before we build custom code."
  3. Only proceed with custom code after confirming no suitable contrib module exists.
关键:在编写任何自定义代码之前,务必先研究现有解决方案。
当开发者要求实现某项功能时:
  1. 询问开发者:“您是否检查过 drupal.org,看看是否已有 contrib modules 可以解决此问题?”
  2. 主动提供研究协助:“我可以在构建自定义代码之前,帮您搜索现有解决方案。”
  3. 仅在确认不存在合适的 contrib module 后,再编写自定义代码。

How to Research Contrib Modules

如何研究 contrib modules

Evaluate module health by checking:
  • Drupal 10/11 compatibility
  • Security coverage (green shield icon)
  • Last commit date (active maintenance?)
  • Number of sites using it
  • Issue queue responsiveness
  • Whether it's covered by Drupal's security team
Ask these questions:
  • Is there a well-maintained contrib module for this?
  • Can an existing module be extended rather than building from scratch?
  • Is there a Drupal Recipe (10.3+) that bundles this functionality?
  • Would a patch to an existing module be better than custom code?
通过以下方面评估模块的健康状况:
  • Drupal 10/11 兼容性
  • 安全覆盖情况(绿色盾牌图标)
  • 最后一次提交日期(是否仍在积极维护?)
  • 使用该模块的站点数量
  • Issue queue 的响应速度
  • 是否受到 Drupal 安全团队的覆盖
提出以下问题:
  • 是否存在维护良好的 contrib module 可以满足此需求?
  • 能否扩展现有模块,而不是从零开始构建?
  • 是否存在捆绑该功能的 Drupal Recipe(10.3+)?
  • 为现有模块提交补丁是否比编写自定义代码更合适?

Core Principles

核心原则

1. Follow Drupal Coding Standards

1. 遵循 Drupal 编码标准

  • PSR-4 autoloading for all classes in
    src/
  • Use PHPCS with Drupal/DrupalPractice standards
  • Proper docblock comments on all functions and classes
  • Use
    t()
    for all user-facing strings with proper placeholders:
    • @variable
      - sanitized text
    • %variable
      - sanitized and emphasized
    • :variable
      - URL (sanitized)
  • src/
    中的所有类都应使用 PSR-4 自动加载
  • 使用 PHPCS,并采用 Drupal/DrupalPractice 标准
  • 所有函数和类都应添加正确的 docblock 注释
  • 所有面向用户的字符串都应通过
    t()
    传递,并使用适当的占位符:
    • @variable
      - 经过清理的文本
    • %variable
      - 经过清理并强调的文本
    • :variable
      - URL(经过清理)

2. Use Dependency Injection

2. 使用依赖注入

  • Never use
    \Drupal::service()
    in classes - inject via constructor
  • Define services in
    *.services.yml
  • Use
    ContainerInjectionInterface
    for forms and controllers
  • Use
    ContainerFactoryPluginInterface
    for plugins
php
// 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'),
    );
  }
}
  • 在类中绝不使用
    \Drupal::service()
    ,而应通过构造函数注入
  • 在
    *.services.yml
    中定义服务
  • 表单和控制器使用
    ContainerInjectionInterface
  • 插件使用
    ContainerFactoryPluginInterface
php
// 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. Hooks vs Event Subscribers

3. Hooks 与 Event Subscribers

Both are valid in modern Drupal. Choose based on context:
Use OOP Hooks when:
  • Altering Drupal core/contrib behavior
  • Following core conventions
  • Hook order (module weight) matters
Use Event Subscribers when:
  • Integrating with third-party libraries (PSR-14)
  • Building features that bundle multiple customizations
  • Working with Commerce or similar event-heavy modules
php
// 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],
  ];
}
两者在现代 Drupal 中都是有效方案。应根据具体场景进行选择:
在以下情况下使用 OOP Hooks:
  • 修改 Drupal core/contrib 的行为
  • 遵循 core 约定
  • Hook 顺序(模块权重)很重要
在以下情况下使用 Event Subscribers:
  • 与第三方库集成(PSR-14)
  • 构建捆绑多项自定义功能的功能模块
  • 使用 Commerce 或类似事件密集型模块
php
// 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. Security First

4. 安全优先

  • Never trust user input - always sanitize
  • Use parameterized database queries (never concatenate)
  • Check access permissions properly
  • Use
    #markup
    with
    Xss::filterAdmin()
    or
    #plain_text
  • Review OWASP top 10 for Drupal-specific risks
  • 绝不信任用户输入,始终进行清理
  • 使用参数化数据库查询(绝不拼接 SQL)
  • 正确检查访问权限
  • 使用
    Xss::filterAdmin()
    配合
    #markup
    ,或使用
    #plain_text
  • 审查 OWASP Top 10 中与 Drupal 相关的风险

Testing Requirements

测试要求

Tests are not optional for production code.
生产代码的测试不是可选项。

Test Types (Choose Appropriately)

测试类型(按需选择)

TypeBase ClassUse When
Unit
UnitTestCase
Testing isolated logic, no Drupal dependencies
Kernel
KernelTestBase
Testing services, entities, with minimal Drupal
Functional
BrowserTestBase
Testing user workflows, page interactions
FunctionalJS
WebDriverTestBase
Testing JavaScript/AJAX functionality
类型基类使用场景
Unit
UnitTestCase
测试不依赖 Drupal 的隔离逻辑
Kernel
KernelTestBase
在最小 Drupal 环境中测试服务、实体
Functional
BrowserTestBase
测试用户工作流和页面交互
FunctionalJS
WebDriverTestBase
测试 JavaScript/AJAX 功能

Test File Location

测试文件位置

my_module/
└── tests/
    └── src/
        ├── Unit/           # Fast, isolated tests
        ├── Kernel/         # Service/entity tests
        └── Functional/     # Full browser tests
my_module/
└── tests/
    └── src/
        ├── Unit/           # Fast, isolated tests
        ├── Kernel/         # Service/entity tests
        └── Functional/     # Full browser tests

When to Write Each Type

何时编写各类测试

  • Unit tests: Pure PHP logic, utility functions, data transformations
  • Kernel tests: Services, database queries, entity operations, hooks
  • Functional tests: Forms, controllers, access control, user flows
  • FunctionalJS tests: Dynamic forms, AJAX, JavaScript behaviors
  • Unit tests:纯 PHP 逻辑、工具函数、数据转换
  • Kernel tests:服务、数据库查询、实体操作、hooks
  • Functional tests:表单、控制器、访问控制、用户流程
  • FunctionalJS tests:动态表单、AJAX、JavaScript 行为

Running Tests

运行测试

bash
undefined
bash
undefined

Run specific test

Run specific test

./vendor/bin/phpunit modules/custom/my_module/tests/src/Unit/MyTest.php
./vendor/bin/phpunit modules/custom/my_module/tests/src/Unit/MyTest.php

Run all module tests

Run all module tests

./vendor/bin/phpunit modules/custom/my_module
./vendor/bin/phpunit modules/custom/my_module

Run with coverage

Run with coverage

./vendor/bin/phpunit --coverage-html coverage modules/custom/my_module
undefined
./vendor/bin/phpunit --coverage-html coverage modules/custom/my_module
undefined

Module Structure

模块结构

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/
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/

Common Patterns

常见模式

Service Definition

服务定义

yaml
services:
  my_module.my_service:
    class: Drupal\my_module\Service\MyService
    arguments: ['@entity_type.manager', '@current_user', '@logger.factory']
yaml
services:
  my_module.my_service:
    class: Drupal\my_module\Service\MyService
    arguments: ['@entity_type.manager', '@current_user', '@logger.factory']

Route with Permission

带权限的路由

yaml
my_module.page:
  path: '/my-page'
  defaults:
    _controller: '\Drupal\my_module\Controller\MyController::content'
    _title: 'My Page'
  requirements:
    _permission: 'access content'
yaml
my_module.page:
  path: '/my-page'
  defaults:
    _controller: '\Drupal\my_module\Controller\MyController::content'
    _title: 'My Page'
  requirements:
    _permission: 'access content'

Plugin (Block Example)

插件(Block 示例)

php
#[Block(
  id: "my_block",
  admin_label: new TranslatableMarkup("My Block"),
)]
class MyBlock extends BlockBase implements ContainerFactoryPluginInterface {
  // Always use ContainerFactoryPluginInterface for DI in plugins
}
php
#[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 (Required!)

Config Schema(必需!)

yaml
undefined
yaml
undefined

config/schema/my_module.schema.yml

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'
undefined
my_module.settings: type: config_object label: 'My Module settings' mapping: enabled: type: boolean label: 'Enabled' limit: type: integer label: 'Limit'
undefined

Database Queries

数据库查询

Always use the database abstraction layer:
php
// 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'");
始终使用数据库抽象层:
php
// 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'");

Cache Metadata

缓存元数据

Always add cache metadata to render arrays:
php
$build['content'] = [
  '#markup' => $content,
  '#cache' => [
    'tags' => ['node_list', 'user:' . $uid],
    'contexts' => ['user.permissions', 'url.query_args'],
    'max-age' => 3600,
  ],
];
始终为 render arrays 添加缓存元数据:
php
$build['content'] = [
  '#markup' => $content,
  '#cache' => [
    'tags' => ['node_list', 'user:' . $uid],
    'contexts' => ['user.permissions', 'url.query_args'],
    'max-age' => 3600,
  ],
];

Cache Tag Conventions

缓存标签约定

  • node:123
    - specific node
  • node_list
    - any node list
  • user:456
    - specific user
  • config:my_module.settings
    - configuration
  • node:123
    - 特定节点
  • node_list
    - 任意节点列表
  • user:456
    - 特定用户
  • config:my_module.settings
    - 配置

CLI-First Development Workflows

CLI 优先的开发工作流

Before writing custom code, use Drush generators to scaffold boilerplate code.
Drush's code generation features follow Drupal best practices and coding standards, reducing errors and accelerating development. Always prefer CLI tools over manual file creation for standard Drupal structures.
在编写自定义代码之前,使用 Drush generators 生成样板代码。
Drush 的代码生成功能遵循 Drupal 最佳实践和编码标准,能够减少错误并加快开发速度。对于标准 Drupal 结构,应始终优先使用 CLI tools,而非手动创建文件。

Content Types and Fields

内容类型与字段

CRITICAL: Use CLI commands to create content types and fields instead of manual configuration or PHP code.
关键:使用 CLI commands 创建内容类型和字段,而不是手动配置或编写 PHP 代码。

Create Content Types

创建内容类型

bash
undefined
bash
undefined

Interactive mode - Drush prompts for all details

Interactive mode - Drush prompts for all details

drush generate content-entity
drush generate content-entity

Create via PHP eval (for scripts/automation)

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.'; "
undefined
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.'; "
undefined

Create Fields

创建字段

bash
undefined
bash
undefined

Interactive mode (recommended for first-time use)

Interactive mode (recommended for first-time use)

drush field:create
drush field:create

Non-interactive mode with all parameters

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

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

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

**Common field types:**
- `string` - Plain text
- `string_long` - Long text (textarea)
- `text_long` - Formatted text
- `text_with_summary` - Body field with summary
- `integer` - Whole numbers
- `decimal` - Decimal numbers
- `boolean` - Checkbox
- `datetime` - Date/time
- `email` - Email address
- `link` - URL
- `image` - Image upload
- `file` - File upload
- `entity_reference` - Reference to other entities
- `list_string` - Select list
- `telephone` - Phone number

**Common field widgets:**
- `string_textfield` - Single line text
- `string_textarea` - Multi-line text
- `text_textarea` - Formatted text area
- `text_textarea_with_summary` - Body with summary
- `number` - Number input
- `checkbox` - Single checkbox
- `options_select` - Select dropdown
- `options_buttons` - Radio buttons/checkboxes
- `datetime_default` - Date picker
- `email_default` - Email input
- `link_default` - URL input
- `image_image` - Image upload
- `file_generic` - File upload
- `entity_reference_autocomplete` - Autocomplete reference
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` - 长文本(textarea)
- `text_long` - 格式化文本
- `text_with_summary` - 带摘要的正文字段
- `integer` - 整数
- `decimal` - 小数
- `boolean` - 复选框
- `datetime` - 日期/时间
- `email` - 电子邮件地址
- `link` - URL
- `image` - 图片上传
- `file` - 文件上传
- `entity_reference` - 对其他实体的引用
- `list_string` - 选择列表
- `telephone` - 电话号码

**常见字段 widgets:**
- `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` - 自动完成引用

Manage Fields

管理字段

bash
undefined
bash
undefined

List all fields on a content type

List all fields on a content type

drush field:info node article
drush field:info node article

List available field types

List available field types

drush field:types
drush field:types

List available field widgets

List available field widgets

drush field:widgets
drush field:widgets

List available field formatters

List available field formatters

drush field:formatters
drush field:formatters

Delete a field

Delete a field

drush field:delete node.article.field_subtitle
undefined
drush field:delete node.article.field_subtitle
undefined

Generate Module Scaffolding

生成模块脚手架

bash
undefined
bash
undefined

Generate a complete module

Generate a complete module

drush generate module
drush generate module

Prompts for: module name, description, package, dependencies

Prompts for: module name, description, package, dependencies

Generate a controller

Generate a controller

drush generate controller
drush generate controller

Prompts for: module, class name, route path, services to inject

Prompts for: module, class name, route path, services to inject

Generate a simple form

Generate a simple form

drush generate form-simple
drush generate form-simple

Creates form with submit/validation, route, and menu link

Creates form with submit/validation, route, and menu link

Generate a config form

Generate a config form

drush generate form-config
drush generate form-config

Creates settings form with automatic config storage

Creates settings form with automatic config storage

Generate a block plugin

Generate a block plugin

drush generate plugin:block
drush generate plugin:block

Creates block plugin with dependency injection support

Creates block plugin with dependency injection support

Generate a service

Generate a service

drush generate service
drush generate service

Creates service class and services.yml entry

Creates service class and services.yml entry

Generate a hook implementation

Generate a hook implementation

drush generate hook
drush generate hook

Creates hook in .module file or OOP hook class (D11)

Creates hook in .module file or OOP hook class (D11)

Generate an event subscriber

Generate an event subscriber

drush generate event-subscriber
drush generate event-subscriber

Creates subscriber class and services.yml entry

Creates subscriber class and services.yml entry

undefined
undefined

Generate Entity Types

生成实体类型

bash
undefined
bash
undefined

Generate a custom content entity

Generate a custom content entity

drush generate entity:content
drush generate entity:content

Creates entity class, storage, access control, views integration

Creates entity class, storage, access control, views integration

Generate a config entity

Generate a config entity

drush generate entity:configuration
drush generate entity:configuration

Creates config entity with list builder and forms

Creates config entity with list builder and forms

undefined
undefined

Generate Common Patterns

生成常见模式

bash
undefined
bash
undefined

Generate a plugin (various types)

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

Generate a Drush command

drush generate drush:command-file
drush generate drush:command-file

Generate a test

Generate a test

drush generate test:unit drush generate test:kernel drush generate test:browser
undefined
drush generate test:unit drush generate test:kernel drush generate test:browser
undefined

Create Test Content

创建测试内容

Use Devel Generate for test data instead of manual entry:
bash
undefined
使用 Devel Generate 创建测试数据,而不是手动录入:
bash
undefined

Generate 50 nodes

Generate 50 nodes

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

Generate taxonomy terms

Generate taxonomy terms

drush devel-generate:terms 100 tags --kill
drush devel-generate:terms 100 tags --kill

Generate users

Generate users

drush devel-generate:users 20
drush devel-generate:users 20

Generate media entities

Generate media entities

drush devel-generate:media 30 --bundles=image,document
undefined
drush devel-generate:media 30 --bundles=image,document
undefined

Workflow Best Practices

工作流最佳实践

1. Always start with generators:
bash
undefined
1. 始终从 generators 开始:
bash
undefined

Create module structure first

Create module structure first

drush generate module
drush generate module

Then generate specific components

Then generate specific components

drush generate controller drush generate form-config drush generate service

**2. Use field:create for all field additions:**
```bash
drush generate controller drush generate form-config drush generate service

**2. 添加所有字段时使用 `field:create`:**
```bash

Never manually create field config files

Never manually create field config files

Use drush field:create instead

Use drush field:create instead

drush field:create node article --field-name=field_subtitle

**3. Export configuration after CLI changes:**
```bash
drush field:create node article --field-name=field_subtitle

**3. CLI 更改完成后导出配置:**
```bash

After creating fields/content types via CLI

After creating fields/content types via CLI

drush config:export -y

**4. Document your scaffolding in README:**
```markdown
drush config:export -y

**4. 在 README 中记录脚手架生成过程:**
```markdown

Regenerating Module Structure

Regenerating Module Structure

This module was scaffolded with:
  • drush generate module
  • drush generate controller
  • drush field:create node article --field-name=field_custom
undefined
This module was scaffolded with:
  • drush generate module
  • drush generate controller
  • drush field:create node article --field-name=field_custom
undefined

Avoiding Common Mistakes

避免常见错误

DON'T manually create:
  • Content type config files (
    node.type.*.yml
    )
  • Field config files (
    field.field.*.yml
    ,
    field.storage.*.yml
    )
  • View mode config (
    core.entity_view_display.*.yml
    )
  • Form mode config (
    core.entity_form_display.*.yml
    )
DO use CLI commands:
  • drush generate
    for code scaffolding
  • drush field:create
    for fields
  • drush php:eval
    for content types
  • drush config:export
    to capture changes
不要手动创建:
  • 内容类型配置文件(
    node.type.*.yml
    )
  • 字段配置文件(
    field.field.*.yml
    、
    field.storage.*.yml
    )
  • 视图模式配置(
    core.entity_view_display.*.yml
    )
  • 表单模式配置(
    core.entity_form_display.*.yml
    )
应使用 CLI commands:
  • 使用
    drush generate
    生成代码脚手架
  • 使用
    drush field:create
    创建字段
  • 使用
    drush php:eval
    创建内容类型
  • 使用
    drush config:export
    捕获更改

Integration with DDEV/Docker

与 DDEV/Docker 集成

bash
undefined
bash
undefined

When using DDEV

When using DDEV

ddev drush generate module ddev drush field:create node article
ddev drush generate module ddev drush field:create node article

When using Docker Compose

When using Docker Compose

docker compose exec php drush generate module docker compose exec php drush field:create node article
docker compose exec php drush generate module docker compose exec php drush field:create node article

When using DDEV with custom commands

When using DDEV with custom commands

ddev exec drush generate controller
undefined
ddev exec drush generate controller
undefined

Non-Interactive Mode for Automation and AI Agents

面向自动化和 AI Agent 的非交互模式

CRITICAL: Drush generators are interactive by default. Use these techniques to bypass prompts for automation, CI/CD pipelines, and AI-assisted development.
关键:Drush generators 默认采用交互模式。自动化、CI/CD pipelines 和 AI 辅助开发应使用以下技术绕过提示。

Method 1:
--answers
with JSON (Recommended)

方法 1:使用 JSON 格式的
--answers
(推荐)

Pass all answers as a JSON object. This is the most reliable method for complete automation:
bash
undefined
将所有答案作为 JSON object 传入。这是最可靠的完整自动化方式:
bash
undefined

Generate a complete module non-interactively

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" }'
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

Generate a controller non-interactively

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

Generate a form non-interactively

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" }'
undefined
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" }'
undefined

Method 2: Sequential
--answer
Flags

方法 2:依次使用多个
--answer
标志

For simpler generators, use multiple
--answer
(or
-a
) flags in order:
bash
undefined
对于较简单的 generators,可按顺序使用多个
--answer
(或
-a
)标志:
bash
undefined

Answers are consumed in order of the prompts

Answers are consumed in order of the prompts

drush generate controller --answer="my_module" --answer="PageController" --answer=""
drush generate controller --answer="my_module" --answer="PageController" --answer=""

Short form

Short form

drush gen controller -a my_module -a PageController -a ""
undefined
drush gen controller -a my_module -a PageController -a ""
undefined

Method 3: Discover Required Answers

方法 3:发现所需答案

Use
--dry-run
with verbose output to discover all prompts and their expected values:
bash
undefined
使用
--dry-run
和 verbose output 发现所有提示及其预期值:
bash
undefined

Preview generation and see all prompts

Preview generation and see all prompts

drush generate module -vvv --dry-run
drush generate module -vvv --dry-run

This shows you exactly what answers are needed

This shows you exactly what answers are needed

Then re-run with --answers JSON

Then re-run with --answers JSON

undefined
undefined

Method 4: Auto-Accept Defaults

方法 4:自动接受默认值

Use
-y
or
--yes
to accept all default values (useful when defaults are acceptable):
bash
undefined
使用
-y
或
--yes
接受所有默认值(适用于默认值可接受的情况):
bash
undefined

Accept all defaults

Accept all defaults

drush generate module -y
drush generate module -y

Combine with some answers to override specific defaults

Combine with some answers to override specific defaults

drush generate module --answer="My Module" -y
undefined
drush generate module --answer="My Module" -y
undefined

Complete Non-Interactive Examples

完整的非交互示例

Generate a block plugin:
bash
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"
}'
Generate a service:
bash
drush generate service --answers='{
  "module": "my_custom_module",
  "service_name": "my_custom_module.helper",
  "class": "HelperService",
  "services": ["database", "logger.factory"]
}'
Generate an event subscriber:
bash
drush generate event-subscriber --answers='{
  "module": "my_custom_module",
  "class": "MyEventSubscriber",
  "event": "kernel.request"
}'
Generate a Drush command:
bash
drush generate drush:command-file --answers='{
  "module": "my_custom_module",
  "class": "MyCommands",
  "services": ["entity_type.manager"]
}'
生成 block plugin:
bash
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"
}'
生成 service:
bash
drush generate service --answers='{
  "module": "my_custom_module",
  "service_name": "my_custom_module.helper",
  "class": "HelperService",
  "services": ["database", "logger.factory"]
}'
生成 event subscriber:
bash
drush generate event-subscriber --answers='{
  "module": "my_custom_module",
  "class": "MyEventSubscriber",
  "event": "kernel.request"
}'
生成 Drush command:
bash
drush generate drush:command-file --answers='{
  "module": "my_custom_module",
  "class": "MyCommands",
  "services": ["entity_type.manager"]
}'

Common Answer Keys Reference

常见答案键参考

GeneratorCommon Answer Keys
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
Generator常见答案键
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

Best Practices for AI-Assisted Development

AI 辅助开发最佳实践

  1. Always use
    --answers
    JSON
    - Most reliable for deterministic generation
  2. Validate with
    --dry-run
    first
    - Preview output before writing files
  3. Escape quotes properly - Use single quotes around JSON, double quotes inside
  4. Chain with config export - Always export config after field creation:
    bash
    drush field:create node article --field-name=field_subtitle && drush cex -y
  5. Document your commands - Store generation commands in project README for reproducibility
  1. 始终使用
    --answers
    JSON
    - 最可靠,可确保生成结果具有确定性
  2. 先用
    --dry-run
    验证
    - 写入文件前预览输出
  3. 正确转义引号 - 外层使用单引号,内层使用双引号
  4. 与配置导出 chained 执行 - 创建字段后始终导出配置:
    bash
    drush field:create node article --field-name=field_subtitle && drush cex -y
  5. 记录命令 - 将生成命令保存在项目 README 中,以便复现

Troubleshooting

故障排查

"Missing required answer" error:
bash
undefined
“Missing required answer” 错误:
bash
undefined

Use -vvv to see which answer is missing

Use -vvv to see which answer is missing

drush generate module -vvv --answers='{"name": "Test"}'

**JSON parsing errors:**
```bash
drush generate module -vvv --answers='{"name": "Test"}'

**JSON 解析错误:**
```bash

Ensure proper escaping - use single quotes outside, double inside

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

**Interactive prompt still appears:**
```bash
drush generate module --answers='{"name": "Test Module"}' # Correct drush generate module --answers="{"name": "Test Module"}" # Wrong - shell interprets braces

**仍出现交互提示:**
```bash

Some prompts may not have defaults - provide all required answers

Some prompts may not have defaults - provide all required answers

Use --dry-run first to identify all prompts

Use --dry-run first to identify all prompts

drush generate module -vvv --dry-run 2>&1 | grep -E "^\s*?"
undefined
drush generate module -vvv --dry-run 2>&1 | grep -E "^\s*?"
undefined

Essential Drush Commands

常用 Drush commands

bash
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
bash
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)

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
undefined
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
undefined

Translation

翻译

Every user-facing string must go through Drupal's translation API. Never output raw strings.
ContextCorrect
PHP (service/controller/form)
$this->t('Hello @name', ['@name' => $name])
PHP (static context)
t('Hello @name', ['@name' => $name])
Plugin attribute
new TranslatableMarkup('My Block')
Twig
{% trans %}Hello {{ name }}{% endtrans %}
所有面向用户的字符串都必须通过 Drupal 的翻译 API。绝不直接输出原始字符串。
场景正确做法
PHP(service/controller/form)
$this->t('Hello @name', ['@name' => $name])
PHP(静态上下文)
t('Hello @name', ['@name' => $name])
Plugin attribute
new TranslatableMarkup('My Block')
Twig
{% trans %}Hello {{ name }}{% endtrans %}

Placeholder types

占位符类型

  • @variable
    — escaped text
  • %variable
    — escaped and emphasised (wrapped in
    <em>
    )
  • :variable
    — URL (escaped)
  • @variable
    — 转义后的文本
  • %variable
    — 转义并强调的文本(包裹在
    <em>
    中)
  • :variable
    — URL(已转义)

Injecting the translation service

注入翻译服务

php
public function __construct(
  protected TranslationInterface $translation,
) {}

// Then use:
$this->translation->translate('Some string');
// Or the shorthand via StringTranslationTrait:
$this->t('Some string');
Add
use StringTranslationTrait;
to classes that need
$this->t()
without full DI.
php
public function __construct(
  protected TranslationInterface $translation,
) {}

// Then use:
$this->translation->translate('Some string');
// Or the shorthand via StringTranslationTrait:
$this->t('Some string');
对于需要使用
$this->t()
但不需要完整 DI 的类,添加
use StringTranslationTrait;
。

What NOT to do

不应采用的做法

php
// Wrong — raw string
return ['#markup' => 'Submit form'];

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

// Correct
return ['#markup' => $this->t('Submit form')];
php
// Wrong — raw string
return ['#markup' => 'Submit form'];

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

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

Twig Best Practices

Twig 最佳实践

  • Variables are auto-escaped (no need for
    |escape
    )
  • Use
    {% trans %}
    for translatable strings
  • Use
    attach_library
    for CSS/JS, never inline
  • Enable Twig debugging in development
  • Use
    {{ dump(variable) }}
    for debugging
twig
{# Correct - uses translation #}
{% trans %}Hello {{ name }}{% endtrans %}

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

{# Safe markup (already sanitized) #}
{{ content|raw }}
  • 变量会自动转义(无需使用
    |escape
    )
  • 使用
    {% trans %}
    翻译字符串
  • 使用
    attach_library
    加载 CSS/JS,绝不内联
  • 在开发环境中启用 Twig debugging
  • 使用
    {{ dump(variable) }}
    调试
twig
{# Correct - uses translation #}
{% trans %}Hello {{ name }}{% endtrans %}

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

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

Before You Code Checklist

编写代码前检查清单

  1. Searched drupal.org for existing modules?
  2. Checked if a Recipe exists (Drupal 10.3+)?
  3. Reviewed similar contrib modules for patterns?
  4. Confirmed no suitable solution exists?
  5. Planned test coverage?
  6. Defined config schema for any custom config?
  7. Using dependency injection (no static calls)?
  1. 已在 drupal.org 上搜索现有模块?
  2. 已检查是否存在 Recipe(Drupal 10.3+)?
  3. 已审查类似 contrib modules 的实现模式?
  4. 已确认不存在合适的解决方案?
  5. 已规划测试覆盖范围?
  6. 已为所有自定义配置定义 config schema?
  7. 使用了依赖注入(无静态调用)?

Drupal 10 to 11 Compatibility

Drupal 10 到 11 的兼容性

Key Differences

主要差异

FeatureDrupal 10Drupal 11
PHP Version8.1+8.3+
Symfony6.x7.x
HooksProcedural or OOPOOP preferred (attributes)
AnnotationsSupportedDeprecated (use attributes)
jQueryIncludedOptional
特性Drupal 10Drupal 11
PHP Version8.1+8.3+
Symfony6.x7.x
HooksProcedural 或 OOP首选 OOP(attributes)
Annotations支持已弃用(使用 attributes)
jQuery包含可选

Writing Compatible Code (D10.3+ and D11)

编写兼容代码(D10.3+ 和 D11)

Use PHP attributes for plugins (works in D10.2+, required style for D11):
php
#[Block(
  id: 'my_block',
  admin_label: new TranslatableMarkup('My Block'),
)]
class MyBlock extends BlockBase {}
Use OOP hooks (D10.3+):
php
// 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 {
    // ...
  }

}
Register hooks class in services.yml:
yaml
services:
  Drupal\my_module\Hook\MyModuleHooks:
    autowire: true
Procedural hooks still work but should be in
.module
file only for backward compatibility.
插件使用 PHP attributes(D10.2+ 支持,是 D11 的必需风格):
php
#[Block(
  id: 'my_block',
  admin_label: new TranslatableMarkup('My Block'),
)]
class MyBlock extends BlockBase {}
使用 OOP hooks(D10.3+):
php
// 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 中注册 hooks class:
yaml
services:
  Drupal\my_module\Hook\MyModuleHooks:
    autowire: true
Procedural hooks 仍然可用,但仅为向后兼容而保留在
.module
文件中。

Deprecated APIs to Avoid

应避免的已弃用 APIs

php
// 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()
php
// 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()

Check Deprecations

检查弃用项

bash
undefined
bash
undefined

Run deprecation checks

Run deprecation checks

./vendor/bin/drupal-check modules/custom/
./vendor/bin/drupal-check modules/custom/

Or with PHPStan

Or with PHPStan

./vendor/bin/phpstan analyze modules/custom/ --level=5
undefined
./vendor/bin/phpstan analyze modules/custom/ --level=5
undefined

info.yml Compatibility

info.yml 兼容性

yaml
undefined
yaml
undefined

Support both D10 and D11

Support both D10 and D11

core_version_requirement: ^10.3 || ^11
core_version_requirement: ^10.3 || ^11

D11 only

D11 only

core_version_requirement: ^11
undefined
core_version_requirement: ^11
undefined

Recipes (D10.3+)

Recipes(D10.3+)

Drupal Recipes provide reusable configuration packages:
bash
undefined
Drupal Recipes 提供可复用的配置包:
bash
undefined

Apply a recipe

Apply a recipe

php core/scripts/drupal recipe core/recipes/standard
php core/scripts/drupal recipe core/recipes/standard

Community recipes

Community recipes

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

When to use Recipes vs Modules:
- **Recipes**: Configuration-only, site building, content types, views
- **Modules**: Custom PHP code, new functionality, APIs
composer require drupal/recipe_name php core/scripts/drupal recipe recipes/contrib/recipe_name

Recipes 与 Modules 的使用场景:
- **Recipes**:仅包含配置、站点构建、内容类型、视图
- **Modules**:自定义 PHP 代码、新功能、APIs

Testing Compatibility

测试兼容性

bash
undefined
bash
undefined

Test against both versions in CI

Test against both versions in CI

jobs: test-d10: env: DRUPAL_CORE: ^10.3 test-d11: env: DRUPAL_CORE: ^11
undefined
jobs: test-d10: env: DRUPAL_CORE: ^10.3 test-d11: env: DRUPAL_CORE: ^11
undefined

Migration Planning

迁移规划

Before upgrading D10 → D11:
  1. Run
    drupal-check
    for deprecations
  2. Update all contrib modules to D11-compatible versions
  3. Convert annotations to attributes
  4. Consider moving hooks to OOP style
  5. Test thoroughly in staging environment
从 D10 升级到 D11 之前:
  1. 运行
    drupal-check
    检查弃用项
  2. 将所有 contrib modules 更新到兼容 D11 的版本
  3. 将 annotations 转换为 attributes
  4. 考虑将 hooks 迁移为 OOP 风格
  5. 在 staging environment 中进行全面测试

Pre-Commit Checks

提交前检查

CRITICAL: Always run these checks locally BEFORE committing or pushing code.
CI pipeline failures are embarrassing and waste time. Catch issues locally first.
关键:提交或推送代码之前,务必在本地运行以下检查。
CI pipeline 失败既尴尬又浪费时间。应先在本地发现问题。

Required: Coding Standards (PHPCS)

必需:编码标准(PHPCS)

bash
undefined
bash
undefined

Check for coding standard violations

Check for coding standard violations

./vendor/bin/phpcs -p --colors modules/custom/
./vendor/bin/phpcs -p --colors modules/custom/

Auto-fix what can be fixed

Auto-fix what can be fixed

./vendor/bin/phpcbf modules/custom/
./vendor/bin/phpcbf modules/custom/

Check specific file

Check specific file

./vendor/bin/phpcs path/to/MyClass.php

**Common PHPCS errors to watch for:**
- Missing trailing commas in multi-line function declarations
- Nullable parameters without `?` type hint
- Missing docblocks
- Incorrect spacing/indentation
./vendor/bin/phpcs path/to/MyClass.php

**需要关注的常见 PHPCS 错误:**
- 多行函数声明缺少末尾逗号
- Nullable 参数缺少 `?` 类型提示
- 缺少 docblocks
- 间距或缩进不正确

DDEV Shortcut

DDEV 快捷方式

bash
undefined
bash
undefined

Run inside DDEV

Run inside DDEV

ddev exec ./vendor/bin/phpcs -p modules/custom/ ddev exec ./vendor/bin/phpcbf modules/custom/
undefined
ddev exec ./vendor/bin/phpcs -p modules/custom/ ddev exec ./vendor/bin/phpcbf modules/custom/
undefined

Recommended: Full Pre-Commit Checklist

推荐:完整的提交前检查清单

bash
undefined
bash
undefined

1. Coding standards

1. Coding standards

./vendor/bin/phpcs -p modules/custom/
./vendor/bin/phpcs -p modules/custom/

2. Static analysis (if configured)

2. Static analysis (if configured)

./vendor/bin/phpstan analyze modules/custom/
./vendor/bin/phpstan analyze modules/custom/

3. Deprecation checks

3. Deprecation checks

./vendor/bin/drupal-check modules/custom/
./vendor/bin/drupal-check modules/custom/

4. Run tests

4. Run tests

./vendor/bin/phpunit modules/custom/my_module/tests/
undefined
./vendor/bin/phpunit modules/custom/my_module/tests/
undefined

Git Pre-Commit Hook (Optional)

Git Pre-Commit Hook(可选)

Create
.git/hooks/pre-commit
:
bash
#!/bin/bash
./vendor/bin/phpcs --standard=Drupal,DrupalPractice modules/custom/ || exit 1
Make executable:
chmod +x .git/hooks/pre-commit
创建
.git/hooks/pre-commit
:
bash
#!/bin/bash
./vendor/bin/phpcs --standard=Drupal,DrupalPractice modules/custom/ || exit 1
设置为可执行:
bash
chmod +x .git/hooks/pre-commit

Installing PHPCS with Drupal Standards

安装带有 Drupal Standards 的 PHPCS

bash
composer require --dev drupal/coder
./vendor/bin/phpcs --config-set installed_paths vendor/drupal/coder/coder_sniffer
bash
composer require --dev drupal/coder
./vendor/bin/phpcs --config-set installed_paths vendor/drupal/coder/coder_sniffer

AI-Assisted Development Patterns

AI 辅助开发模式

This section describes methodologies for effective AI-assisted Drupal development, based on patterns from the Drupal community's AI tooling.
本节介绍高效的 AI 辅助 Drupal 开发方法,其依据来自 Drupal 社区的 AI tooling 模式。

The Context-First Approach

Context-First 方法

CRITICAL: Always gather context before generating code. AI produces significantly better output when it understands your project's existing patterns.
关键:生成代码之前,务必先收集上下文。当 AI 了解项目现有模式时,输出质量会显著提升。

Step 1: Find Similar Files

第 1 步:查找相似文件

Before generating new code, locate similar implementations in your codebase:
bash
undefined
生成新代码之前,先在代码库中查找类似实现:
bash
undefined

Find similar services

Find similar services

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

Find similar forms

Find similar forms

find modules/custom -name "*Form.php" -type f
find modules/custom -name "*Form.php" -type f

Find similar controllers

Find similar controllers

find modules/custom -path "/Controller/.php" -type f
find modules/custom -path "/Controller/.php" -type f

Find similar plugins

Find similar plugins

find modules/custom -path "/Plugin/Block/.php" -type f

**Why this matters:** When you show existing code patterns to AI, it will:
- Match your naming conventions
- Use the same dependency injection patterns
- Follow your project's architectural style
- Integrate consistently with existing code
find modules/custom -path "/Plugin/Block/.php" -type f

**这样做的重要性:**当向 AI 展示现有代码模式时,它将:
- 匹配您的命名约定
- 使用相同的依赖注入模式
- 遵循项目的架构风格
- 与现有代码保持一致地集成

Step 2: Understand Project Patterns

第 2 步:理解项目模式

Before requesting code generation, identify:
markdown
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?
请求生成代码之前,应确定:
markdown
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?

Step 3: Provide Context in Requests

第 3 步:在请求中提供上下文

Structure your requests with explicit context:
markdown
**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"
使用明确的上下文组织请求:
markdown
**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"

Structured Prompting for Drupal Tasks

Drupal 任务的结构化提示

Use hierarchical prompts for complex generation tasks. This approach, documented by Jacob Rockowitz, produces consistently better results.
**对于复杂的代码生成任务,使用分层提示。**Jacob Rockowitz 记录的方法能够持续获得更优结果。

Prompt Template Structure

提示模板结构

markdown
undefined
markdown
undefined

Task

Task

[One sentence describing what you want to create]
[One sentence describing what you want to create]

Module Context

Module Context

  • Module name: my_custom_module
  • Module path: modules/custom/my_custom_module
  • Drupal version: 10.3+ / 11
  • PHP version: 8.2+
  • Module name: my_custom_module
  • Module path: modules/custom/my_custom_module
  • Drupal version: 10.3+ / 11
  • PHP version: 8.2+

Requirements

Requirements

  • [Specific requirement 1]
  • [Specific requirement 2]
  • [Specific requirement 3]
  • [Specific requirement 1]
  • [Specific requirement 2]
  • [Specific requirement 3]

Code Standards

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
  • 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)

Similar Files (for reference)

  • [Path to similar implementation]
  • [Path to similar implementation]
  • [Path to similar implementation]
  • [Path to similar implementation]

Expected Output

Expected Output

undefined
undefined

Example: Creating a Block Plugin

示例:创建 Block Plugin

markdown
undefined
markdown
undefined

Task

Task

Create a block that displays recent articles with a configurable limit.
Create a block that displays recent articles with a configurable limit.

Module Context

Module Context

  • Module name: my_articles
  • Module path: modules/custom/my_articles
  • Drupal version: 10.3+
  • PHP version: 8.2+
  • Module name: my_articles
  • Module path: modules/custom/my_articles
  • Drupal version: 10.3+
  • PHP version: 8.2+

Requirements

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
  • 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

Code Standards

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

Similar Files

Similar Files

  • modules/custom/my_articles/src/Plugin/Block/FeaturedArticleBlock.php
  • modules/custom/my_articles/src/Plugin/Block/FeaturedArticleBlock.php

Expected Output

Expected Output

  • src/Plugin/Block/RecentArticlesBlock.php
  • config/schema/my_articles.schema.yml (update)
undefined
  • src/Plugin/Block/RecentArticlesBlock.php
  • config/schema/my_articles.schema.yml (update)
undefined

The Inside-Out Approach

Inside-Out 方法

Based on the Drupal AI CodeGenerator pattern, this methodology breaks complex tasks into deterministic steps:
该方法基于 Drupal AI CodeGenerator 模式,将复杂任务拆分为确定性步骤。

Phase 1: Task Classification

阶段 1:任务分类

Determine what type of task is being requested:
TypeDescriptionApproach
CreateNew file/component neededGenerate with DCG, then customize
EditModify existing codeRead first, then targeted changes
InformationQuestion about code/architectureSearch and explain
CompositeMultiple steps neededBreak down, execute sequentially
确定所请求任务的类型:
类型说明方法
Create需要新建文件或组件使用 DCG 生成,然后自定义
Edit修改现有代码先读取,再进行针对性更改
Information关于代码或架构的问题搜索并解释
Composite包含多个步骤拆分并按顺序执行

Phase 2: Solvability Check

阶段 2:可解决性检查

Before generating, verify:
markdown
✓ Required dependencies available?
✓ Target directory exists and is writable?
✓ No conflicting files/classes?
✓ All referenced services/classes exist?
✓ Compatible with Drupal version?
生成之前,验证:
markdown
✓ Required dependencies available?
✓ Target directory exists and is writable?
✓ No conflicting files/classes?
✓ All referenced services/classes exist?
✓ Compatible with Drupal version?

Phase 3: Scaffolding First

阶段 3:优先搭建脚手架

Use DCG to scaffold, then customize. This ensures Drupal best practices:
bash
undefined
**使用 DCG 生成脚手架,然后进行自定义。**这样可以确保遵循 Drupal 最佳实践:
bash
undefined

1. Generate base structure

1. Generate base structure

drush generate plugin:block --answers='{ "module": "my_module", "plugin_id": "recent_articles", "admin_label": "Recent Articles", "class": "RecentArticlesBlock" }'
drush generate plugin:block --answers='{ "module": "my_module", "plugin_id": "recent_articles", "admin_label": "Recent Articles", "class": "RecentArticlesBlock" }'

2. Review generated code

2. Review generated code

cat modules/custom/my_module/src/Plugin/Block/RecentArticlesBlock.php
cat modules/custom/my_module/src/Plugin/Block/RecentArticlesBlock.php

3. Customize with specific requirements

3. Customize with specific requirements

(AI edits the generated file to add business logic)

(AI edits the generated file to add business logic)

undefined
undefined

Phase 4: Auto-Generate Tests

阶段 4:自动生成测试

Always generate tests alongside code:
bash
undefined
始终与代码一同生成测试:
bash
undefined

Generate kernel test for the new functionality

Generate kernel test for the new functionality

drush generate test:kernel --answers='{ "module": "my_module", "class": "RecentArticlesBlockTest" }'
undefined
drush generate test:kernel --answers='{ "module": "my_module", "class": "RecentArticlesBlockTest" }'
undefined

Iterative Development Workflow

迭代开发工作流

Expect 80% completion from AI-generated code. Plan for refinement cycles.
**预计 AI 生成的代码可完成约 80%。**应规划后续的完善周期。

The Realistic Workflow

实际工作流

┌─────────────────────────────────────────────────────────────┐
│  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                               │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│  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                               │
└─────────────────────────────────────────────────────────────┘

Common Refinement Tasks

常见的完善任务

IssueSolution
PHPCS errorsRun
phpcbf
for auto-fix, manual fix for complex issues
Missing DIAdd to constructor, update
create()
method
No cache metadataAdd
#cache
with tags, contexts, max-age
Missing access checkAdd permission check or access handler
No config schemaCreate schema file matching config structure
Hardcoded stringsWrap in
$this->t()
with proper placeholders
问题解决方案
PHPCS errors运行
phpcbf
自动修复,复杂问题手动修复
Missing DI添加到构造函数,更新
create()
方法
No cache metadata添加带有 tags、contexts、max-age 的
#cache
Missing access check添加权限检查或 access handler
No config schema创建与配置结构匹配的 schema 文件
Hardcoded strings使用适当的占位符包裹在
$this->t()
中

Integration with Drupal AI Module

与 Drupal AI Module 集成

When the AI module is available, leverage
drush aigen
for rapid prototyping:
bash
undefined
当 AI module 可用时,利用
drush aigen
快速进行原型开发:
bash
undefined

Check if AI Generation is available

Check if AI Generation is available

drush pm:list --filter=ai_generation
drush pm:list --filter=ai_generation

Generate a complete content type

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)"
drush aigen "Create a content type called 'Event' with fields: title, date (datetime), location (text), description (formatted text), image (media reference)"

Generate a view

Generate a view

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

Generate a custom module

Generate a custom module

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

**Important:** Always review AI-generated code. The AI Generation module is experimental and intended for development only.
drush aigen "Create a module that sends email notifications when new events are created"

**重要:**始终审查 AI 生成的代码。AI Generation module 仍处于实验阶段,仅适用于开发环境。

Prompt Patterns for Common Tasks

常见任务的提示模式

Content Type with Fields

带字段的内容类型

markdown
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
markdown
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

Custom Service

自定义 Service

markdown
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
markdown
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

Event Subscriber

Event Subscriber

markdown
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
markdown
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

Debugging AI-Generated Code

调试 AI 生成的代码

When generated code doesn't work:
bash
undefined
当生成的代码无法正常工作时:
bash
undefined

1. Check for PHP syntax errors

1. Check for PHP syntax errors

php -l modules/custom/my_module/src/MyClass.php
php -l modules/custom/my_module/src/MyClass.php

2. Clear all caches

2. Clear all caches

drush cr
drush cr

3. Check service container

3. Check service container

drush devel:services | grep my_module
drush devel:services | grep my_module

4. Check for missing use statements

4. Check for missing use statements

grep -n "^use" modules/custom/my_module/src/MyClass.php
grep -n "^use" modules/custom/my_module/src/MyClass.php

5. Verify class is autoloaded

5. Verify class is autoloaded

drush php:eval "class_exists('Drupal\my_module\MyClass') ? print 'Found' : print 'Not found';"
drush php:eval "class_exists('Drupal\my_module\MyClass') ? print 'Found' : print 'Not found';"

6. Check logs

6. Check logs

drush ws --severity=error --count=20
undefined
drush ws --severity=error --count=20
undefined

Sources

参考资料