云客Drupal8源码分析之渲染数组(render array)

从本质上讲现代所有的web软件系统中都用到了渲染数组,只不过在drupal世界里明确给了它这个名字:“渲染数组”。

如果你使用过模板引擎,那么会很熟悉它,要使模板引擎工作,那么需要给它传入一些变量,这些变量将决定模板里面对应变量的显示,传入的这些变量往往是以数组的方式传入,键名代表模板里面的变量名,键值代表变量值,这样的数组就叫做渲染数组,比如著名的php小型框架CodeIgniter(简称CI框架)中的经典用法就是这样:

 

$this->load->view('show_news', $data);

其中参数show_news指定网页模板,参数$data就是程序中准备好的一个数组,包含了传递给模板的变量

 

可以说渲染数组是数据系统和显示系统的一个桥梁,在数据系统中程序计算出需要给用户的数据,他们全部放置到渲染数组中,显示系统仅面向渲染数组这一个接口工作,这样一来系统和显示之间解耦了,许多事情变得简单,这里的显示是广义的,它也包括“显示”成json或xml给其他系统“看”。

由此可见渲染数组的重要性,相比CI框架等轻量级系统,drupal中的渲染数组则更上一层楼,它包含更多的功能,在drupal中渲染数组不仅包含传递给模板的变量,还包含缓存数据,附件数据,在不用模板的情况下如何进行渲染的数据,下面来看一看drupal的渲染数组:

在drupal中控制器只有三种行为:返回响应对象、返回渲染数组、抛出异常。如果返回的是渲染数组,将派发视图事件,此事件将触发渲染系统,渲染系统启动渲染器对渲染数组进行渲染,在这个过程中渲染器根据渲染数组包含的信息视情况启动主题子系统,也可能直接进行渲染,最后输出内容。渲染数组在整个系统处理流程中大多被以引用传递给各种处理函数或方法,以便于模块等对渲染数组进行修改。

先给出一个简单的列子,看看渲染数组长什么样:

 

$page = [
  '#type' => 'page',
  'content' => [
    'system_main' => […],
    'another_block' => […],
    '#sorted' => TRUE,
  ],
  'sidebar_first' => [
    …
  ],
];

在drupal中“渲染”是什么意思?就是将一个结构化的数组(render array)转换成某格式的字符串,比如html字符串(页面),这个结构化的数组被称为渲染数组。
渲染数组是一个有许多层级的关联嵌套数组,其子元素分为两大类:以#开头的键名表示属性,用于指示怎么渲染及缓存等等,没有以#开头的任意其他命名的元素表示children(理解为子渲染元素、下一层级元素、子内容,html元素是嵌套的),在渲染数组中children子元素必是一个数组类型,他代表下一层级的渲染数组。
属性名是特定的,子内容名是灵活的,它可以是代表分区的变量名(分区概念见后续主题)
在脑海中可以这样理解:渲染数组就像DOM在drupal世界中的体现,从上层元素到下层元素逐级嵌套,形成一棵渲染树

 

 

 

看看常用的渲染数组属性,根据它的作用体会渲染数组怎么工作:

 

#type 可选,但通常均有设置,指定元素类型,并非是指html元素类型,是更高一级的抽象,设置它的目的是为了向渲染数组附加该类型默认的渲染属性,详见本系列的渲染类型主题,不指定或指定的元素类型不存在将不附加任何值,见下文的元素类型。
#theme 可选,定义一个主题钩子回调,这个回调将调用主题模块对渲染数组进行解析转变为html字符串,通常是使用模板引擎,传递给模板的变量可以包含在渲染数组中并用#开头,比如模板中变量名为yunke,那么渲染数组键名为#yunke
#markup 可选,渲染数组直接提供的html标签字符串,可以是嵌套的html片段,值表现为字符串类型或Markup对象,一旦被标记为Markup对象则认为它是安全的(经过转义的),一般使用#type或#theme代替这个选项,这样可以让主题自定义标签
#plain_text 可选,是一个未经过转义的html字符串文本,如果设置,它将被转义处理,并替换#markup的值
#allowed_tags 可选,如果#markup的值不是Markup对象而是一个字符串,则需要进行转义并转换成Markup对象,此项表示此过程中允许使用的标签,如果不设置默认使用Drupal\Component\Utility\Xss::getAdminTagList()返回的标签,此项设置对#plain_text没有影响
#access_callback  可选,访问检查回调,和控制器定义方法相同
#access  可选,如果设置此项则忽略#access_callback项,访问检查,值可以是AccessResultInterface类型,也可以是布尔类型,如果不被允许则不会渲染数组,返回空字符串
#printed  可选,布尔值,标记是否已经被处理成了html字符串内容,渲染时使用empty判断此属性,如果为非空值这不会进行渲染,返回空字符串
#defaults_loaded 可选,指示是否已经向本渲染数组附加合并了对应元素类型的默认渲染属性,此值为空则加载#type所指元素类型的默认渲染属性
#lazy_builder 可选,数组值,必须有且只有两个元素,一个回调,一个回调的参数,参数只能是NULL或者标量类型,当指定这个选项时不能够指定children元素及'#cache','#create_placeholder','#weight','#printed'之外的属性,它们应该在回调中产生
#lazy_builder_built 可选,布尔值,内部使用,指示元素由#lazy_builder回调产生
#create_placeholder 可选,布尔值,是否自动创建占位符,当指定此选项时,#lazy_builder必须存在
#pre_render 可选,值是一个回调构成的数组,这些回调函数能在渲染前修改渲染数组,比如重新排序、修改、删除某部分等,回调可以设置添加#printed,如果调用后数组中设置了#printed,其值不为空将终止渲染
#post_render 可选,一个由回调构成的数组,在渲染子元素并追加到#markup之后,依次调用这些回调,它们可以修改之前的渲染输出,某些方面有点类似于#theme_wrappers,只是不使用主题子系统
#states 可选,代表元素在浏览器中的状态信息,选中、隐藏等等,详细见函数:core\includes\common.inc : drupal_process_states
#sorted 可选,布尔值,代表子元素是否已经被排序过
#weight 可选,代表子元素在父元素中排序的权重,支持三位小数精度
#attributes 可选,代表元素的html属性,其值为一个数组,此数组的键名为属性名,键值为渲染数组对应于html属性名的属性元素的值,详见\core\lib\Drupal\Core\Render\Element.php中的setAttributes方法
#children 内部使用,表示被渲染后的子元素,字符串类型或者Markup对象
#theme_wrappers 一个由主题钩子回调组成的数组,当子元素被渲染赋值给#children后,依次调用它们,它们使用主题子系统,常用于给子元素的html内容加上外层包装,这个选项用的比较少,通常使用#theme
#render_children 可选,程序内部使用,如果设置,则不会使用#theme_wrappers和#theme中的主题钩子函数,防止无限递归
#prefix 可选,html内容字符串,经过安全处理后加到子元素前面
#suffix 可选,html内容字符串,经过安全处理后加到子元素后面
#title 可选,html页面标题,如果未设定,将使用标题解析器从路由设置等地方计算出标题,
#cache_properties 可选,在进行缓存的时候将被保留的属性或子内容,默认值只缓存渲染数组中的#markup、#attached、#cache属性,如果保留子内容也仅是缓存子内容的#markup属性

由于每种元素类型都有自己的属性,因此在渲染数组中根据不同元素类型就有不同的以#开头的键名(属性),此外传递给模板的变量也是以#加变量名储存在渲染数组中,所以渲染数组的属性数量庞大,在这里仅例举了最常见的属性
更多属性见:https://api.drupal.org/api/drupal/developer%21topics%21forms_api_reference.html/7.x
渲染数组被Form API 和 Render API共用,但数组中有些表单元素的属性只对于表单API有意义,将这样的渲染数组用到渲染API时需要通过FormBuilder转换,否则可能有不能预料的结果。

特别说明:
#attached 可选,代表渲染元素的附件,渲染数组各个层级的附件会被渲染上下文对象跟踪收集,有效子元素有:
   'library':css或js库,统称资源库
   'drupalSettings':前端js设置 (JavaScript settings)
   'feed':内容聚合RSS feeds
   'html_head':在HTML <head>元素里面的标签
   'html_head_link':在HTML <head>里面的<link>标签
   'http_header':HTTP响应头和状态码
   'placeholders':见下文的占位符介绍。注意:替换值本身也可以是渲染数组,此时它被渲染后再去替换占位符,缓存标签照样冒泡影响被替换的元素

#cache 可选,指定渲染数组的缓存参数,drupal能够通过此设置分别缓存渲染数组各个层级的渲染输出,这样减少计算量,提高性能,各层级的缓存数据会向上冒泡以决定上一层级的缓存性质,有效子键名为:
   'keys':用于指定缓存id,在内部产生这个id还会结合contexts的值,渲染数组要缓存时才设置这个项
   'contexts':指定缓存上下文
   'tags':指定缓存标签
   'max-age':指定缓存最大时长,秒为单位,是最大时长,不是到期时间,永不过期为-1,默认为永久
   'bin':指定缓存储存位置,如不指定默认为render,默认放在数据库的render缓存表中

 

 

 

在渲染数组中#cache和#attached特别重要,渲染过程中他们会从渲染树中逐级冒泡,直到文档根元素,这样才能保证所有元素的缓存正确,以及收集所有需要用到的js、css,为此系统建立了一个叫做渲染上下文的对象来跟踪他们, 渲染上下文会一直跟踪渲染递归过程,收集整个页面需要的所有附件,并判断渲染数组各个层级的可缓存性

 

为什么渲染数组需要渲染占位符:
1:有时候页面是一模一样的,仅仅某一元素不一样,比如一个页面仅显示的用户名不一样,如果有一万的用户岂不是要缓存一万份?这是不划算的,也不合理
2:有些内容变化频率特别快,几秒钟就变化,甚至更短,这个时候更新缓存的成本相对就大,使用缓存不划算
3:有些内容很快就过期,缓存没什么价值,还会浪费缓存执行成本
综上所述三条,你会发现他们就对应缓存三要素:上下文、标签、时间,由于这些原因导致缓存不划算花费过大,所以设置占位符机制,将这些缓存不划算的内容和划算的内容隔离开,仅缓存划算的内容,从缓存取出数据后再根据占位符信息将缓存不划算的内容实时替换进去,可以看出渲染数组的占位符机制是非常有用的。
判断页面某部分缓存不划算而运用占位符的过程叫做“Auto-placeholdering”(自动占位过程),它是怎么自动的呢?就得依据渲染配置,容器参数中的renderer.config就是指定此配置默认值如下:

 

[renderer.config] => Array
                (
                    [required_cache_contexts] => Array
                        (
                            [0] => languages:language_interface
                            [1] => theme
                            [2] => user.permissions
                        )

                    [auto_placeholder_conditions] => Array
                        (
                            [max-age] => 0
                            [contexts] => Array
                                (
                                    [0] => session
                                    [1] => user
                                )

                            [tags] => Array
                                (
                                )
                        )

可以看到基本没什么问题的设置均被指定为默认配置了,更多的内容就要依据站点的实际情况,这也是站点优化的一个主要内容。
有#lazy_builder的元素会被运用自动占位机制,因为#lazy_builder代表延迟构建,往往不是靠渲染数组来构建页面,而是使用一个回调
注意占位符会在渲染最后一刻才执行替换,这个机制允许系统缓存许多碎片数据。

元素类型:
在drupal中元素类型 "element types"本质上是一个预包装好的默认渲染数组,渲染数组指定了元素类型将附加这些默认渲染属性,模块可以定义元素类型,创建一个实现了ElementInterface的RenderElement插件即可,关于此块内容请看本系列的渲染元素类型主题

参考链接:
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21theme.api.php/group/theme_render/8.2.x
https://www.drupal.org/docs/8/api/render-api

 

 

 

drupal的渲染系统内容很多,需要分多个主题进行介绍,这里先介绍最最重要的渲染数组,其他内容请看后续主题
有疑问没有关系,等学习完后续主题后便能云开雾散

 

我是云客,【云游天下,做客四方】欢迎转载,但须注明出处,讨论请加qq群203286137

 

 

Drupal 版本: