logo

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

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

演示 EBT 模块 下载 EBT 模块

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

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

演示 EPT 模块 滚动

滚动

定义和使用内容实体字段定义

30/09/2025, by Ivan

Content entities 必须通过为实体类提供定义来显式定义其所有字段。字段定义基于 Typed data API(参见 实体如何实现它)。

字段定义

实体类型在实体类的静态方法中定义它们的基本字段。基本字段是不可配置的字段,它们在给定实体类型中始终存在,例如节点的标题或创建和修改日期。实体管理器通过调用 hook_entity_field_info() 和 hook_entity_field_info_alter() 来补充模块提供的可配置和不可配置字段。通过 Field UI 配置的字段也是这样添加的(这些钩子在新的 API 中已不存在)。

字段定义是实现了 FieldDefinitionInterface 的简单对象,而基本字段通常使用 BaseFieldDefinition 类创建,而自定义字段直接实现接口并配合相应的配置对象(所谓的 Field 和 FieldInstance)。
字段定义还是为字段元素或字段元素属性定义验证约束的地方。所有字段类型插件的实现都可以使用。(此接口和类已不存在)。

字段当前始终是字段元素列表,这意味着定义为类型的 FieldItem 类将被封装在表示这些字段元素列表的 FieldItemList 类中。

所有字段(包括基本字段)也可以具有用于显示和编辑的表单小部件和格式器。

基本字段

下面是节点实体类型的字段定义示例(简化版)。

use Drupal\Core\Field\BaseFieldDefinition;

class Node implements NodeInterface {

  /**
   * {@inheritdoc}
   */
  public static function baseFieldDefinitions($entity_type) {
    // 节点 ID 是整数,使用 IntegerItem 字段项类。
    $fields['nid'] = BaseFieldDefinition::create('integer')
      ->setLabel(t('Node ID'))
      ->setDescription(t('The node ID.'))
      ->setReadOnly(TRUE);

    // UUID 字段使用 uuid_field 类型,确保在创建实体时自动生成新的 UUID。
    $fields['uuid'] = BaseFieldDefinition::create('uuid')
      ->setLabel(t('UUID'))
      ->setDescription(t('The node UUID.'))
      ->setReadOnly(TRUE);

    // 语言代码被定义为 language_field,确保为新实体设置有效的默认语言代码。
    $fields['langcode'] = BaseFieldDefinition::create('language')
      ->setLabel(t('Language code'))
      ->setDescription(t('The node language code.'));

    // 标题是 StringItem,默认值为空字符串,并定义了一个属性约束,值最多为 255 个字符。
    $fields['title'] = BaseFieldDefinition::create('string')
      ->setLabel(t('Title'))
      ->setDescription(t('The title of this node, always treated as non-markup plain text.'))
      ->setRequired(TRUE)
      ->setTranslatable(TRUE)
      ->setSettings(array(
        'default_value' => '',
        'max_length' => 255,
      ));

    // uid 是对用户实体类型的实体引用,可以通过 $node->uid->target_id 获取用户 ID,
    // 通过 $node->uid->entity 获取用户实体。
    $fields['uid'] = BaseFieldDefinition::create('entity_reference')
      ->setLabel(t('User ID'))
      ->setDescription(t('The user ID of the node author.'))
      ->setSettings(array(
        'target_type' => 'user',
        'default_value' => 0,
      ));

    // changed 字段类型在每次保存实体时自动更新时间戳。
    $fields['changed'] = BaseFieldDefinition::create('changed')
      ->setLabel(t('Changed'))
      ->setDescription(t('The time that the node was last edited.'))
    return $fields;
  }
}

多值字段

要指定字段允许的最大元素数,请调用 setCardinality() 方法。
例如,定义一个最多有 3 个元素的字段:

->setCardinality(3);

要定义具有无限值的字段,请调用:

->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);

多值字段的示例,引用“用户”实体:

$fields['my_field'] = BaseFieldDefinition::create('entity_reference')
  ->setLabel(t('The label of the field'))
  ->setDescription(t('The description of the field.'))
  ->setRevisionable(TRUE)
  ->setSetting('target_type', 'user')
  ->setSetting('handler', 'default')
  ->setTranslatable(TRUE)
  ->setDisplayOptions('view', [
    'label' => 'hidden',
    'type' => 'author',
    'weight' => 0,
  ])
  ->setDisplayOptions('form', [
    'type' => 'entity_reference_autocomplete',
    'weight' => 5,
    'settings' => [
      'match_operator' => 'CONTAINS',
      'size' => '60',
      'autocomplete_type' => 'tags',
      'placeholder' => '',
    ],
  ])
  ->setDisplayConfigurable('form', TRUE)
  ->setDisplayConfigurable('view', TRUE);
  ->setRequired(TRUE)
  ->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);

实体还可以为特定 bundle 提供字段,或通过 bundle 修改它们。例如,节点的标题可以在每个 bundle 上具有不同的标签。要在 bundle 上实现更改,必须克隆基本字段定义再进行修改,否则会更改所有 bundle 的定义。

use Drupal\node\Entity\NodeType;

  /**
   * {@inheritdoc}
   */
  public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
    $node_type = NodeType::load($bundle);
    $fields = array();
    if (isset($node_type->title_label)) {
      $fields['title'] = clone $base_field_definitions['title'];
      $fields['title']->setLabel($node_type->title_label);
    }
    return $fields;
  }

字段类型

Drupal 核心提供了可以用于基本字段的字段类型列表。此外,模块还可以提供额外的字段类型。

  • string:简单字符串
  • boolean:布尔值,以整数存储
  • integer:整型,带最小值和最大值的验证设置(decimal 和 float 也有类似的设置)
  • decimal:十进制数,带可配置的精度和小数位
  • float:浮点数
  • language:包含语言代码,并且语言作为计算属性
  • timestamp:Unix 时间戳,存储为整数
  • created:时间戳,默认值为当前时间
  • changed:时间戳,每次保存对象时更新为当前时间
  • datetime:以 ISO 8601 字符串形式存储的日期
  • URI:包含 URI。Link 模块还提供 Link 字段类型,可以包含链接标题并指向内部或外部 URI/路由
  • uuid:UUID 字段,生成新的 UUID 作为默认值
  • email:电子邮件字段,包含相应的验证、小部件和格式器
  • entity_reference:实体引用,通过 target_id 和计算的 entity 属性存储
  • map:可以包含任意数量的属性,存储为序列化字符串

可配置字段

可以在 hook_entity_base_field_info() 和 hook_entity_bundle_field_info() 中注册额外的字段。下面示例分别添加了 base 和 bundle 字段。

use Drupal\Core\Field\BaseFieldDefinition;

/**
 * Implements hook_entity_base_field_info().
 */
function path_entity_base_field_info(EntityTypeInterface $entity_type) {
  if ($entity_type->id() === 'taxonomy_term' || $entity_type->id() === 'node') {
    $fields['path'] = BaseFieldDefinition::create('path')
      ->setLabel(t('The path alias'))
      ->setComputed(TRUE);

    return $fields;
  }
}

/**
 * Implements hook_entity_bundle_field_info().
 */
function field_entity_bundle_field_info(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
  if ($entity_type->isFieldable()) {
    // 可配置字段总是绑定到具体 bundle。
    return Field::fieldInfo()->getBundleInstances($entity_type->id(), $bundle);
  }
}

以上每个钩子都有对应的 alter 钩子。

字段存储

如果字段没有特殊需求,Entity Field API 会处理数据库存储并相应更新数据库 schema。这是默认值,除非字段标记为计算字段 (setComputed(TRUE)) 或特别声明提供自定义存储 (setCustomStorage(TRUE))。

假设你要为所有 Node 实体添加一个新的布尔字段,用于标记内容是否高亮。

use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;

/**
 * Implements hook_entity_base_field_info().
 */
function MYMODULE_entity_base_field_info(EntityTypeInterface $entity_type) {
  $fields = array();

  // 为所有节点类型添加 'Highlight' 字段。
  if ($entity_type->id() === 'node') {
    $fields['highlight'] = BaseFieldDefinition::create('boolean')
      ->setLabel(t('Highlight'))
      ->setDescription(t('Whether or not the node is highlighted.'))
      ->setRevisionable(TRUE)
      ->setTranslatable(TRUE)
      ->setDisplayOptions('form', array(
        'type' => 'boolean_checkbox',
        'settings' => array(
          'display_label' => TRUE,
        ),
      ))
      ->setDisplayConfigurable('form', TRUE);
  }

  return $fields;
}

我尝试了很多次,访问 update.php 不会在数据库中添加列,但运行

  \Drupal::entityTypeManager()->clearCachedDefinitions();
  \Drupal::service('entity.definition_update_manager')->applyUpdates();

