102. 实体视图构建器EntityViewBuilder

实体视图显示和表单显示在实现上有许多相似之处,许多类都共用了相同基类,有许多概念和知识已经在本系列的实体表单显示主题中介绍过,如显示模式及其定义等,本篇不再重复介绍,将假设读者已阅读过实体表单显示主题;视图这个词可能会让人感觉生涩,可以将其理解为查看、显示,实体视图构建就是产生实体的查看页面或数据。

 

 

从节点视图说起:

drupal中最常见的是节点查看,网址如:/node/1,查看节点的路由名为:

  entity.node.canonical

此路由在以下方法中动态定义:

  \Drupal\node\Entity\NodeRouteProvider::getRoutes

对应的路由控制器:\Drupal\node\Controller\NodeViewController::view

该控制器继承自以下类:

   \Drupal\Core\Entity\Controller\EntityViewController

该类是一个通用实体视图控制器类,如果路由定义中没有设置控制器,只设置了“_entity_view,那么就会直接使用该类的view方法做控制器(该方法的参数已在路由系统中得到转化,请见参数转换相关主题),“_entity_view”在以下路由增强器中进行处理:

  \Drupal\Core\Entity\Enhancer\EntityRouteEnhancer::enhanceEntityView

以上“_entity_view”的值是如下格式:

  实体类型id.视图模式名

其中实体类型id是必不可少的,和视图模式以点号分隔,如果省略点号及视图模式名,则默认使用全文视图模式:“full”,但在默认情况下,full模式并没有开启,此时将回退使用default视图模式,如果开启了则full模式优先,注意这和默认表单模式直接采用“default”不同

这样的用法可参见用户实体的entity.user.canonical路由

 

由以上视图控制器类可见真正产生用于显示实体的渲染数组的是实体视图构建器,见下。

 

视图模式:

显示模式分为表单模式和视图模式,详见本系列实体表单显示主题,和表单模式一样,并不是所有实体类型都能运用视图模式,条件是实体类型定义中设置了根键field_ui_base_route且不能为空,且定义了视图构建器。详见以下类:

  \Drupal\field_ui\Controller\EntityDisplayModeController

这意味着只有内容实体类型才能设置显示模式,默认安装中有六个实体类型可以设置,实体类型id如下:

block_content(自定义区块)
comment(评论)
contact_message(联系信息)
node(内容)
taxonomy_term(分类术语)
user(用户)
他们都是内容实体类型,在drupal中内容实体类型继承自可字段化实体类型,视图显示模式即是用于管理哪些字段被显示及如何显示

 

实体视图构建器:

实体视图构建器在实体释文中定义,用于构建显示实体的渲染数组,获取方法如下:

$viewBuilder=\Drupal::entityTypeManager()->getViewBuilder($entityTypeID);

获取后产生显示渲染数组:

$renderArr =$viewBuilder->view($entity, $view_mode);

要显示一个节点实体,可以在控制器中执行以下代码:

   $node = \Drupal::entityTypeManager()->getStorage('node')->load(34);
   $viewBuilder=\Drupal::entityTypeManager()->getViewBuilder('node');
   return $renderArr =$viewBuilder->view($node);

和表单不同的是也可以同时渲染多个实体:

   $nodes[] = \Drupal::entityTypeManager()->getStorage('node')->load(34);
   $nodes[] = \Drupal::entityTypeManager()->getStorage('node')->load(37);
   $viewBuilder=\Drupal::entityTypeManager()->getViewBuilder('node');
   return $renderArr =$viewBuilder->viewMultiple($nodes);

视图构建器是一种实体处理器,定义在实体释文处理器键下的“view_builder”键中,按照处理器流程实例化,即如果实现了处理器接口,将从以下静态方法得到实例对象:

  \Drupal\Core\Entity\EntityHandlerInterface::createInstance

视图构建器需要实现接口:

  \Drupal\Core\Entity\EntityViewBuilderInterface

系统提供了默认实体视图构建器基类:

  \Drupal\Core\Entity\EntityViewBuilder

实体的视图构建器通常需要继承她,如节点视图构建器:

  \Drupal\node\NodeViewBuilder

 

实体视图构建概述:

也就是显示实体的渲染数组构建,实体视图构建器既可以一次构建一个实体的视图,也可以一次构建多个,传入的实体对象必须是相同的实体类型,但bundle可以不同,换句话说视图构建器是针对特定实体类型工作的,在构建过程中模块可以通过钩子为每一个实体指定特定的视图模式;视图构建器除了针对实体进行视图构建外,还可以针对实体字段以及字段中某个值单独构建,这在进行多实体类型组合显示时特别有用。

 

