logo

额外区块类型 (EBT) - 全新的布局构建器体验❗

额外区块类型 (EBT) - 样式化、可定制的区块类型:幻灯片、标签页、卡片、手风琴等更多类型。内置背景、DOM Box、JavaScript 插件的设置。立即体验布局构建的未来。

演示 EBT 模块 下载 EBT 模块

❗额外段落类型 (EPT) - 全新的 Paragraphs 体验

额外段落类型 (EPT) - 类似的基于 Paragraph 的模块集合。

演示 EPT 模块 滚动

滚动

Drupal 8 中的 Entity API 简介

30/09/2025, by Ivan

Drupal 8 实体系统

实体是带有方法的强类型类

通用方法

$entity->id()

特定于实体类型的方法 $node->getTitle()

 

背景

实体系统在 Drupal 7 开发周期的后期引入,带有基本的实体加载标准。entity.module 进一步扩展了 API,增加了对保存和删除实体的支持,以及许多其他改进。

这些改进中的大多数现在已包含在 Drupal 8 中。实体验证现在在其自己的 API 中进行(例如,它可以验证通过 REST 保存的实体,而不是通过表单)。

两种变体

核心中的实体类型有两种变体。

配置实体
使用 配置系统。支持翻译,并且可以为安装提供默认的用户设置。配置对象以行的形式存储在共享的配置数据库表中。

内容实体
由可配置字段和基本字段组成,可以有修订并支持翻译。内容对象以行的形式存储在自定义数据库表中。表名与对象的“id”相同,列由对象的“baseFieldDefinitions”方法定义。

包(Bundles)

包是实体类型的不同变体。例如,对于节点实体类型,包是不同的节点类型,如“文章”和“页面”。

通常,包由配置对象表示,尽管 contrib 模块中也有其他模型。因此,在节点示例中,“article” 节点类型本身就是一个配置对象。配置存储了不同内容实体类型之间的差异,如设置和字段。当您创建一个带包的实体类型时,您将同时创建一个内容实体(用于处理内容的详细信息和操作)和一个配置对象(用于处理内容实体类型之间的差异)。

注解

创建新的实体类型时,您需要使用内置在核心中的注解系统。注解看起来像类上方的 docblock 注释,但会被 Drupal 核心解析和缓存。在许多方面,注解替代了 Drupal 7 中使用的一些较旧风格。

注解解析器

注解在运行时由注解机制读取和解析。Drupal 8 使用 Doctrine 注解解析器,将其转换为 PHP 可以使用的对象。

语法。 注解语法用 @ClassName() 包裹,主要由 key/value 键值对组成,并且可以包含使用大括号的数组。顶层键不需要加引号,而数组键必须加引号。每个 key/value 键值对必须在单独的一行上,并且这一行必须以逗号结尾。某些函数可以对值执行,特别是 @Translation() 函数。

注解语法的一个无效示例:

/**
 * @ContentEntityType(
 *   id = "my_entity_type_id",
 *   label = @Translation("My entity type label"),
 *   example_pair = "this_examples_value",
 *   example_array = {
 *     "array_key" = "array_value",
 *     "some_other_key" = "some_other_value",
 *   },
 * )
 */

常见的顶层注解

Key = "示例值" 描述 实体变体
id = "node",

对象类型的机器名。

内容 & 配置
label = @Translation("Node"),

对象类型的可读名称。

内容 & 配置
admin_permission = "administer nodes",

允许管理访问以配置和管理对象类型的权限。如果您的实体未定义“访问”处理器,则这是必需的。

内容 & 配置
bundle_label = @Translation("Content type"),

包对象类型的可选可读名称。

内容
bundle_entity_type = "node_type",

当创建带包的内容对象时,此值必须是配置对象的“id”。在本例中,“node_type”是配置对象。

内容
base_table = "node",

对象类型的数据库表名。

内容
fieldable = TRUE,

(布尔值)此实体类型是否可以通过字段 UI 扩展。

内容
field_ui_base_route = "entity.node_type.edit_form", 绑定到实体可用对象的字段 UI 的路由名称。 内容

 

处理器(Handlers)

处理器在对象的注解中定义为数组。它们支持实体,将实体执行的特定部分映射到其他 PHP 类。这些类将“处理”实体执行的指定部分。

存储 - 处理对象的加载、保存和删除。默认情况下,内容对象使用 Drupal\Core\Entity\Sql\SqlContentEntityStorage,而配置对象使用 Drupal\Core\Config\Entity\ConfigEntityStorage。您可以定义一个存储处理器,以扩展您的实体的标准存储方法。您可能希望这样做,以便提供额外的方法来收集实体修订 ID 或确定实体拥有的翻译数量。
示例: "storage" = "Drupal\node\NodeStorage",

表单 - 在任何实体的处理器注解中都有几个表单处理器,它们将添加、编辑和删除实体的表单映射到其他 PHP 类。
示例:

"form" = {
  "add" = "Drupal\block\BlockForm",
  "edit" = "Drupal\block\BlockForm",
  "delete" = "Drupal\block\Form\BlockDeleteForm",
}

此外,您可以定义一个“默认”表单来处理“添加”和“编辑”表单,而不是分别定义它们。值得注意的是,“删除”表单几乎总是由与其他表单不同的类处理。这是因为删除表单通常是一个“确认表单”,它只会询问用户是否确定要删除对象。

视图构建器(View builder) - 此处理器提供一个类,该类将在最终用户查看实体时处理其输出。例如,在 Drupal 8 站点上访问一个节点时,实体的输出由 NodeViewBuilder 类处理。
示例: "view_builder" = "Drupal\node\NodeViewBuilder",

列表构建器(List builder) - 列表构建器类将处理实体的管理列表。该类将在访问实体管理页面时定义表头、行和操作的内容。例如,在访问 Drupal 站点的 URI /admin/content 时,表格内容由节点实体的列表构建器类提供。
示例: "list_builder" = "Drupal\node\NodeListBuilder",

路由提供器(Route provider) - 可选的处理器,如果实现,它将为管理对象生成路由。实现此处理器可能取代您模块 routing.yml 文件中定义的实体路径。请注意,route_provider 与为您的实体定义的链接一起工作(参见下文“链接”部分的示例)。route_provider 注解是一个数组。
示例:

"route_provider" = {
  "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
}

访问(Access) - 访问处理器可用于动态检查您的实体的权限。它映射到一个实现 EntityAccessControlHandlerInterface 的类。核心提供了该接口的实现 EntityAccessControlHandler,但如果您希望对实体进行更严格的控制,则需要扩展该类并自定义。
示例: "access" = "NodeAccessControlHandler",

视图数据(Views data) - views_data 处理器允许对象为 Views 模块扩展您的实体提供的自定义数据。这可能用于将实体的 baseFieldDefinitions 添加为视图字段、在实体关系上连接表,或进行与视图相关的其他数据更改。
示例: "views_data" = "Drupal\node\NodeViewsData",

存储模式(Storage schema) - storage_schema 处理器可用于进一步更改对象的数据库存储设置。例如,添加额外的表索引。
示例: "storage_schema" = "Drupal\node\NodeStorageSchema",

翻译(Translation) - 翻译处理器可用于更改实体表单与翻译的交互方式。
示例: "translation" = "Drupal\node\NodeTranslationHandler",

完整 handlers 示例:
Drupal 核心提供了开箱即用的处理器,但在许多情况下,您会希望扩展这些类以便对您的实体有更多控制和自定义。以下示例展示了使用核心类的更完整的处理器注解,您可以扩展这些类。

handlers = {
  "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
  "list_builder" = "Drupal\Core\Entity\EntityListBuilder",
  "access" = "Drupal\Core\Entity\EntityAccessControlHandler",
  "views_data" = "Drupal\views\EntityViewsData",
  "storage" = "Drupal\Core\Entity\Sql\SqlContentEntityStorage",
  "storage_schema" = "Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema",
  "translation" = "Drupal\content_translation\ContentTranslationHandler",
  "form" = {
    "default" = "Drupal\Core\Entity\ContentEntityForm",
    "add" = "Drupal\Core\Entity\ContentEntityForm",
    "edit" = "Drupal\Core\Entity\ContentEntityForm",
    "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
  },
  "route_provider" = {
    "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
  },
},

链接(Links)

链接在对象注解中以数组语法定义。链接有一组特定的键,其值是用于管理对象类型或该类型单个对象的 URI。这些对象可以为内容或配置对象定义。

示例:

id = "node",
handlers = {
  "route_provider" = {
    "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider"
  }
},
links = {
  "canonical" = "/node/{node}",
  "add-page" = "/node/add",
  "add-form" = "/node/add/{node_type}",
  "edit-form" = "/node/{node}/edit",
  "delete-form" = "/node/{node}/delete",
  "collection" = "/admin/content",
},

请注意,这不是从 Node 模块逐字提取的,这只是一个示例。

创建这些链接不会自动为这些 URI 创建路由。为了使这些链接可用,您的模块需要实现自己的 routing.yml 文件,或在对象注解中使用 route_provider 处理器。

链接和路由提供器

上述链接与“route_provider”一起使用时,将使以下命名路由在 Drupal 中可用。

链接键 路由名称 示例路由 URI 描述
canonical entity.node.canonical /node/1 查看特定节点
add-page entity.node.add_page /node/add 选择要添加的节点
add-form entity.node.add_form /node/add/article 添加节点(特定包)
edit-form entity.node.edit_form /node/1/edit 编辑特定节点的表单
delete-form entity.node.delete_form /node/1/delete 删除特定节点的表单
collection entity.node.collection /admin/content 以列表形式查看所有节点

 

使用链接
这些链接可以通过对象的 toUrl() 方法访问:

$view_url_object = $entity->toUrl();  // 默认是 'canonical'
$edit_url_string = $entity->toUrl('edit-form')->toString();

参考资料: