定义和使用内容实体字段定义
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。