实体视图构建器在构建实现流程上,利用了渲染管道中的“#pre_render”回调,以此回调运行为时间点,可将构造视为分两步完成,第一步仅包含必要的基本数据,此时称为默认渲染数组,或基本渲染数组,该概念名称将在下文多次提及,此时还未进行实体字段渲染,控制器返回的正是该数组,第二步是在控制器返回后,系统在渲染器中执行“#pre_render”回调完成的,这期间才构造实体字段的渲染数组,字段渲染数组由实体视图显示对象构造,本系列将单独讲解,回调运行完成后才得到完整的实体渲染数组;整个构建过程中将派发多个钩子以便模块可以控制渲染结果。

由上可见实体视图构建器可以输出实体列表页,这和drupal中另一个概念容易混淆:列表构建器,后者也是一种实体处理器,定义在实体释文处理器键下的“list_builder”中,路由可用“_entity_list”,主要用于实体管理目的,而非实体显示,可在控制器中执行以下代码看一看列表构建器产生的输出:

return $this->entityManager()->getListBuilder('node')->render();

她虽然也显示了一些实体字段信息,但和视图是不一样的,如附带了操作链接,本系列将单独讲解。

 

实体视图渲染数组结构

单个实体的视图渲染数组结构如下(基本数组指控制器返回的数组):

#{$entityTypeId}键值为实体对象,键名如“#node”,在基本数组中存在

#view_mode实际用于渲染的视图模式,已经过钩子调整,在基本数组中存在

#cache缓存属性,使用实体对象的缓存属性,但添加了缓存标签:$entityTypeId . '_view',通过该标签可失效所有视图缓存;缓存属性有些情况下可能不存在,比如在节点预览模式中的实体显示,如果可缓存则会在其下设置keys键,利用渲染缓存提高性能

#theme 如果存在以实体类型id作为名字的主题钩子,将在基本数组中添加该键,值为实体类型id

#weight显示排序序号

#contextual_links值为上下文链接,仅在完整数组中存在(基本数组中没有)

字段名:由视图显示对象构建,仅在完整数组中存在(基本数组中没有)

 

查看实体的完整视图渲染数组可在控制器中执行以下代码:

批量查看:

        $node[] = \Drupal::entityTypeManager()->getStorage('node')->load(37);
        $viewBuilder = \Drupal::entityTypeManager()->getViewBuilder('node');
        $renderArr = $viewBuilder->viewMultiple($node);
        unset($renderArr['#pre_render']);
        $renderArr = $viewBuilder->buildMultiple($renderArr);

查看单个实体:

        $node = \Drupal::entityTypeManager()->getStorage('node')->load(34);
        $viewBuilder=\Drupal::entityTypeManager()->getViewBuilder('node');
        $renderArr =$viewBuilder->view($node);
        unset($renderArr['#pre_render']);
        $renderArr = $viewBuilder->build($renderArr);

提示:如果你不是使用断点调试工具查看$renderArr而是直接打印,很可能会耗尽内存而无法显示,可安装“yunke_help”模块,用其提供的打印函数查看

 

默认实体视图构建器基类方法解释:

public function viewMultiple(array $entities = [], $view_mode = 'full', $langcode = NULL)

渲染多个实体,多个实体以数组方式传入,按传入的数组顺序渲染,并以此顺序返回渲染数组,实体会以传入的语言优先显示,其次使用内容语言协商的结果,针对每一个实体系统派发以下两个修改钩子:

  $entityType . '_build_defaults'

  'entity_build_defaults'

第一个针对具体的实体类型,第二个针对所有实体类型,默认没有模块实现,形式如下:

  hook_entity_build_defaults_alter(array &$build, EntityInterface $entity, $view_mode)

其中$build为显示实体的基本渲染数组,仅包含基本的共用数据(见上),此时尚未渲染字段(该操作在“#pre_render”回调中进行),派发钩子的目的便是为了模块可以向基本数组添加数据;$entity为实体对象,$view_mode为原始请求的视图模式,注意这不一定是实际被使用的视图模式,因为在实际渲染过程中模块可以修改她,后者保存在$buil['#view_mode']

 

protected function isViewModeCacheable($view_mode)

判断某个视图模式的渲染结果是否能被缓存,该值最初来源于“entity_view_mode”实体,如节点全文视图模式的查看代码如下:

\Drupal::entityTypeManager()->getStorage('entity_view_mode')->load("node.full")->get("cache");

这原始来自于以下变量:

  \Drupal\Core\Entity\EntityDisplayModeBase::$cache

默认为true能缓存,在后台管理配置页面没有UI设置接口,这是一个需要改进的地方,但模块可以在“entity_view_mode”实体保存时通过相关钩子去改变该值,也可以在使用过程中通过以下修改钩子去改变:

  entity_view_mode_info

但须注意:该修改钩子是在实体显示知识库中派发的,如果模块没有通过知识库去获取值,将不起作用,在规范情况下模块应当通过知识库获取

 

protected function getBuildDefaults(EntityInterface $entity, $view_mode)

返回实体最初的默认(基本)渲染数组,后续可被钩子修改,并不包含字段的渲染数据,在构造该渲染数组前,会派发以下修改钩子以允许模块改变单个实体的视图模式:

  'entity_view_mode'

钩子函数如下:

  hook_entity_view_mode_alter(&$view_mode, EntityInterface $entity, $context)

其中$view_mode为原始请求的视图模式(字符串值),$entity为实体对象,$context派发时为一个空数组;该钩子意味着在显示实体列表时,她们可以各自使用不同的显示模式,这带来极大的控制细腻度,比如node实体类型,相同bundle都可以采用不同模式来显示,甚至依据实体的某个属性值来进行不同显示;该方法返回的基本渲染数组包含实体对象、实际使用的实体模式、缓存属性、可选的主题钩子。

运行时主题注册表是主题注册表的优化(见本系列主题钩子注册),还没有被请求的主题钩子在其中存在,但值为NULL,其has方法将返回true;也就是说只要注册了以实体类型id作为名字的主题钩子将添加#theme键,反之不添加。

 

public function view(EntityInterface $entity, $view_mode = 'full', $langcode = NULL)

public function build(array $build)

这两个方法一起渲染单个实体对象,注意设置#pre_render的微妙关系

 

public function buildMultiple(array $build_list)

将基本默认渲染数组转变为完整渲染数组,主要工作是渲染字段和派发钩子;在渲染管道的#pre_render回调中执行,参数是viewMultiple方法返回的结果(如果有延迟构建或元素类型补充则可能不同),因此须用以下代码来返回子元素的键名:

  $children = Element::children($build_list);

这将排除#号开始的属性键名;每一个子元素对应一个实体的基本渲染数组,其最重要的有两个键名:

  #{$entityTypeId}:键值为实体对象

  #view_mode:实际用于渲染的视图模式

在该方法中每个实体对象在其字段渲染完成后将派发以下两个钩子:

  {$entityTypeId}_view(如node_view

  entity_view

参数为:[&$build_list[$key], $entity, $display, $view_mode],也就是:

实体的渲染数组、实体对象、实体视图显示对象、视图模式

钩子运行后将添加上下文链接,运行alterBuild方法,排序字段,最后将运行这两个钩子的修改钩子,参数和顺序不变,仅少了$view_mode参数

 

public function buildComponents(array &$build, array $entities, array $displays, $view_mode)

调用视图显示对象构造实体字段的渲染数组,经过该方法后实体字段的渲染数组已经产生,参数解释如下:

$buildviewMultiple方法返回的结果(未进行字段渲染的基本渲染数组列表),键名为渲染序号

$entities为被渲染的实体对象,键名为渲染序号,有相同的视图模式和实体类型id,但bundle可以不同

$displays为对应实体将使用的视图显示对象数组,键名为bundle名,有相同的原始请求视图模式

$view_mode为使用的视图模式,字符串

该方法会派发以下钩子:

  entity_prepare_view

钩子原型:

  hook_entity_prepare_view($entity_type_id, array $entities, array $displays, $view_mode)

该钩子在实体字段被渲染前执行,主要目的是让模块可以通过她修改实体对象,当然也可以修改显示对象,但往往修改显示对象使用修改钩子entity_view_display更为合适

注意:基本渲染数组将采用“+”操作和视图显示对象构造的实体字段的渲染数组合并,基本渲染数组优先级更高,这意味着模块可以通过viewMultiple方法中的钩子修改基本数组以强制显示某字段为特定内容

 

在节点实体的该方法中对语言字段进行了固定显示,在显示配置中设置的格式化器将无效,并为伪字段links添加了内容:如摘要视图模式中的“阅读更多”链接,模块可以通过修改钩子“node_links”添加更多内容,该修改钩子如下:

   hook_node_links_alter(array &$links, NodeInterface $entity, array &$context)

这属于节点实体专用修改钩子,其他实体类型不可使用,参数如下:

$links:为链接渲染数组

$entity:节点实体对象

$context:为数组['view_mode' => $view_mode,'langcode' => $langcode,];

添加的这些链接内容将在视图显示配置中所示位置出现

 

protected function addContextualLinks(array &$build, EntityInterface $entity)

为实体的渲染数组添加上下文链接,上下文链接在前端页面中以link标签的方式提供与本实体相关的一些链接或信息,如:

<link rel="canonical" href="/zh-hans/node/37" />

这些上下文链接可以被前端js程序使用,也可供搜索引擎识别

 

protected function alterBuild(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode)

实体的单个渲染数组构建完成后运行,默认没有实现

 

public function resetCache(array $entities = NULL)

失效所有和传入实体的视图相关的缓存,包括列表视图,参数为实体对象数组,如果没有传递参数,将失效这个实体类型的所有视图

 

public function viewField(FieldItemListInterface $items, $display_options = [])

单独渲染实体的一个字段对象,当多个实体的字段组合显示时特别有用,如视图模块,参数$items是实体的某个字段对象,来自$entity->get($fieldName),参数$display_options指示如何渲染,分两种情况,如果是一个字符串值,将当做视图模式看待,按其指示的方法渲染(如不存在将回退到默认视图模式),如果是一个数组,将当做显示对象中该字段组件配置数据看待,此时将新建一个临时的显示对象,模式名为默认的'_custom',并将执行:

$display->setComponent($field_name, $display_options);

可通过以下代码查看$display_options选项的数组内容:

 $display =\Drupal::entityTypeManager()->getStorage('entity_view_display')->load("node.article.default");
 $display_options=$display->getComponent('body');
 print_r($display_options);die;

键名解释如下:

label:标签label的位置信息,inlineabovehidden之一,默认above

type:字符串值,格式化器的插件id

settings:格式化器的配置数据,默认为格式化器指定的默认值

weight:渲染输出的权重值,默认为0

region:分区信息,通常为content代表可见

third_party_settings:第三方模块的设置,键名为模块名

 

public function viewFieldItem(FieldItemInterface $item, $display = [])

单独渲染实体字段对象中的一条值,当多个实体的字段组合显示时特别有用,如视图模块,参数$item是字段对象中的一条值,如$entity->get($fieldName)->get(0),参数$displayviewField方法的$display_options

 

protected function getSingleFieldDisplay($entity, $field_name, $display_options)

获取实体某个字段对象的显示对象,参数$display_optionsviewField方法的$display_options相同,注意当其为字符串值时,返回的显示对象中其他字段的显示配置数据已被删除

 

构建一个实体列表页:

在本篇最后云客为读者提供一个实用示例程序,可借助实体查询API的强大功能显示所需的任何内容,请在控制器中执行:

        $limit = 3; //每页显示数量 值为false将全部显示
        $storage = \Drupal::entityTypeManager()->getStorage('node');
        $query = $storage->getQuery();//获取实体查询对象
        $query->sort('changed', 'DESC');//按时间倒序显示,最新的在最前
        $query->currentRevision(); //仅选择每个实体的当前版本
        $query->condition('status', \Drupal\node\NodeInterface::PUBLISHED);//仅显示发布的内容
        //显示什么取决于这里设置的条件,更多请参考本系列实体查询主题
        if ($limit) {
            $query->pager($limit);
        }
        $entity_ids = $query->execute();
        $entities = $storage->loadMultiple($entity_ids);
        $viewBuilder = \Drupal::entityTypeManager()->getViewBuilder('node');
        $build['nodes'] = $viewBuilder->viewMultiple($entities, 'teaser'); //构建摘要列表视图
        if ($limit) {
            $build['pager'] = [
                '#type' => 'pager', //构建分页器
            ];
        }
        return $build;

 

 

补充:

1、视图构建器并不考虑权限问题,这需要调用者考虑

2、实体视图显示对象及格式化器将单独讲解,在视图构建器中的使用入口如下:

构建多个实体的字段渲染数组:

$display->buildMultiple($entities);

该方法渲染多个有相同实体类型、bundle、视图模式的实体,保持传入键名不变

构建单个实体的字段渲染数组:

$display->build($entity);

传入的实体必须属于显示对象对应的实体类型和budle

 

本书共115小节:

评论 (写第一个评论)