首页
/ awesome-copilot 中 Drupal Expert 自定义 Agent 全解析:基于 PHP 8.3+ 的 Drupal 开发现代最佳实践

awesome-copilot 中 Drupal Expert 自定义 Agent 全解析:基于 PHP 8.3+ 的 Drupal 开发现代最佳实践

2026-09-08 19:14:05作者:明树来

本文以 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),文件名则一律小写加连字符,同时强烈推荐声明 modeltools 字段。

对照这些约定,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_subscriberaccess_checkbreadcrumb_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 作为十进制字段显式设置 precisionscalecreated / 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.managerconfig.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 同时启用 DrupalDrupalPractice 两个标准集(前者查格式,后者查实践层面的反模式);修复则用 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 代码评审的收尾检查:

  1. Use Drupal APIs:绝不绕过 Drupal 的 API——坚持 entity API、Form API、render API;
  2. Dependency Injection:注入服务,在类中避免静态 \Drupal:: 调用;
  3. Security Always:验证输入、转义输出、校验权限;
  4. Cache Properly:所有 render arrays 都要带 cache tags、contexts 与 max-age;
  5. Follow Standards:用带 Drupal 规则的 phpcs 检查编码规范;
  6. Test Everything:内核测试覆盖逻辑,功能测试覆盖工作流;
  7. Document Code:补全 docblock、行内注释与 README;
  8. Configuration Management:导出所有配置、使用 schema、把 YAML 纳入版本控制;
  9. Performance Matters:优化查询、使用懒加载、落实缓存策略;
  10. 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 的核心价值。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391