可以创建数据库列。注意:这也会触发其他待处理字段定义更新。

更新: 上述代码在 Drupal 8.7 中不起作用

请参考 变更记录 中的示例。

安装新的字段存储定义

function example_update_8701() {
  $field_storage_definition = BaseFieldDefinition::create('boolean')
    ->setLabel(t('Revision translation affected'))
    ->setDescription(t('Indicates if the last edit of a translation belongs to current revision.'))
    ->setReadOnly(TRUE)
    ->setRevisionable(TRUE)
    ->setTranslatable(TRUE);

  \Drupal::entityDefinitionUpdateManager()
    ->installFieldStorageDefinition('revision_translation_affected', 'block_content', 'block_content', $field_storage_definition);
}

如果你的自定义模块添加了新字段,启用模块时会自动添加,卸载模块时会自动移除。

如果模块已经安装,并且需要编写 hook_update_N 来更新字段定义,可以这样:

/**
 * Add in highlight field to all nodes.
 */
function MYMODULE_update_8001() {
  $entity_type = \Drupal::service('entity_type.manager')->getDefinition('node');
  \Drupal::service('entity.definition_update_manager')->updateEntityType($entity_type);
}

或:

/**
 * Add 'revision_translation_affected' field to 'node' entities.
 */
function node_update_8001() {
  // 安装 \Drupal\node\Entity\Node::baseFieldDefinitions() 中的定义。
  $storage_definition = BaseFieldDefinition::create('boolean')
      ->setLabel(t('Revision translation affected'))
      ->setDescription(t('Indicates if the last edit of a translation belongs to current revision.'))
      ->setReadOnly(TRUE)
      ->setRevisionable(TRUE)
      ->setTranslatable(TRUE);

  \Drupal::entityDefinitionUpdateManager()
    ->installFieldStorageDefinition('revision_translation_affected', 'node', 'node', $storage_definition);
}

参见 https://www.drupal.org/node/2554097 获取更多信息和示例。

使用字段定义

注意: 由于实体是复杂数据,它们必须遵循 ComplexDataInterface。从 Typed Data 的角度来看,复杂数据对象中的所有元素都是属性。这种命名约束可能会被移除。

// 检查实体是否有某个字段
$entity->hasField('field_tags');

// 返回所有字段及其定义的数组。例如 ‘image’ 字段。
$field_definitions = $entity->getFieldDefinitions();

// 返回 image 字段的所有属性及其定义。例如 ‘file_id’ 和 ‘alt’ 属性。
$property_definitions = $entity->image->getFieldDefinition()->getPropertyDefinitions();

// 返回 ‘alt’ 属性的定义。
$alt_definition = $entity->image->getFieldDefinition()->getPropertyDefinition('alt');

// 从 entity manager 获取字段定义,返回所有 bundle 可用的字段。
\Drupal::service('entity_field.manager')->getFieldStorageDefinitions($entity_type);

// 返回指定 bundle 的字段。
\Drupal::service('entity_field.manager')->getFieldDefinitions($entity_type, $bundle);

基本字段的小部件和格式器

基本字段可以像可配置字段一样指定小部件和格式器。小部件、格式器及其参数在 FieldDefinition 类中定义:

use Drupal\Core\Field\BaseFieldDefinition;

// ...

    $fields['title'] = BaseFieldDefinition::create('string')
      ->setLabel(t('Title'))
      ->setDescription(t('The title of this node, always treated as non-markup plain text.'))
      ->setRequired(TRUE)
      ->setTranslatable(TRUE)
      ->setSettings(array(
        'default_value' => '',
        'max_length' => 255,
      ))
      ->setDisplayOptions('view', array(
        'label' => 'hidden',
        'type' => 'string',
        'weight' => -5,
      ))
      ->setDisplayOptions('form', array(
        'type' => 'string',
        'weight' => -5,
      ))
      ->setDisplayConfigurable('form', TRUE);

这里使用了 string 格式器和小部件,并为节点标题设置了权重。setDisplayConfigurable() 可以让字段在“管理表单显示/显示”界面中可见,从而可以调整顺序和标签显示。目前核心不允许在 UI 中修改小部件或其设置。

要将字段默认设置为隐藏,还可以在传递给 setDisplayOptions() 的数组中定义 region 键,并将其设置为 hidden。