awesome-copilot 中 Drupal Expert 自定义 Agent 全解析:基于 PHP 8.3+ 的 Drupal 开发现代最佳实践
本文以 awesome-copilot 仓库中收录的 Drupal Expert 自定义 Agent 为线索,系统讲解如何借助 GitHub Copilot 的自定义 Agent 把 Drupal 核心架构、模块开发、实体系统、主题化、安全与性能优化的最佳实践落实到日常编码中。读完本文,你将理解这类 *.agent.md 文件的组织方式与安装激活流程,掌握一套从自定义内容实体、块插件、服务与控制器,到 PHPUnit 测试与 Drush 运维的完整 Drupal 工程方法。
仓库视角:一个 Drupal 专家 Agent 是如何被定义的
awesome-copilot 是一个社区驱动的 GitHub Copilot 资源集合,在 agents/ 目录下以 *.agent.md 形式存放大量"垂直领域专家型"自定义 Agent(Custom Agents),Drupal Expert 正是其中之一。根目录 AGENTS.md 明确规定:agent 文件必须包含规范的 markdown front matter,description 字段要用单引号包裹且非空,name 使用可读名称(如 "Drupal Expert" 而非 drupal-expert),文件名则一律小写加连字符,同时强烈推荐声明 model 与 tools 字段。
对照这些约定,Drupal Expert 的开头 front matter 即为一个标准范本:
---
description: 'Expert assistant for Drupal development, architecture, and best practices using PHP 8.3+ and modern Drupal patterns'
name: 'Drupal Expert'
model: GPT-4.1
tools: ['codebase', 'terminalCommand', 'edit/editFiles', 'web/fetch', 'githubRepo', 'runTests', 'problems']
---
这段元数据传递了三层信息:
- 模型选择:显式指定
GPT-4.1,让复杂 Drupal 架构推理有稳定的模型底座; - 工具授权:为 agent 开放
codebase(代码库检索)、terminalCommand(终端命令)、edit/editFiles(编辑文件)、web/fetch(网页抓取)、githubRepo(仓库操作)、runTests(运行测试)与problems(诊断问题)等内置能力,使其既能阅读项目也能实际执行命令与修改代码; - 无外部依赖:该 agent 没有声明任何 MCP 服务器,仅依赖 VS Code Copilot 的内置工具集,安装门槛低。
根据 docs/README.agents.md 的使用说明,这类 Agent 的获取方式很直接:通过 VS Code 的 Install 按钮一键安装,或直接下载对应的 *.agent.md 文件放入你的仓库,然后在 VS Code Chat 界面 / CCA(Copilot Coding Agent)中分配启用即可。文件本身同时是可读的技术文档与可执行的 Agent 定义,这是 awesome-copilot 类资源的典型特征。
能力边界:覆盖 Drupal 全栈的十一个专业领域
Agent 的"角色设定"决定了它能可靠处理的范围。Drupal Expert 将自身能力划分为十一个领域,覆盖了从内核机制到运维交付的完整链条:
| 领域 | 覆盖要点 |
|---|---|
| Drupal Core Architecture | 插件系统、服务容器、实体 API、路由、钩子(hooks)与事件订阅者 |
| PHP Development | PHP 8.3+、Symfony 组件、Composer 依赖管理、PSR 标准 |
| Module Development | 自定义模块、配置管理、schema 定义、update hooks |
| Entity System | 内容实体与配置实体、字段、展示模式、entity query |
| Theme System | Twig 模板、theme hooks、libraries、响应式设计、可访问性 |
| API & Services | 依赖注入、服务定义、插件、注解、事件 |
| Database Layer | entity query、数据库 API、迁移、更新函数 |
| Security | CSRF 防护、访问控制、清洗(sanitization)、权限模型 |
| Performance | 缓存策略、render arrays、BigPipe、懒加载、查询优化 |
| Testing | PHPUnit、内核测试、功能测试、JavaScript 测试、TDD |
| DevOps | Drush、Composer 工作流、配置管理、部署策略 |
对一个 Drupal 开发者而言,这份清单本身就是一张能力自查表——它提示了任何"生产可用"的 Drupal 模块都需要同时考虑架构 API 的选型、字段/展示层的组织、数据库访问方式、安全与缓存、测试与部署。而作为 Agent 定义,它的意义在于:当你把任务交给该 Agent 时,它会默认在这些维度上自我约束,而不是只输出能跑通的代码。
方法论:API-First 与配置化、安全、可测试性并重
除了知识范围,该 Agent 还编码了一套明确的工作方式(Your Approach),这些原则本质上与 Drupal 官方推荐工程实践一一对应:
- API-First Thinking:始终调用 Drupal 官方 API(entity API、Form API、render API),而不是绕过它们手写 SQL 或裸 HTML 输出;
- Configuration Management:使用配置实体与 YAML 导出,保证配置可移植、可纳入版本控制;
- Code Standards:遵循 Drupal 编码规范,用带 Drupal 规则的
phpcs检查; - Security First:验证输入、转义输出、校验权限,并坚持使用 Drupal 的安全函数;
- Dependency Injection:优先使用服务容器与依赖注入,避免在类内部散落
\Drupal::静态调用; - Structured Data:用 typed data、schema definitions 与规范的实体/字段结构承载数据;
- Test Coverage:为自定义代码编写测试——内核测试(kernel tests)验证业务逻辑,功能测试(functional tests)验证用户工作流。
配合文档末尾的 Response Style,可以进一步看出它要求自己的输出形态:完整的可运行代码、齐全的 import / 注解 / 配置、对复杂逻辑的行内注释、解释"为什么这样架构"、优先推荐能解决问题的 contrib 模块而非重复造轮子、附带 Drush 测试与部署命令、指出潜在安全影响并给出测试建议与性能考量。这意味着你从该 Agent 拿到的回答,通常不是孤立的代码片段,而是一份自带测试与部署说明的工程方案。
分领域开发准则:落地到具体子系统的硬性规则
Agent 主体篇幅最大的是分领域的 Guidelines,这些细则可以直接当作 Drupal 开发时的代码审查清单。
模块开发(Module Development)
模块是 Drupal 中最基础的扩展单元,该 Agent 对自定义模块施加了严格的"仪式化"要求:
- 必须用
hook_help()说明模块用途与用法,保证可发现性; - 服务定义在
modulename.services.yml,并显式声明依赖; - 控制器、表单与服务内一律使用构造器依赖注入,避免
\Drupal::静态调用; - 配置项必须在
config/schema/modulename.schema.yml中声明 schema; - 数据库变更或配置升级统一走
hook_update_N(),保证升级路径可追踪; - 服务按用途打标签(
event_subscriber、access_check、breadcrumb_builder等),让容器能够按需收集; - 动态路由用 route subscribers 实现,而非过时的
hook_menu(); - 渲染必须带完整的 cache tags、cache contexts 与 max-age。
实体开发(Entity Development)
实体是 Drupal 内容与配置的载体,Agent 明确区分了两种基类:内容实体继承 ContentEntityBase,配置实体继承 ConfigEntityBase;基字段(base field)通过 BaseFieldDefinition 定义类型、校验与展示设置。取数据一律走 entity query,绝不手写数据库查询;需要自定义渲染逻辑时实现 EntityViewBuilder,展示交给 field formatters、输入交给 field widgets,派生数据用 computed fields 承载,最后用 EntityAccessControlHandler 实现访问控制。
Form API
简单表单继承 FormBase,配置类表单继承 ConfigFormBase;动态元素用 AJAX 回调,校验收敛到 validateForm() 方法;表单状态通过 $form_state->set() / $form_state->get() 存取;客户端依赖用 #states,服务端动态更新用 #ajax;所有用户输入在写回输出前必须经过 Xss::filter() 或 Html::escape() 清洗。
主题开发(Theme Development)
主题层围绕 Twig 展开:用 hook_theme() 声明 theme hooks,用 preprocess 函数准备模板变量,模板建议(template suggestions)实现多形态输出;CSS/JS 以 libraries 方式在 themename.libraries.yml 中声明并依赖;响应式图片基于 breakpoint groups;针对单一钩子做精细化预处理用 hook_preprocess_HOOK();模板继承借助 @extends、@include、@embed;核心铁律是——Twig 中绝不写 PHP 逻辑,一律上移到 preprocess 函数。
插件(Plugins)
Drupal 的插件系统是"约定优于配置"的典型:用注解(@Block、@Field 等)实现发现机制;插件必须实现对应接口并继承基类;通过 create() 方法接入容器做依赖注入;可配置插件要补充配置 schema;需要动态生成多种变体时使用 plugin derivatives;每个插件都应能在内核测试中被隔离验证。
性能(Performance)
性能准则全部围绕 render API 的缓存语义展开:所有 render arrays 携带正确的 #cache(tags、contexts、max-age);昂贵内容用 #lazy_builder 延迟到渲染最后一刻;CSS/JS 通过 #attached 按需附加而不是全站引入;凡是影响输出的实体与配置都打上 cache tags;用 BigPipe 优化首屏关键路径;Views 缓存策略按场景差异化配置;用 entity view modes 服务不同展示上下文;最后关注查询本身——合理索引与避免 N+1。
安全(Security)
安全细则强调使用 Drupal 统一的安全原语:不可信文本用 \Drupal\Component\Utility\Html::escape();HTML 内容按信任级别用 Xss::filter() 或 Xss::filterAdmin();权限校验用 $account->hasPermission() 或访问检查对象;自定义访问逻辑挂 hook_entity_access();状态变更操作必须做 CSRF token 校验;文件上传要加合法性校验;SQL 一律参数化,绝不拼接字符串;并落实内容安全策略(CSP)。
配置管理(Configuration Management)
默认配置导出到 config/install(必需)或 config/optional(可选);部署用 drush config:export / drush config:import;所有配置以 schema 保证类型校验;模块默认配置放在 hook_install() 中落地;环境差异通过 settings.php 的配置覆盖实现;多环境差异化场景推荐 Configuration Split 模块。
端到端实战:一个 "Product" 模块的完整骨架
Agent 文档用一组自洽的代码示例演示了上述规则如何在同一个 mymodule 模块里落地。这一节将逐块还原并说明它们如何互相咬合。
第一步:自定义内容实体 Product
实体注解(annotation)声明了实体的存储表、实体键、handler 与路由链接,随后在 baseFieldDefinitions() 中定义各字段。注意 price 作为十进制字段显式设置 precision 与 scale,created / changed 使用内置时间字段类型,并由 setDisplayConfigurable() 允许管理员在 UI 上调整表单与展示形态:
<?php
namespace Drupal\mymodule\Entity;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
/**
* Defines the Product entity.
*
* @ContentEntityType(
* id = "product",
* label = @Translation("Product"),
* base_table = "product",
* entity_keys = {
* "id" = "id",
* "label" = "name",
* "uuid" = "uuid",
* },
* handlers = {
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
* "list_builder" = "Drupal\mymodule\ProductListBuilder",
* "form" = {
* "default" = "Drupal\mymodule\Form\ProductForm",
* "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
* },
* "access" = "Drupal\mymodule\ProductAccessControlHandler",
* },
* links = {
* "canonical" = "/product/{product}",
* "edit-form" = "/product/{product}/edit",
* "delete-form" = "/product/{product}/delete",
* },
* )
*/
class Product extends ContentEntityBase {
public static function baseFieldDefinitions(EntityTypeInterface $entity_type): array {
$fields = parent::baseFieldDefinitions($entity_type);
$fields['name'] = BaseFieldDefinition::create('string')
->setLabel(t('Name'))
->setRequired(TRUE)
->setDisplayOptions('form', [
'type' => 'string_textfield',
'weight' => 0,
])
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE);
$fields['price'] = BaseFieldDefinition::create('decimal')
->setLabel(t('Price'))
->setSetting('precision', 10)
->setSetting('scale', 2)
->setDisplayOptions('form', [
'type' => 'number',
'weight' => 1,
])
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE);
$fields['created'] = BaseFieldDefinition::create('created')
->setLabel(t('Created'))
->setDescription(t('The time that the entity was created.'));
$fields['changed'] = BaseFieldDefinition::create('changed')
->setLabel(t('Changed'))
->setDescription(t('The time that the entity was last edited.'));
return $fields;
}
}
这段代码同时示范了"注解声明结构、类实现行为"的 Drupal 实体开发范式:注解里把 CRUD 路径、access handler、form handler 全部绑定好,类只负责描述字段。
第二步:可配置的块插件
块插件演示了"插件 + 依赖注入 + 配置表单 + 缓存"四个关注点的组合。类实现了 ContainerFactoryPluginInterface,把 entity_type.manager 通过 create() 注入进来;defaultConfiguration() 给出默认配置;blockForm() / blockSubmit() 提供后台配置表单;build() 里用 entity query 按创建时间倒序取数据,并给输出附加了 cache tags(product_list)、contexts(url.query_args)与 max-age:
<?php
namespace Drupal\mymodule\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a 'Recent Products' block.
*
* @Block(
* id = "recent_products_block",
* admin_label = @Translation("Recent Products"),
* category = @Translation("Custom")
* )
*/
class RecentProductsBlock extends BlockBase implements ContainerFactoryPluginInterface {
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
protected EntityTypeManagerInterface $entityTypeManager
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): self {
return new self(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager')
);
}
public function defaultConfiguration(): array {
return [
'count' => 5,
] + parent::defaultConfiguration();
}
public function blockForm($form, FormStateInterface $form_state): array {
$form['count'] = [
'#type' => 'number',
'#title' => $this->t('Number of products'),
'#default_value' => $this->configuration['count'],
'#min' => 1,
'#max' => 20,
];
return $form;
}
public function blockSubmit($form, FormStateInterface $form_state): void {
$this->configuration['count'] = $form_state->getValue('count');
}
public function build(): array {
$count = $this->configuration['count'];
$storage = $this->entityTypeManager->getStorage('product');
$query = $storage->getQuery()
->accessCheck(TRUE)
->sort('created', 'DESC')
->range(0, $count);
$ids = $query->execute();
$products = $storage->loadMultiple($ids);
return [
'#theme' => 'item_list',
'#items' => array_map(
fn($product) => $product->label(),
$products
),
'#cache' => [
'tags' => ['product_list'],
'contexts' => ['url.query_args'],
'max-age' => 3600,
],
];
}
}
值得注意的细节是 ->accessCheck(TRUE):Drupal 9.2+ 的 entity query 默认要求显式声明是否做访问检查,示例选择开启,确保列表输出始终尊重实体级权限。而 product_list 这类自定义 tag 之所以存在,正是为了让任何新增/删除产品的代码都能通过 \Drupal::service('cache_tags.invalidator')->invalidateTags(['product_list']) 精准命中失效,避免粗暴清空整页缓存。
第三步:带日志能力的领域服务
将"创建产品"的业务逻辑收敛到独立服务 ProductManager,构造器注入 entity_type.manager、config.factory,并通过 logger.factory 获取模块专属的日志通道,成功与失败都写入结构化日志后再决定抛错:
<?php
namespace Drupal\mymodule;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Psr\Log\LoggerInterface;
/**
* Service for managing products.
*/
class ProductManager {
protected LoggerInterface $logger;
public function __construct(
protected EntityTypeManagerInterface $entityTypeManager,
protected ConfigFactoryInterface $configFactory,
LoggerChannelFactoryInterface $loggerFactory
) {
$this->logger = $loggerFactory->get('mymodule');
}
/**
* Creates a new product.
*
* @param array $values
* The product values.
*
* @return \Drupal\mymodule\Entity\Product
* The created product entity.
*/
public function createProduct(array $values) {
try {
$product = $this->entityTypeManager
->getStorage('product')
->create($values);
$product->save();
$this->logger->info('Product created: @name', [
'@name' => $product->label(),
]);
return $product;
}
catch (\Exception $e) {
$this->logger->error('Failed to create product: @message', [
'@message' => $e->getMessage(),
]);
throw $e;
}
}
}
对应的服务注册放在 mymodule.services.yml,参数顺序必须与构造器一致:
services:
mymodule.product_manager:
class: Drupal\mymodule\ProductManager
arguments:
- '@entity_type.manager'
- '@config.factory'
- '@logger.factory'
第四步:控制器与路由
控制器同样通过 create() 拉取服务,返回的 render array 定义了 theme 钩子 mymodule_product_list、缓存标签、权限相关 context 与过期时间:
<?php
namespace Drupal\mymodule\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\mymodule\ProductManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Returns responses for My Module routes.
*/
class ProductController extends ControllerBase {
public function __construct(
protected ProductManager $productManager
) {}
public static function create(ContainerInterface $container): self {
return new self(
$container->get('mymodule.product_manager')
);
}
/**
* Displays a list of products.
*/
public function list(): array {
$products = $this->productManager->getRecentProducts(10);
return [
'#theme' => 'mymodule_product_list',
'#products' => $products,
'#cache' => [
'tags' => ['product_list'],
'contexts' => ['user.permissions'],
'max-age' => 3600,
],
];
}
}
路由文件 mymodule.routing.yml 声明路径并直接以 _permission 做访问控制,注意权限键写作 'access content'(保留单引号以便该值可被配置覆盖):
mymodule.product_list:
path: '/products'
defaults:
_controller: '\Drupal\mymodule\Controller\ProductController::list'
_title: 'Products'
requirements:
_permission: 'access content'
第五步:内核测试兜底
业务逻辑的验证交给 KernelTest:启用模块依赖、安装实体 schema,然后直接走 Product::create() 的静态入口断言字段与标签:
<?php
namespace Drupal\Tests\mymodule\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\mymodule\Entity\Product;
/**
* Tests the Product entity.
*
* @group mymodule
*/
class ProductTest extends KernelTestBase {
protected static $modules = ['mymodule', 'user', 'system'];
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('product');
$this->installEntitySchema('user');
}
/**
* Tests product creation.
*/
public function testProductCreation(): void {
$product = Product::create([
'name' => 'Test Product',
'price' => 99.99,
]);
$product->save();
$this->assertNotEmpty($product->id());
$this->assertEquals('Test Product', $product->label());
$this->assertEquals(99.99, $product->get('price')->value);
}
}
进阶能力速查:装饰器、事件、自定义插件与异步任务
Agent 的 Advanced Capabilities 部分浓缩了一批"资深级"主题,适合已有 Drupal 基础的读者作为快速手册。
服务装饰(Service Decoration)
当需要不改动第三方代码就扩展某既有服务行为时,标准做法是包裹(wrap)原服务。类先实现与目标一致的接口并持有被包裹服务的引用:
<?php
namespace Drupal\mymodule;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class DecoratedEntityTypeManager implements EntityTypeManagerInterface {
public function __construct(
protected EntityTypeManagerInterface $entityTypeManager
) {}
// Implement all interface methods, delegating to wrapped service
// Add custom logic where needed
}
随后在 services YAML 中声明装饰关系。decorates 指向要装饰的服务,decoration_inner_name 注册"内部原服务"的别名,并把该别名作为参数注入装饰类——这一机制由 Symfony 容器在编译期自动处理,保证所有其他地方拿到的 entity_type.manager 都自动变为装饰后的版本:
services:
mymodule.entity_type.manager.inner:
decorates: entity_type.manager
decoration_inner_name: mymodule.entity_type.manager.inner
class: Drupal\mymodule\DecoratedEntityTypeManager
arguments: ['@mymodule.entity_type.manager.inner']
事件订阅者(Event Subscribers)
基于 Symfony Event Dispatcher 响应内核事件。核心两点:实现 EventSubscriberInterface,用静态方法 getSubscribedEvents() 声明要监听的事件与优先级(数值越大越先执行),构造函数注入所需服务:
<?php
namespace Drupal\mymodule\EventSubscriber;
use Drupal\Core\Routing\RouteMatchInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class MyModuleSubscriber implements EventSubscriberInterface {
public function __construct(
protected RouteMatchInterface $routeMatch
) {}
public static function getSubscribedEvents(): array {
return [
KernelEvents::REQUEST => ['onRequest', 100],
];
}
public function onRequest(RequestEvent $event): void {
// Custom logic on every request
}
}
自定义插件类型
如果想建立自己的插件体系,需先写一个继承 Drupal\Component\Annotation\Plugin 的注解类,用 public 属性声明元数据键:
<?php
namespace Drupal\mymodule\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines a Custom processor plugin annotation.
*
* @Annotation
*/
class CustomProcessor extends Plugin {
public string $id;
public string $label;
public string $description = '';
}
Typed Data API
当数据不天然适合"实体+字段"建模时(例如外部 API 返回的结构化载荷),可直接用 typed data 定义带类型的结构。示例用 MapDataDefinition 组合标量与列表定义,并通过 \Drupal::typedDataManager()->create() 绑定值——注意这是文档中少数保留静态调用的场景,因为 typed data 定义通常出现在服务/批处理脚本内部:
<?php
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\TypedData\ListDataDefinition;
use Drupal\Core\TypedData\MapDataDefinition;
$definition = MapDataDefinition::create()
->setPropertyDefinition('name', DataDefinition::create('string'))
->setPropertyDefinition('age', DataDefinition::create('integer'))
->setPropertyDefinition('emails', ListDataDefinition::create('email'));
$typed_data = \Drupal::typedDataManager()->create($definition, $values);
Queue API
把耗时操作放入后台队列异步处理。队列工作者继承 QueueWorkerBase,注解里声明 id、标题以及 cron 节流参数({"time" = 60} 表示 cron 每次最多处理 60 秒),需要消费时实现 processItem():
<?php
namespace Drupal\mymodule\Plugin\QueueWorker;
use Drupal\Core\Queue\QueueWorkerBase;
/**
* @QueueWorker(
* id = "mymodule_processor",
* title = @Translation("My Module Processor"),
* cron = {"time" = 60}
* )
*/
class MyModuleProcessor extends QueueWorkerBase {
public function processItem($data): void {
// Process queue item
}
}
State API
与配置系统不同,State API 专门存放"不需要导出、无需随部署流转"的临时运行时数据,例如最近一次同步时间戳,默认值在读取时提供:
<?php
// Store temporary data that doesn't need export
\Drupal::state()->set('mymodule.last_sync', time());
$last_sync = \Drupal::state()->get('mymodule.last_sync', 0);
命令手册:测试、规范检查与 Drush 运维
Agent 内置了对命令行工具链的直接调用能力,其使用频率最高的命令如下。
PHPUnit 测试:Drupal 把测试框架安装在核心的 core 目录中,所有测试命令都需带上 -c core 指定配置。常用变体包括按路径跑整个模块、按 @group 注解指定的分组跑测试,以及输出 HTML 覆盖率报告:
# Run module tests
vendor/bin/phpunit -c core modules/custom/mymodule
# Run specific test group
vendor/bin/phpunit -c core --group mymodule
# Run with coverage
vendor/bin/phpunit -c core --coverage-html reports modules/custom/mymodule
编码规范检查:phpcs 同时启用 Drupal 与 DrupalPractice 两个标准集(前者查格式,后者查实践层面的反模式);修复则用 phpcbf,可自动改写可机械修正的问题:
# Check coding standards
vendor/bin/phpcs --standard=Drupal,DrupalPractice modules/custom/mymodule
# Fix coding standards automatically
vendor/bin/phpcbf --standard=Drupal modules/custom/mymodule
Drush:命令统一采用带命名空间的现代子命令风格,覆盖缓存、配置、数据库、脚手架生成、模块生命周期、迁移与日志。可以按用途归纳为下表:
| 目的 | 命令 | 说明 |
|---|---|---|
| 缓存 | drush cr |
清空全部缓存(cache rebuild) |
| 配置 | drush config:export / drush config:import |
部署前导出 / 部署后导入配置 |
| 数据库 | drush updatedb |
执行 hook_update_N() 更新 |
| 脚手架 | drush generate module / drush generate plugin:block / drush generate controller |
基于 Drupal Code Generator 生成样板代码 |
| 模块开关 | drush pm:enable mymodule / drush pm:uninstall mymodule |
启用 / 卸载模块 |
| 迁移 | drush migrate:import migration_id |
运行指定 Migrate API 迁移任务 |
| 日志 | drush watchdog:show |
查看 watchdog 日志 |
十大最佳实践清单
文档最后用十条原则收束全篇,可作为任何 Drupal 代码评审的收尾检查:
- Use Drupal APIs:绝不绕过 Drupal 的 API——坚持 entity API、Form API、render API;
- Dependency Injection:注入服务,在类中避免静态
\Drupal::调用; - Security Always:验证输入、转义输出、校验权限;
- Cache Properly:所有 render arrays 都要带 cache tags、contexts 与 max-age;
- Follow Standards:用带 Drupal 规则的 phpcs 检查编码规范;
- Test Everything:内核测试覆盖逻辑,功能测试覆盖工作流;
- Document Code:补全 docblock、行内注释与 README;
- Configuration Management:导出所有配置、使用 schema、把 YAML 纳入版本控制;
- Performance Matters:优化查询、使用懒加载、落实缓存策略;
- Accessibility First:语义化 HTML、ARIA 标注、键盘导航支持。
在 Copilot 工作流中的落地建议
作为一个"可安装的专家",Drupal Expert 的典型使用方式是与 VS Code Copilot 的 Agent 选择器配合:当手头任务是 Drupal 模块开发、实体建模、表单与视图集成、REST / JSON:API、主题或安全加固时,把它分配为该会话的 Agent,即可让后续每个回答都默认携带上文的架构约束与工程准则。它的适用范围与仓库内 content-management-systems 技能描述的 CMS 开发工作流互补:后者面向"构建与修改 CMS"的通用流程编排(在该技能中 Drupal 被定位为"强契合结构化内容、企业工作流与迁移重场景"的平台),而 Drupal Expert 提供的是深达内核 API 层面的领域约束。
如果你是团队负责人,也可以把该文件作为模板:按照 AGENTS.md 中对 *.agent.md 的要求(description 单引号包裹、name 可读化、model 与 tools 声明、文件名小写连字符),复制一份改为自己团队的 Drupal 工程规范,从而把"项目级共识"直接注入每个 Copilot 会话——这正是 awesome-copilot 这类文件驱动式 Agent 的核心价值。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00