103. 实体视图显示及格式化器
在实体视图构建器中构建完实体的基本渲染数组后,会调用实体视图显示对象继续构建实体字段对象的渲染数组,然后合并到基本数组中(合并过程基本数组的优先级更高)以形成完整实体渲染数组,实体视图显示对象内部又依据配置调用字段格式化器来构建每一个字段对象的渲染数组。
实体显示对象在视图构建器中的调用入口如下:
构建多个实体的字段渲染数组:
$display->buildMultiple($entities);
该方法用同一视图模式渲染多个有相同实体类型、bundle的实体,保持传入键名不变
构建单个实体的字段渲染数组:
$display->build($entity);
传入的实体必须属于显示对象对应的实体类型和budle
字段格式化器在视图显示对象中的调用入口如下:
让格式化器执行准备工作:
$formatter->prepareView($grouped_items);
返回字段完整渲染数组:
$build_list[$id][$name]=$formatter->view($items, $view_langcode);
和实体表单显示一样,“实体视图显示EntityViewDisplay”同样有两个身份,既是一个配置实体,实体类型id:entity_view_display,用来储存实体类型的bundle在某个视图模式下的显示配置数据,如字段采用何种格式化器、显示顺序、是否显示等等,同时也是一个实体视图构建对象,负责在内容实体视图构建流程中对具备显示选项的字段进行视图渲染数组构建,类定义如下:
\Drupal\Core\Entity\Entity\EntityViewDisplay
该类实例化的对象既是配置实体对象又是视图构建对象,下文简称为视图显示对象或显示对象
作为一个配置实体,加载储存的所有视图显示配置代码如下:
\Drupal::entityTypeManager()->getStorage('entity_view_display')->loadMultiple();
这些配置实体的id格式如下:
实体类型id.bundle.模式名
其中模式名不带实体类型前缀,如:user.user.default、node.article.full、node.article.rss
作为一个视图构建对象,她实现了以下接口:
\Drupal\Core\Entity\Display\EntityViewDisplayInterface
在控制器中运行以下代码可查看视图显示配置数据:
$entities = \Drupal::entityTypeManager()->getStorage('entity_view_display')->loadMultiple();
$data = [];
foreach ($entities as $id => $entity) {
$data[$id] = $entity->toArray();
}
print_r(array_keys($data));
print_r($data);
die;
这些数据除了配置实体的通用数据外,最重要的有以下键:
id:实例化的视图显示对象id,和加载实体的实体id相同
targetEntityType:本视图显示对象代表的实体类型id
bundle:本视图显示对象代表的实体类型的某个bundle
mode:视图显示模式名
content:子健为字段名,包括伪字段,在视图显示对象中称为组件Component,其值为字段显示配置
hidden:子健为被禁用的字段名,包括伪字段,他们不会被显示,其值为true
其中字段显示配置有以下键:
type:该字段要使用的格式化器类型id,如省略、配置错误或不适用将使用字段类型定义中指定的默认格式化器,伪字段没有该项
label:label所在的位置,inline、above、hidden之一,默认above
weight:字段显示排序的权重
region:显示字段所在的区域,值为content表示正常显示,值为hidden表示隐藏
settings:用于格式化器本身的设置,来自管理员设置、字段定义的显示选项和格式化器默认值,优先级依次递减,可由格式化器提供的配置表单设置
third_party_settings:通过hook_field_formatter_third_party_settings_form()钩子收集的第三方模块的设置,以模块名做第一级键名,传递给格式化器使用,详见本系列表单显示主题
在实体视图构建器中通过以下方法实例化视图显示对象:
public static function collectRenderDisplays($entities, $view_mode)
实体视图显示对象方法说明:
该类继承自实体显示基类,基类中的方法在本系列实体表单显示中已经介绍,这里仅介绍本类的方法
public static function collectRenderDisplays($entities, $view_mode)
获取多个视图显示对象,参数$entities是一个要被显示的实体对象构成的数组,键名是在显示列表中的序号,键值是实体对象,这些实体对象必须属于同一个实体类型,且使用相同视图模式,参数$view_mode是这些实体对象正在被显示的视图模式,返回值是一个数组,以bundle作为键名,键值是视图显示对象,该方法优先使用传入的视图模式的显示对象,如果不存在将以视图模式名为“default”的显示对象代替,如果仍然不存在,则将以传入的视图模式新建一个(不会执行保存),在经过这个过程得到显示对象后会派发以下修改钩子以便模块参与修改:
entity_view_display
钩子函数如下:
hook_entity_view_display_alter(EntityViewDisplayInterface $display, array $context)
传递的第一个参数是以上过程得到的视图显示对象,她即将被使用,如需整个替换则应以引用接收,第二个参数是一个上下文数组,键名及其值如下:
entity_type:值为字符串值的实体类型id
bundle:值为字符串值的bundle ID
view_mode:值为原始请求的视图模式,也被储存在视图显示对象的originalMode属性下,这并不一定就是当前视图显示对象的显示模式,换句话说$viewDisplay->getMode()可能和该值不相等,也可以使用$viewDisplay->getOriginalMode()获得该值;默认安装中仅节点实现了该修改钩子(对节点实体的搜索索引视图进行特殊处理)。
public function postSave(EntityStorageInterface $storage, $update = TRUE)
当显示对象有保存动作时,视为发生更新,失效该实体类型的视图输出,该方法有改进的余地,最佳做法应该是失效和该显示对象代表的视图模式及bundle相关的视图输出,而不是鲁莽的失效全部
public function getRenderer($field_name)
实例化字段格式化器,参数为字段名,实例化工作由字段格式化器的插件管理器getInstance方法负责,注意这里传递的配置信息是已经合并了默认值的最终配置信息,详见下文
public function buildMultiple(array $entities)
构建多个实体对象的字段视图渲染数组,该方法用同一视图模式渲染多个有相同实体类型、bundle的实体,返回值以传入键名做键名并保持对应关系不变;在显示配置中隐藏的字段(没有组件数据)不被渲染构造,没有格式化器的字段也不被渲染构造(伪字段往往通过钩子构造),prepareView方法调用见下
改造完成后会派发以下修改钩子:
entity_display_build
函数原型:
hook_entity_display_build_alter(&$build, $context)
参数$build为一个数组,键名为实体的字段名,键值为其视图渲染数组
参数$context为上下文数组,有如下键值:
entity:实体对象
view_mode:原始请求的实体模式,见$display->originalMode
display:视图显示对象
实体视图显示相关表单:
配置编辑表单路由_entity_form为entity_view_display.edit
类:\Drupal\field_ui\Form\EntityViewDisplayEditForm
继承自:\Drupal\field_ui\Form\EntityDisplayFormBase
详见本系列实体表单显示
字段格式化器插件管理器:
字段格式化器以插件方式提供,后简称格式化器,其插件管理器定义如下
服务id :plugin.manager.field.formatter
类:Drupal\Core\Field\FormatterPluginManager
获取方式:\Drupal::service('plugin.manager.field.formatter')
插件定义缓存位置:缓存表cache_discovery 的field_formatter_types_plugins条目
插件储存目录:Plugin/Field/FieldFormatter
字段格式化器插件定义修改钩子名:field_formatter_info
释文类:Drupal\Core\Field\Annotation\FieldFormatter
格式化器接口:Drupal\Core\Field\FormatterInterface
格式化器插件管理器方法说明:
public function getInstance(array $options)
public function createInstance($plugin_id, array $configuration = [])
见下文:格式化器插件的实例化
public function prepareConfiguration($field_type, array $configuration)
参数$field_type为字段类型id,参数$configuration来自字段定义中的以下方法:
$field_definition->getDisplayOptions(‘view’);
合并显示选项中的子健settings与格式化器的默认设置选项,字段定义优先,其中无效多余的settings将去除
public function getOptions($field_type = NULL)
返回字段类型可用的格式化器,参数为字段类型插件id,返回值是一个数组,键名为字段类型可用的格式化器插件id,键值为格式化器label,如果没有传递字段类型参数,将返回所有字段类型的该数组,此时第一级键名为字段类型插件id
public function getDefaultSettings($type)
参数为格式化器插件id,获取某个字段格式化器的默认配置选项,也就是调用格式化器的静态方法:defaultSettings()
格式化器插件的实例化:
在渲染内容实体视图时,有显示选项且允许显示的字段,其视图渲染数组的产生在实体视图显示对象中进行,在该对象内部通过字段的视图显示配置确定一个格式化器类型,并通过格式化器插件管理器实例化格式化器对象,最终由格式化器对象来完成相应工作,详见以下方法:
\Drupal\Core\Entity\Entity\EntityViewDisplay::getRenderer
该方法实例化过程相当于在执行以下代码:
\Drupal::service('plugin.manager.field.formatter') ->getInstance($options);
参数$options是一个数组,有如下键名:
field_definition:字段定义对象
form_mode:原始请求的视图模式originalMode
prepare:布尔值,是否需要合并格式化器默认值,默认为FALSE,因为视图显示对象中储存的配置已经合并了格式化器默认配置
configuration:字段对应的视图显示配置,储存在视图显示配置的字段键名下,这里假设视图显示对象为$display,字段名为$field_name(字符串值),可通过以下代码查看:
$display->getComponent($field_name)
实例化为什么没有直接使用createInstance方法呢?那是因为如果配置错误(如格式化器不支持字段类型、不适用等),将回退使用字段的默认格式化器,最终还是会统一调用插件管理器的创建实例方法:
\Drupal::service('plugin.manager.field.formatter')->createInstance($plugin_id, $configuration)
其中$configuration来自前文提到的字段视图显示配置,是一个数组,有以下键:
type:要使用的格式化器类型id,如省略、配置错误或不适用将使用字段类型定义中指定的默认格式化器,因此其值未必等于插件id
weight:字段显示排序的权重
region:显示字段所在的区域,值为content表示正常显示,值为hidden表示隐藏
settings:用于格式化器本身的设置,来自字段定义显示选项和格式化器默认值,可由格式化器提供的配置表单设置
third_party_settings:第三方模块附加的设置,以模块名做第一级键名
此外被附加了键名field_definition,其值为字段定义对象,请熟知该数组的结构,对于插件而言:
如果实现了容器工厂接口那么将用插件类自身的创建方法实例化,并传入如下参数:
$plugin_class::create(\Drupal::getContainer(), $configuration, $plugin_id, $plugin_definition);
反之则直接实例化,传入如下参数:
$plugin_class($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
后三个参数来自$configuration的相应键名
字段格式化器插件定义:
和drupal其他插件定义一样,详见本系列插件篇,这里对字段格式化器的释文说明如下:
id:格式化器ID
label:格式化器人类可读标签
description:格式化器人类可读描述
field_types:格式化器支持的字段类型,数组值,元素值为字段类型id,如不支持,则无法被其使用
weight:格式化器默认权重,用于与其他控件在一起时的排序,可选,默认为0
格式化器接口:
Drupal\Core\Field\FormatterInterface
格式化器对象在显示对象中的执行入口为:
让格式化器执行准备工作:
$formatter->prepareView($grouped_items);
该方法的介绍见下文
返回字段完整渲染数组:
$build_list[$id][$name]=$formatter->view($items, $view_langcode);
该方法中的参数$items为内容实体的字段对象,来自:$items = $entity->get($name);
字段格式化器默认基类:
系统提供了以下字段格式化器的默认实现:
\Drupal\Core\Field\FormatterBase
她实现了格式化器接口,格式化器插件类通常需要继承该基类,这里结合接口对方法说明如下:
public function prepareView(array $entities_items);
该方法将在view方法前调用,用于格式化器在开始构造渲染数组前做一些准备工作,大部分格式化器用不到该方法,但有些情况下是必要的,因此接口设计了该方法,并在视图显示对象中给予了先行调用,目前在系统中默认情况下仅引用字段类型用到了她:为提高性能在该方法中一次性加载需要渲染的所有被引用实体对象,详见:
\Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceFormatterBase::prepareView
如果没有该方法那么每次view方法调用都要进行一次数据库查询,性能是非常糟糕的,相反就可以一次性知道有哪些实体需要渲染,就能做到一次查询多个,这就大大提高了性能,可见该方法是很有必要的
参数$entities_items是一个数组,键名为渲染序号,键值为字段对象,详见显示对象的buildMultiple方法
public function view(FieldItemListInterface $items, $langcode = NULL)
返回单个完整的字段对象渲染数组,参数$items为内容实体的字段对象,来自:$entity->get($name);,参数$langcode指明需要的语言id,如果没有传递将使用内容协商产生的语言,字段对象的渲染数组构造分两步完成,第一步构造字段对象的所有条目对象的渲染数组(由viewElements方法完成),第二步附加共用数据,有如下共用数据:
$info = [
'#theme' => 'field',
'#title' => $this->fieldDefinition->getLabel(),
'#label_display' => $this->label,
'#view_mode' => $this->viewMode,
'#language' => $items->getLangcode(),
'#field_name' => $field_name,
'#field_type' => $this->fieldDefinition->getType(),
'#field_translatable' => $this->fieldDefinition->isTranslatable(),
'#entity_type' => $entity_type,
'#bundle' => $entity->bundle(),
'#object' => $entity,
'#items' => $items,
'#formatter' => $this->getPluginId(),
'#is_multiple' => $this->fieldDefinition->getFieldStorageDefinition()->isMultiple(),
];
在附加时采用array_merge($info, $elements);合并,也就是说共用数据优先级低,可被viewElements方法返回的内容覆写
public function viewElements(FieldItemListInterface $items, $langcode)
构造字段对象中全部条目对象的渲染数组,通常内部以foreach遍历$items的每个条目,构造后形成一个索引数组返回
public function settingsForm(array $form, FormStateInterface $form_state);
返回格式化器自身的配置表单,在管理显示页面使用,以供管理员配置格式化器,见:
\Drupal\field_ui\Form\EntityDisplayFormBase
系统会将提交的值存放到视图显示配置中字段组件的settings键下,该数据也是格式化器的构造参数之一,注意该表单不要设置#parents、#tree属性以免影响值提取,更多详细可参见本系列实体表单显示主题
public function settingsSummary();
用于在管理显示页面的字段行中显示格式化器当前的设置信息,返回一个字符串数组,每个元素代表一条当前格式化器配置的摘要信息,模块可以通过field_formatter_settings_summary修改钩去修改返回值
public static function isApplicable(FieldDefinitionInterface $field_definition);
返回格式化器能否用于被提供的字段,虽然在格式化器插件释文中已经通过field_types表明了她支持的字段类型,但由于字段是可配置的,该方法通过字段定义对象进一步精确判断是否可用,返回布尔值
public static function defaultSettings()
返回格式化器的默认设置,注意这是一个静态方法,在产生格式化器设置数据时将合并该数据
默认标准安装中存在以下格式化器:
键名为插件id,键值为label
Array
(
[comment_username] => 作者名
[comment_default] => Comment list
[comment_permalink] => Comment Permalink
[datetime_custom] => 自定义
[datetime_default] => 默认
[datetime_plain] => 纯文本
[datetime_time_ago] => 以前
[file_link] => File link
[file_audio] => Audio
[file_extension] => 文件扩展名
[file_filemime] => File MIME
[file_size] => 文件大小
[file_uri] => File URI
[file_video] => Video
[file_default] => 通用文件
[file_rss_enclosure] => RSS enclosure
[file_table] => 文件表格
[file_url_plain] => 文件链接
[image] => 图像
[image_url] => URL to image
[link] => 链接
[link_separate] => Separate link text and URL
[list_default] => 默认
[list_key] => 键
[entity_reference_rss_category] => RSS 分类
[text_default] => 默认
[text_summary_or_trimmed] => 总结摘要
[text_trimmed] => 切边的
[author] => 作者
[user_name] => 用户名
[basic_string] => 纯文本
[boolean] => 布尔值
[number_decimal] => 默认
[entity_reference_entity_view] => 输出的实体
[entity_reference_entity_id] => 实体ID
[entity_reference_label] => 标签
[number_integer] => 默认
[language] => 语言
[email_mailto] => 电子邮件
[number_unformatted] => 未格式化的
[string] => 纯文本
[timestamp_ago] => 以前
[timestamp] => 默认
[uri_link] => 到URI的链接
)
补充:
1、在表单控件基类中,派发了一些钩子,如:
field_widget_form
field_widget_multivalue_form
这些钩子可以对字段条目进行表单渲染数组的修改,相比之下格式化器基类相对简单,并没有派发什么钩子,但模块依然可以通过显示对象中派发的钩子进行修改,如果自定义格式化器,可在子类中自行派发钩子