“中国要复兴、富强,必须在开源软件领域起到主导作用,为了国家安全和人类发展,责无旁贷,我们须为此而奋斗”——By:云客
在本系列的视图使用篇中已经介绍了视图是什么,以及站点管理员如何去使用她,这里我们开始学习视图的原理及开发,学完后应可以向视图系统提供自定义数据、各种插件、调整视图构建的某过程等。完整的视图功能由以下两模块共同完成:
views模块:
是视图的核心模块,执行视图核心功能,包括构建查询、处理输出等,她依赖过滤器filter模块
views_ui模块:
提供管理员控制视图的用户接口,即为视图提供管理操作界面,没有该模块并不影响视图的功能,已配置的视图将照常显示,有些用户甚至为了优化一点性能而在配置好视图后卸载该模块,这种做法在视图功能上并没有什么影响,但卸载该模块后,“管理-结构-视图”菜单将消失,用户将无法从管理后台看到有哪些视图,也无法配置她们,只能在程序层面进行配置,该模块就像套在视图模块外的一个壳,提供图形化界面供用户管理视图,类似linux系统的UI界面和核心的关系
SQL关键知识点回顾:
开始了解视图系统前,我们先回顾一下数据库查询的相关重点内容
完整SELECT查询结构及各部分顺序:
SELECT DISTINCT(可选DISTINCT)
(TablesAlias.field 或expression AS FieldAlias)多个用逗号分隔
FROM Tables或(子查询) TablesAlias
INNER或LEFT OUTER或RIGHT OUTER JOIN Tables或(子查询) TablesAlias ON 条件
前条可多个用空格分隔或换行
WHERE 条件
GROUP BY 分组字段1 , 分组字段2 , …
HAVING 聚合查询条件
UNION ALL(可选ALL) (子查询)
前条可多个用空格分隔或换行
ORDER BY (字段 DESC降序或ASC升序)可多个用逗号分隔
LIMIT length OFFSET start
FOR UPDATE
结联语句JOIN:
用于将两个表通过某列进行关联,即把两个表合成为一个表来查询,其中FROM关键词后面的表称为左表,INNER关键词后面的表称为右表,左右表有如下结联查询方式:
INNER JOIN (内连接,其中INNER关键字可以省略,即同JOIN):
仅在表中至少有一个匹配时,才返回行,如果没有匹配则无查询结果,仅返回左右表中有匹配的那些行,当左表中的行在右表中有多个匹配时,左表的行将重复
LEFT JOIN(左连接):
返回左表中所有的行,即使其在右表中没有匹配,当左表某行在右表有多个匹配时,左表行将重复,这是大多数时候使用的方式,也是drupal视图的默认结联方式
RIGHT JOIN(右连接):
和左连接相反,返回右表中所有的行,即使其在左表中没有匹配,当右表某行在左表有多个匹配时,右表行将重复
FULL JOIN(全连接):
左右表中的行都会被返回,有匹配的行将结联成为一行
注意:
1、左右表可以是同一个表(取不同别名)
2、LEFT JOIN、RIGHT JOIN、FULL JOIN是LEFT OUTER JOIN、RIGHT OUTER JOIN、FULL OUTER JOIN的简写
3、A left join B 等价B right join A
4、MySQL不支持全连接(full join)
分组语句GROUP BY:
分组关键词“GROUP BY”用于以某个列(或某些列)对结果集进行分组,分组的目的通常是为了支持聚合函数,即对每个组应用聚合函数
视图核心辅助类:
\Drupal\views\Views
请先了解一下该辅助类,其提供视图系统常用辅助方法,对于视图模块而言,相当于全局的“\Drupal”类和系统的关系
视图数据:
视图系统用于查询、显示系统中的数据,可是系统中的数据有些以实体方式储存(占大多数,在应用层看到的是实体结构,其数据库表结构是从实体结构映射而来),有些则直接以数据库表储存(比如系统日志),为了解决储存上的差异,需要为视图系统提供一个统一的数据界面,或者叫储存界面、结构界面。
为此系统提供了“视图数据服务”:
服务id:views.views_data
类:Drupal\views\ViewsData
该服务提供了数据描述抽象层,通过该层屏蔽了数据的储存差异,统一描述了要显示的数据,即元数据(关于数据的数据),如表、字段、描述、数据类型等,在具体层面即是提供了一个描述要显示的数据的元数据数组,即:“视图数据”数组,用来向视图系统描述提供视图内容的数据库表和字段的信息;那么视图数据是怎么来的呢?其是本服务通过派发钩子“views_data”及其修改钩而来,这里以官方提供的钩子说明为基础,外加云客的补充来介绍“视图数据”数组的格式,如下所示(其中提到的各类插件见下文):
function hook_views_data()
{
/**
* 举个列子来说明如何实现钩子:hook_views_data() ,假设提供给视图展示的数据的数据库表如下:
* CREATE TABLE example_table (
* nid INT(11) NOT NULL COMMENT '主键: {node}.nid.',
* plain_text_field VARCHAR(32) COMMENT '一个原始文本字段',
* numeric_field INT(11) COMMENT '一个整型数组字段',
* boolean_field INT(1) COMMENT '一个布尔字段on/off',
* timestamp_field INT(8) COMMENT '一个时间戳字段',
* langcode VARCHAR(12) COMMENT '语言代码字段',
* PRIMARY KEY(nid)
* );
*/
// 初始化视图数据数组,构造后即返回该数组
$data = [];
// 第一级键名是视图使用的表名,通常就是真实的数据库表名,即hook_schema()中的表名,
// 但也可以是非真实存在的表名,即虚拟表名,比如视图模块提供的'views'表名,其用于提供特殊字段
// 如果是虚拟表名,但其确实对应着真实使用的数据库表名,那么该虚拟表名将作为真实数据库表名的别名进行查询
// 在其中如何指定真实表明见下文描述
$data['example_table'] = [];
// 键名'table'用于定义表本身的元数据
$data['example_table']['table'] = [];
//在'table'数组中,'group'键名对应的值是一个字符串翻译对象,表示该表中所有字段所属的组名
//在视图UI中也用作字段、过滤器等的前缀,在添加她们时,可以用该组属性进行过滤
$data['example_table']['table']['group'] = t('Example table');
// 在'table'数组中, 键名'provider'指示提供本表的模块名,这用来向系统说明依赖关系
// 如果没有设置,那么会被系统自动设置
$data['example_table']['table']['provider'] = 'example_module';
// 在视图中有些表是“基本表”,用作查询起始点,新建视图时需要选择基本表,非基本表需要通过关联引入查询
// 要把表定义成基本表需设置'base'键名,如下:
$data['example_table']['table']['base'] = [
// 指明主键字段(primary field),在其他需要和本表关联的表定义中,如无指定关联字段,则默认使用该项指定的值
'field' => 'nid',
// 在视图UI中新建视图时显示的Label
'title' => t('Example table'),
// 在视图UI中显示的长描述,必须指定
'help' => t('Example table contains example content and can be related to nodes.'),
//排序权重
'weight' => -10,
//可选的指定数据库,通常不需要该项,但当数据表位于外部数据库时,就需要该项来指定是哪一个数据库
//详见:\Drupal\Core\Database\Database::getConnection($target, $key)
//该处的值即为$key值,这也是drupal视图系统显示其他系统数据的关键
'database' => '',
'query_id' => 'pluginID', //可选,所用查询插件的id(见后,用于构造数据库查询),默认为‘views_query’
'query metadata' => [],//可选,可用来传递查询元数据信息,这些可供查询标签钩子使用
'access query tag' => 'access_tag',//可选,设置用于访问控制的查询标签,只能传递一个
];
// 有些表和其他表有着隐式的自动关联关系,比如实体的“字段专用表”和“数据表”
// 在视图中实体数据表有效时,字段专用表也应自动有效,而不必管理员指定关联配置
// 要将本表定义成某表的“隐式关联表”,须添加'join'属性,
// 注意:
// 如果隐式关联表和被关联表中的数据不是一对一关系,那么视图会查询出多行
// 如果不需要自动有效,那么不需要设置为隐式关联,而让管理员显式设置关联代替
// 见下文的“relationship”配置项
//
// 隐式关联定义如下:
$data['example_table']['table']['join'] = [
// 本表称为“隐式关联表”,在'join'下是一个或多个“被隐式关联的表”
// 这里以节点实体数据表为例进行定义,将产生类似如下的SQL语句(表别名可能不同):
// ... FROM node_field_data nfd... JOIN example_table et ON et.nid = nfd.nid AND ('extra' 额外条件) ...
// 每当'node_field_data'表有效时,本表也将有效
// 注意:确保提供“隐式关联表”的模块依赖在提供“被隐式关联的表”的模块上
'node_field_data' => [
// node_field_data表中的字段主键,用来执行join关联
// 注意:字段名来自'node_field_data'表的hook_views_data()实现
'left_field' => 'nid',
// 'example_table'表中的外键字段,用来执行join关联,此时'field'是'right_field'的简写
'field' => 'nid',
// 'extra'包含额外的join条件
'extra' => [
0 => [
// 添加一个AND条件到join语句: node_field_data.published = TRUE
// 'left_field'表示左字段,这里即node_field_data表中的字段
'left_field' => 'published',
'value' => TRUE,
],
1 => [
// 添加一个AND条件到join语句: example_table.numeric_field = 1
// 'field'表示右字段,这里即example_table表中的字段.
'field' => 'numeric_field',
'value' => 1,
// 表示值为数值类型,'numeric'的值为true, 在SQL中值不被引号包括
'numeric' => TRUE,
],
2 => [
// 添加一个AND条件到join语句: node_field_data.published <> example_table.boolean_field
'left_field' => 'published',
'field' => 'boolean_field',
// 操作符号 默认为"=".
'operator' => '!=',
],
],
],
];
// 隐式关联可以间接进行,什么意思呢?假设在某个hook_views_data()实现中,
// 已经声明了表“foo”隐式关联到表'node_field_data',
// 那么本表就可以通过表“foo”间接隐式关联到'node_field_data',如下:
$data['example_table']['table']['join']['node_field_data'] = [
// 声明'left_table'是关键,声明后相关左字段'left_field'均是foo中的字段
'left_table' => 'foo',
// 相对于'left_table' 我们也可以声明'right_table',简写为'table',其值默认为本表
'table' => 'example_table',
//此时左字段为foo表中的字段
'left_field' => 'nid',
'field' => 'nid',
// 'extra' 中的左字段也是foo表中的字段
'extra' => [
// 条件类似如下:
// ... AND foo.langcode = example_table.langcode ...
['left_field' => 'langcode', 'field' => 'langcode'],
// 条件类似如下:
// ... AND example_table.numeric_field > 0 ...
['field' => 'numeric_field', 'value' => 0, 'numeric' => TRUE, 'operator' => '>'],
],
];
/**
* 在表数组中,'table'键表示表元数据,其他键代表字段元数据,所用的键名为描述方便这里称为“字段定义键”或“字段键”
* 字段键即字段元数据所用的键名,其通常使用真实的数据库字段名,但也可以是虚拟的,
* 此时如果对应着真实的字段,那么在查询时将作为其别名使用
* 虚拟字段键也可以不对应真实的字段名,可以是更高级的抽象,比如视图系统提供的功能性字段(表“views”中的字段)
* 字段元数据条目中必须包含以下两个元素:
* - title: 一个翻译对象,在视图UI中显示
* - help: 一个翻译对象,在视图UI中显示的字段描述
*
* 字段元数据定义中可选的包含以下元素:
* - relationship: 其值为关联处理器定义,指明该字段可作为关联项关联到其他表,如不能则不要设置该项
* - field: 其值为字段处理器定义,如存在,则指本字段可作为字段被添加,并指明采用哪一个字段处理器插件处理
* - filter: 其值为过滤处理器定义,同上
* - sort: 其值为排序处理器定义,同上
* - argument: 其值为参数处理器定义,同上
* - area: 其值为区域处理器定义,指明本字段可被添加到视图的首、尾、或无结果时的行为区域
*
* 何为可选呢?假设该字段不会被用做排序项,那么就无需设置‘sort’,此时在视图中添加排序字段时将不会出现该字段
*
* 在以上处理器定义中,都可以可选的定义以下项:
* 'group', 'title', 'title short', 'label', 'help', 'real field', 'real table', 'entity type', 'entity field'
* 如果没有定义,那么会依次向上查找字段定义、表定义,以第一个找到的为准
*
* 各处理器以插件方式提供,处理器定义即是给出插件定义数据,其中必选的只有插件ID,即:'id'键
*
* 不同的处理器需要不同的定义数据,详见:\Drupal\views\Plugin\ViewsHandlerManager::getHandler
* 可选的可以给出插件定义的覆写值
*/
// 节点id字段,做关联项用
$data['example_table']['nid'] = [ //这里'nid'即为字段键
'title' => t('Example content'),
'help' => t('Relate example content to the node content'),
// 定义一个到node_field_data表的关联,定义后,如果视图以本表做为基本表,
// 那么可以用本字段添加一个关联到节点数据表,注意这种关联和隐式关联不同,需要管理员添加
// 一旦定义,则双向关联,即在node_field_data表定义中不必定义,也能在节点视图中通过本字段添加到本表的关联
'relationship' => [
// 要关联到的表名,也就是要和本表join的表名
'base' => 'node_field_data',
// 要关联的表的关联字段,如果留空将假设是表中的主键字段
'base field' => 'nid',
// 关联处理器的插件ID
'id' => 'standard',
// 在视图UI中显示的默认关联label,与前面的title、help不同
// 这在添加关联之后作为标题显示,而前两者在添加对话框中显示
'label' => t('Example node'),
// 可选,同样可以指定额外条件
'extra' => [],
// 可选,指定系统在底层使用的join处理器插件id,默认为'standard'
'join_id' => 'standard',
],
];
//以上只能为nid字段设置一个关联项,如果有多个怎么办呢?
//可以通过虚拟字段键名(a dummy field key)来处理
//如下即可,这里'unique_dummy_nid'即是一个虚拟字段,表中并不真实存在
$data['example_table']['unique_dummy_nid'] = [
'title' => t('foo content'),
'help' => t('Relate example content to the foo content'),
// 定义一个到foo_table表的关联,定义后,如果视图以本表做为基本表,
// 那么可以用本字段添加一个关联到foo数据表,同样是双向的
// 再次说明这种关联和隐式关联不同,需要管理员添加
'relationship' => [
// 要关联到的表名,也就是要join进本表的表名
'base' => 'foo_table',
// 要关联的表的关联字段
'base field' => 'fid',
// 因为'unique_dummy_nid'是虚拟字段,因此需要一个选项来指定真实使用的字段,键名可用如下中的一个:
// “real field”、“field” 、“relationship field”优先级依次增大
// 其值为'example_table'表中真实的字段名,如不设置将默认采用'unique_dummy_nid'
// 如果表名也是虚拟的,那么可以使用'table'、'real table'或'relationship table'指出,后者优先级更高
'field' => 'nid',
// 关联处理器的插件ID
'id' => 'standard',
// 在视图UI中显示的默认关联label
'label' => t('Example foo'),
],
];
// 纯文本字段, 假设用作field, sort, filter, and argument.
$data['example_table']['plain_text_field'] = [
'title' => t('Plain text field'),
'help' => t('Just a plain text field.'),
'field' => [
// 字段处理器插件ID
'id' => 'standard',
//可选,用于指定该字段是否可以点击排序,无设置时默认值为true
'click sortable' => false,
//可选,用于指定应该随本字段一并被添加到查询的其他字段,元素有两种形式:
//如果是本表中的字段,那么直接为字段名
//如果是其他表中的字段那么为一个数组:['table' => 'tablename', 'field' => 'fieldname']
'additional fields' => [
'fieldname',
['table' => 'tablename',
'field' => 'fieldname',
'params' => [//可选的参数设置,用于控制是否聚合、DISTINCT等
'function' => 'SUM',
'aggregate' => true,
],
],
],
//如果字段键不是真实的数据库字段名,那么以此项指出所用的真实字段名
'real field' => 'fieldname',
],
'sort' => [
// 排序处理器插件ID
'id' => 'standard',
],
'filter' => [
// 过滤处理器插件ID
'id' => 'string',
'allow empty' => true, //是否让'IS NULL'、'IS NOT NULL'操作符生效
],
'argument' => [
// 参数处理器插件ID
'id' => 'string',
'name field' => 'summary name', //可选,显示在摘要中的名字
],
];
// 数字字段,作为 field, sort, filter, and argument.
$data['example_table']['numeric_field'] = [
'title' => t('Numeric field'),
'help' => t('Just a numeric field.'),
'field' => [
// 字段处理器插件ID
'id' => 'numeric',
],
'sort' => [
// 排序处理器插件ID
'id' => 'standard',
],
'filter' => [
// 过滤处理器插件ID
'id' => 'numeric',
],
'argument' => [
// 参数处理器插件ID
'id' => 'numeric',
],
];
// 布尔字段,作为field, sort, and filter.过滤器项展示了插件定义覆写
$data['example_table']['boolean_field'] = [
'title' => t('Boolean field'),
'help' => t('Just an on/off field.'),
'field' => [
'id' => 'boolean',
],
'sort' => [
'id' => 'standard',
],
'filter' => [
// 过滤器处理器插件ID
'id' => 'boolean',
// 覆写通用的字段标题,以便在UI中使用不同的label
'label' => t('Published'),
// 覆写过滤处理器的类型定义条目,
// 见Drupal\views\Plugin\views\filter\BooleanOperator
'type' => 'yes-no',
// 覆写过滤处理器的'use_equal'定义条目
// 见Drupal\views\Plugin\views\filter\BooleanOperator
'use_equal' => TRUE,
],
];
// 时间戳字段, 作为field, sort, and filter.
$data['example_table']['timestamp_field'] = [
'title' => t('Timestamp field'),
'help' => t('Just a timestamp field.'),
'field' => [
'id' => 'date',
],
'sort' => [
'id' => 'date',
],
'filter' => [
'id' => 'date',
],
];
/**
* 视图数据的表和字段键都可以是虚拟的,也就是说并不真实存在于数据库中,
* 当然也可以存在,此时需要在处理器设置中专门指定,在不真实存在也无指定时,通常是为了提供特殊功能
* 如视图模块提供的一些全局功能性字段,其定义如下(来自views_views_data()):
*/
$data['views']['area'] = [
'title' => t('Text area'),
'help' => t('Provide markup text for the area.'),
'area' => [
// 区域处理器插件id
'id' => 'text',
],
];
return $data;
}
希望用视图系统来展示数据的模块需要实现该钩子,但在视图模块的该钩子实现中有一些特别处理:
1、实体类型可以通过设置“views_data”处理器代替钩子实现,但处理器同样是返回“视图数据”数组,系统提供了默认的实体类型视图数据处理器基类:
\Drupal\views\EntityViewsData
可参考节点实体的实现:
Drupal\node\NodeViewsData。
2、提供字段类型的模块可以通过实现钩子“field_views_data”及其修改钩来提供某字段类型的“视图数据”数组,如未实现将执行默认函数:
views_field_default_views_data($field_storage)。
详见视图模块的钩子实现:views_views_data(),该实现还提供了一些全局字段(用于提供一些特殊功能)
示例:
在前文的钩子实现中,因介绍目的提供了许多冗余信息,如果你真的想动手试一试,这里为你提供了示例代码,新建一个模块,假设模块名为“yunke_view”,过程如下:
建立示例数据库表:
在模块根目录新建yunke_view.install文件,在其中建立数据库表安装钩子函数,以在模块安装时自动建立数据库表,函数如下:
function yunke_view_schema()
{
$schema['yunke_view_one'] = [
'description' => '用作视图数据表测试.',
'fields' => [
'nid' => [
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'description' => '节点id.',
],
'plain_text_field' => [
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'default' => '',
'description' => '文本类型',
],
'numeric_field' => [
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'size' => 'normal',
'description' => '数字类型',
],
'boolean_field' => [
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'size' => 'tiny',
'default' => 1,
'description' => '布尔类型',
],
'timestamp_field' => [
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'Unix 时间戳',
],
'langcode' => [
'type' => 'varchar_ascii',
'length' => '12',
'not null' => TRUE,
'default' => '',
'description' => '语言代码',
],
],
'primary key' => ['nid'],
'indexes' => [
'nid' => ['nid'],
],
'foreign keys' => [
'node' => array(
'table' => 'node_field_data',
'columns' => array('nid' => 'nid'),
),
]
];
return $schema;
}
实现视图数据钩子:
然后在yunke_view.module文件中实现视图数据钩子函数:
function yunke_view_views_data()
{
// 举个列子来说明如何实现钩子:hook_views_data() ,假设要用视图展示的数据的数据库表如下:
// CREATE TABLE yunke_view_one (
// nid INT(11) NOT NULL COMMENT '主键: {node}.nid.',
// plain_text_field VARCHAR(32) COMMENT '一个原始文本字段',
// numeric_field INT(11) COMMENT '一个整型数组字段',
// boolean_field INT(1) COMMENT '一个布尔字段on/off',
// timestamp_field INT(8) COMMENT '一个时间戳字段',
// langcode VARCHAR(12) COMMENT '语言代码字段',
// PRIMARY KEY(nid)
// );
// 定义一个返回数组
$data = [];
// 第一级键名是视图的表名,通常就是真实的数据库表名,即hook_schema()中的表名,但也可以是非真实存在的表名(见下)
$data['yunke_view_one'] = [];
// 键名'table'对应的值包含表本身的元数据
$data['yunke_view_one']['table'] = [];
//在'table'数组中,'group'键名对应的值是一个字符串翻译对象,表示该表中所有字段所属的组名
//在视图UI中也用作字段、过滤器等的前缀,在添加她们时,可以用该组属性进行过滤
$data['yunke_view_one']['table']['group'] = t('yunke test table');
// 在'table'数组中, 键名'provider'指示提供本表的模块名,这用来说明依赖关系
// 如果没有设置,那么会被系统自动设置
$data['yunke_view_one']['table']['provider'] = 'yunke_view';
// 在视图中有些表是“基本表”,非基本表只能通过“关联relationships”引入
// 要把表定义成基本表就设置'base'键名,如下:
$data['yunke_view_one']['table']['base'] = [
// 指明主键字段(primary field)
'field' => 'nid',
// 在视图UI中显示的Label
'title' => t('yunke test table'),
// 在视图UI中显示的长描述,必有的
'help' => t('yunke test table contains example content and can be related to nodes.'),
//排序权重
'weight' => -10,
];
// 有些表和其他表有着隐式的自动关联关系,比如实体的“字段专用表”和“数据表”
// 在视图中数据表有效时,字段专用表也应自动有效,而不必管理员指定关联配置
// 要将本表定义成某表的“隐式关联表”,须添加'join'属性,
// 注意:
// 如果隐式关联表和被关联表中的数据不是一对一关系,那么视图会查询出多行
// 如果不需要自动有效,那么不需要设置为隐式关联,而让管理员设置关联代替
// 见下文的“relationship”配置项
//
// 隐式关联定义如下:
$data['yunke_view_one']['table']['join'] = [
// 本表称为“隐式关联表”,在'join'下是一个或多个“被隐式关联的表”
// 这里以节点数据表为例进行定义,将产生类似如下的SQL语句(表别名可能不同):
// ... FROM node_field_data nfd... JOIN yunke_view_one et ON et.nid = nfd.nid AND ('extra' 额外条件) ...
// 每当'node_field_data'表有效时,本表也将有效
// 注意:确保提供“隐式关联表”的模块依赖在提供“被隐式关联的表”的模块上
'node_field_data' => [
// node_field_data表中的字段主键,用来执行join关联
// 注意:字段名来自'node_field_data'表的hook_views_data()实现
'left_field' => 'nid',
// 'yunke_view_one'表中的外键字段,用来执行join关联,此时'field'是'right_field'的简写 todo:是否正确
'field' => 'nid',
// 'extra'包含额外的join条件
'extra' => [
0 => [
// 添加一个AND条件到join语句: node_field_data.published = TRUE
// 'left_field'表示左字段,这里即node_field_data表中的字段
'left_field' => 'status',
'value' => TRUE,
],
],
],
];
// 在表数组中,'table'键包含表元数据,其他键包含字段元数据,对应的键名通常使用数据库字段名
// 各字段条目必须包含以下两个元素:
// - title: 一个翻译对象,在视图UI中显示
// - help: 一个翻译对象,在视图UI中显示的字段描述
//
// 可选的包含以下元素:
// - relationship: 指定一个关联处理器,可使用该字段作为关联项关联到其他表
// - field: 指定一个字段处理器
// - filter: 指定一个过滤处理器
// - sort: 指定一个排序处理器
// - argument: 指定一个参数处理器
// - area: 指定一个区域处理器,用于处理添加内容到首、尾、或无结果时的行为
//
// 各处理器以插件方式提供,指定时必须给出插件ID,可选的可以给出插件定义的覆写值
// 节点id字段,做关联项用
$data['yunke_view_one']['nid'] = [
'title' => t('yunke test content'),
'help' => t('Relate yunke test content to the node content'),
// 定义一个到node_field_data表的关联,定义后,如果视图以本表做为基本表,
// 那么可以用本字段添加一个关联到节点数据表,注意这种关联和隐式关联不同,需要管理员添加
'relationship' => [
// 要关联到的表名
'base' => 'node_field_data',
// 要关联的表的关联字段
'base field' => 'nid',
// 关联处理器的插件ID
'id' => 'standard',
// 在视图UI中显示的默认关联label
'label' => t('yunke test '),
],
];
// 纯文本字段, 用作field, sort, filter, and argument.
$data['yunke_view_one']['plain_text_field'] = [
'title' => t('yunke Plain text field'),
'help' => t('yunke Just a plain text field.'),
'field' => [
// 字段处理器插件ID
'id' => 'standard',
],
'sort' => [
// 排序处理器插件ID
'id' => 'standard',
],
'filter' => [
// 过滤处理器插件ID
'id' => 'string',
],
'argument' => [
// 参数处理器插件ID
'id' => 'string',
],
];
// 数字字段,作为 field, sort, filter, and argument.
$data['yunke_view_one']['numeric_field'] = [
'title' => t('yunke Numeric field'),
'help' => t('yunke Just a numeric field.'),
'field' => [
// 字段处理器插件ID
'id' => 'numeric',
],
'sort' => [
// 排序处理器插件ID
'id' => 'standard',
],
'filter' => [
// 过滤处理器插件ID
'id' => 'numeric',
],
'argument' => [
// 参数处理器插件ID
'id' => 'numeric',
],
];
// 布尔字段,作为field, sort, and filter.过滤器项展示了插件定义覆写
$data['yunke_view_one']['boolean_field'] = [
'title' => t('yunke Boolean field'),
'help' => t('yunke Just an on/off field.'),
'field' => [
'id' => 'boolean',
],
'sort' => [
'id' => 'standard',
],
'filter' => [
// 过滤器处理器插件ID
'id' => 'boolean',
// 覆写通用的字段标题,以便在UI中使用不同的label
'label' => t('Published'),
// 覆写过滤处理器的类型定义条目,
// 见Drupal\views\Plugin\views\filter\BooleanOperator
'type' => 'yes-no',
// 覆写过滤处理器的'use_equal'定义条目
// 见Drupal\views\Plugin\views\filter\BooleanOperator
'use_equal' => TRUE,
],
];
// 时间戳字段, 作为field, sort, and filter.
$data['yunke_view_one']['timestamp_field'] = [
'title' => t('yunke Timestamp field'),
'help' => t('yunke Just a timestamp field.'),
'field' => [
'id' => 'date',
],
'sort' => [
'id' => 'date',
],
'filter' => [
'id' => 'date',
],
];
return $data;
}
运行测试:
可以手动在数据库表中添加一些数据,然后就可以新建视图进行测试了
视图配置实体:
从程序角度看,我们在系统中建立的每一个视图均是一些配置信息的集合,由一个配置实体储存,即视图配置实体,简称视图实体,实体类如下:
Drupal\views\Entity\View
视图实体包含着视图所有方面的信息(内部数据结构及解释见下文),视图UI模块的主要用途即是帮助管理员通过UI界面去构建视图实体,视图管理页(管理-结构-视图,路径:/admin/structure/views)即是视图实体的列表页面,由以下实体列表构建器输出:
Drupal\views_ui\ViewListBuilder
路由名为:entity.view.collection
视图实体的各类处理器如下:
Array
(
[access] => Drupal\Core\Entity\EntityAccessControlHandler
[storage] => Drupal\Core\Config\Entity\ConfigEntityStorage
[form] => Array
(
[edit] => Drupal\views_ui\ViewEditForm
[add] => Drupal\views_ui\ViewAddForm
[preview] => Drupal\views_ui\ViewPreviewForm
[duplicate] => Drupal\views_ui\ViewDuplicateForm
[delete] => Drupal\Core\Entity\EntityDeleteForm
[break_lock] => Drupal\views_ui\Form\BreakLockForm
)
[list_builder] => Drupal\views_ui\ViewListBuilder
)
在视图实体类中并未指定这些处理器,她们由视图UI模块实现的实体类型构建钩子添加:
views_ui_entity_type_build(array &$entity_types)
各类视图配置操作均由对应的表单完成
这里需要首先说明“break_lock”表单,为叙述方便这里将其称为“解除锁定”表单,当一个用户在编辑视图时,所有未保存数据将储存在“共享临时储存”中,反过来,系统发现某视图在临时储存中有数据,就说明其正在被编辑,则将该视图视为锁定的,此时其他用户如果尝试编辑该视图,则不被允许,会被提示视图正在被谁编辑,同时给出一个链接允许用户强制解除锁定状态,用户点击链接时将打开“解除锁定”表单,确认后之前用户所做的所有修改将被丢弃,当前用户可对视图进行编辑。
那么编辑视图时,尚未保存的数据是如何储存在临时储存中的呢?这就需要用到一个中间对象,在编辑视图时,所有的修改均保存在中间对象上,一旦点击保存,则将中间对象上的数据保存到视图实体,并从临时储存中删除中间对象,该中间对象即“视图UI”对象。
视图UI对象:
类:Drupal\views_ui\ViewUI
用于在视图编辑过程中保存临时数据,同时也提供很多辅助编辑表单的方法,在视图编辑时,操作的即是该对象,在实现上,其是视图实体对象的一个包装代理,也实现了视图实体接口:
\Drupal\views\ViewEntityInterface
编辑过程中改变的数据实际上还是保存到了视图对象上,但视图对象以未保存状态被视图UI对象持有,后者在请求间被序列化保存到临时储存,详见以下方法:
\Drupal\views_ui\ViewUI::cacheSet
直到执行视图UI对象的保存方法时,改变的数据才会真实生效(此时联动执行了视图对象的保存方法)
视图可执行对象:
类:\Drupal\views\ViewExecutable
视图的可执行对象是视图系统的核心对象,或者说是最顶层对象(各类插件和处理器对象均挂接到该对象),包含了产生视图的全部信息,且有构建查询、执行查询、渲染输出的成员方法,可以说视图可执行对象就代表着整个视图
其通常是由可执行视图对象工厂实例化
工厂服务id:views.executable
工厂类:Drupal\views\ViewExecutableFactory
工厂的get方法接收一个视图实体对象,返回其可执行对象;采用工厂服务除集中设置构造参数外,还带来灵活性,允许开发者将默认的可执行视图对象类替换成自定义的。
在程序上得到一个视图的完整渲染数组怎么做呢?
在控制器中输出某个视图:
$view = \Drupal::entityTypeManager()->getStorage("view")->load($name);
$viewExecutable = $view->getExecutable();
if (!$viewExecutable->access($display_id)) {
return; //权限控制
}
return $viewExecutable->preview($display_id);
或者使用函数(该函数默认会检查权限):
return views_embed_view($name, $display_id = 'default');
这种输出方式适用于所有的显示类型,但如果某视图是专门配置为程序输出的,那么该视图实例应该采用“嵌入”显示类型。
以上代码演示了视图的执行流程,我们可以清晰的看到各方法的调用次序,这里按照大概的逻辑顺序(各方法的实际执行其实很复杂)对一些重点方法做简要说明:
public function preview($display_id = NULL, $args = [])
视图系统的入口函数
public function setDisplay($display_id = NULL)
设置视图当前显示实例,在该方法中初始化显示插件对象
public function preExecute($args = [])
预执行,在该方法运行过程中会初始化当前显示实例的所有处理器
public function build($display_id = NULL)
构建数据库查询对象,运行后,查询对象(Drupal\Core\Database\Driver\mysql\Select)保存在可执行对象的“build_info”属性上
public function execute($display_id = NULL)
执行视图的查询操作,也就是执行数据库查询并得到查询结果,运行后,查询结果保存在可执行对象的“result”属性上,值为Drupal\views\ResultRow对象构成的数组,直接由数据库查询结果对象生成:
$result = $query->execute();
$result->setFetchMode(\PDO::FETCH_CLASS, 'Drupal\views\ResultRow');
详见:\Drupal\views\Plugin\views\query\Sql::execute
protected function _postExecute()
调用所有处理器的执行后方法
public function render($display_id = NULL)
执行视图的渲染部分
视图配置实体内部数据结构:
查看视图配置实体的内部数据结构可在控制器中运行如下代码:
$view = \Drupal::entityTypeManager()->getStorage("view")->load($viewID);
print_r($view->toArray());die;
或通过“yunke_help”模块查看,为方便查询这里将结构及解释列出:
$view = [
'id' => 'viewID', //视图id
'label' => '视图label',
'description' => '视图描述',
'status' => 1, //视图是否启用
'tag' => '', //视图标签,英文字符串,按标签控制主题输出,多个可用英文逗号或空格分隔
'base_table' => 'node_field_data', //视图所用的基本表
'base_field' => 'nid', //视图基本字段
'display' => [ //保存所有显示实例的详细配置,每一个键名代表一个显示实例,键名即为显示实例的实例id
'default' => [
'display_plugin' => 'default', //该显示实例所用的插件id
'id' => 'default', //显示实例的实例id,同键名
'display_title' => 'Master', //显示实例的管理标题,用在视图编辑表单顶部导航选项卡中,在表单“Display name”处修改
'position' => 0, //显示实例的排序权重
'cache_metadata' => [], //缓存元数据数组,键名有:max-age、contexts、tags
'display_options' => [ //显示实例的各种显示配置,即
'access' => [//视图访问控制设置(插件设置)
'type' => 'none', //非处理器的插件均以键名“type”指定插件id
'options' => [], //所有插件设置均有“options”项,用于保存插件配置
],
'cache' => [//缓存控制设置(插件设置)
'type' => 'tag',
'options' => [],
],
'query' => [//查询选项设置(插件设置)
'type' => 'views_query',
'options' => [
'disable_sql_rewrite' => false, //禁用SQL重写
'distinct' => false, //排除重复
'replica' => false, //是否使用从数据库
'query_comment' => null, //查询语句注释
'query_tags' => ['tag1', 'tag2'], //查询标签,用于派发查询标签钩子
],
],
'css_class' => 'class1 class2', //视图输出的包装元素上运用的css类名
'exposed_form' => [ //高级设置中的“Exposed form”设置(插件设置)
'type' => 'basic',
'options' => [
'submit_button' => '应用',
'reset_button' => '',
'reset_button_label' => 'Reset',
'exposed_sorts_label' => 'Sort by',
'expose_sort_order' => true,
'sort_asc_label' => 'Asc',
'sort_desc_label' => 'Desc',
]
],
'pager' => [//分页器设置(插件设置)
'type' => 'mini',
'options' => [
],
],
'style' => [//布局设置(插件设置)
'type' => 'html_list',
'options' => [],
],
'row' => [//数据单元设置(插件设置)
'type' => 'fields',
'options' => [],
],
'fields' => [//字段设置 各键名为字段处理器id
],
'filters' => [//过滤器设置
],
'sorts' => [//排序设置
],
'title' => '显示实例的标题', //即Title设置项
'header' => [//头部设置
],
'footer' => [//尾部设置
],
'empty' => [//为空时的行为设置
],
'relationships' => [//关联设置
],
'arguments' => [//参数设置
],
'display_extenders' => [//设置显示扩展插件
'display_extenders_PluginId_foo' => [],
//键名为display_extenders插件id,键值为其配置选项
//详见\Drupal\views\Plugin\views\display\DisplayPluginBase::initDisplay
'display_extenders_PluginId_bar' => [],
],
'use_ajax' => true, //是否使用ajax
],
],
'page_1' => [
//...数据结构与default类似
'deleted' => false, //在每一个显示实例中如果出现该键,且值不为空,则表示该实例已被删除
'display_options' => [
'path' => 'yunke/path', //页面显示实例的路径
'enabled' => true, //本显示实例是否开启,在设置表单顶部“Display name”右侧下拉按钮中配置
'display_description' => '显示实例的描述,用于后台管理目的', //在设置表单顶部“Display name”中配置
'defaults' => [ //指示某显示选项是否使用默认选项(默认显示实例中的选项)的指示器
//是否使用默认选项由本项决定,键名和显示选项中的键名相同,
//其值如果为true,表示将从默认显示实例中获取选项值
//如果为false,那么从本显示选项中获取值,此时必须存在
'fields' => true,
'sorts' => true,
'query' => true,
'css_class' => true,
'use_ajax' => true,
]
],
],
'block_1' => [
//...数据结构与default类似
],
],
];
视图的新建:
在视图管理页点击添加视图,即打开以下地址:
/admin/structure/views/add
对应路由如下:
views_ui.add:
path: '/admin/structure/views/add'
defaults:
_entity_form: 'view.add'
_title: 'Add view'
requirements:
_entity_create_access: view
对应的实体表单类如下:
Drupal\views_ui\ViewAddForm
该表单的构造较复杂,会用到向导插件,见下
编辑视图:
在视图管理页点击编辑视图,将打开以下路由:
entity.view.edit_form:
path: '/admin/structure/views/view/{view}'
options:
parameters:
view:
tempstore: TRUE
type: entity:view
defaults:
_controller: '\Drupal\views_ui\Controller\ViewsUIController::edit'
requirements:
_entity_access: view.update
如果是点击编辑某一个显示实例时,将打开以下路由:
entity.view.edit_display_form:
path: '/admin/structure/views/view/{view}/edit/{display_id}'
options:
parameters:
view:
tempstore: TRUE
type: entity:view
defaults:
_controller: '\Drupal\views_ui\Controller\ViewsUIController::edit'
display_id: NULL
requirements:
_entity_access: view.update
以上两种编辑入口路由均采用了同一个控制器,得到的视图对象为视图UI对象,这是因为使用了如下参数转化器:
服务id:paramconverter.views_ui
类:Drupal\views_ui\ParamConverter\ViewUIConverter
从该参数转化器可以看到:如果临时储存中已经有了视图UI对象,那么将直接加载,如果还没有,那么会新实例化一个,并注入视图实体对象,但新实例化的视图UI对象在控制器接收到时,尚未保存到临时储存。
在控制器中将使用实体表单构建器以通常方法构建视图的编辑表单和预览表单
视图表单基类:
类:\Drupal\views_ui\ViewFormBase
视图的添加、编辑、预览、复制表单均继承自该表单基类,其又继承了实体表单基类,执行流程请参考本系列实体表单主题,该基类各方法说明如下:
public function init(FormStateInterface $form_state)
初始化表单,加载视图库函数文件,将视图UI对象保存到表单状态对象的'view'键下
public function buildForm(array $form, FormStateInterface $form_state, $display_id = NULL)
实体表单流程中执行的第一个方法,在其中会调用初始化方法
protected function prepareEntity()
在初始化方法中被调用,用于判断表单正在操作哪一个显示实例,如未传递,见采用以权重排序的第一个可编辑实例
public function getDisplayTabs(ViewUI $view)
构建显示实例间的导航,返回编辑表单中,选项卡栏的渲染数组
public function isDefaultDisplayShown(ViewUI $view)
判断是否显示默认的主显示实例,返回布尔值
public function getDisplayLabel(ViewUI $view, $display_id, $check_changed = TRUE)
获取显示实例的label,用在编辑表单的显示实例导航选项卡中
视图编辑表单:
类:Drupal\views_ui\ViewEditForm
该表单类用于构造视图编辑表单,视图编辑表单由以下几部分组成:
提示消息区:
表单位置:$form['changed'],如果视图处于锁定状态则是$form['locked']
当有编辑变化时才显示,默认类'messages', 'messages--warning'
导航栏区:
表单位置:$form['displays']['top']
用于显示实例间切换导航、添加实例、操作链接,该区id为:views-display-top,开发者可以通过以下钩子去修改该区:views_ui_display_top_links、views_ui_display_top,钩子详细信息见下文
显示实例设置区:
表单位置:$form['displays']['settings']['settings_content']['tab_content']
类为:views-display-tab,id为:'views-tab-' . $display_id
可以使用钩子“views_ui_display_tab”去修改该区,钩子详细信息见下文
设置区分为三列,具体的设置表单由插件构建
添加新的显示实例:
在以下方法中执行:
\Drupal\views_ui\ViewEditForm::submitDisplayAdd
视图插件概述:
用于构建和渲染视图,几乎控制了视图的所有方面,包括查询、排序、过滤、显示等
视图模块定义了十九种插件类型,可以分为两大类:普通视图插件和视图处理器插件,区别是:
1、处理器插件是普通插件的进一步定义,是其子类,处理器插件也属于视图插件
2、处理器插件的操作对象是“视图数据”定义中的字段
3、系统运行过程中同一个普通插件通常只实例化一个实例,而同一个处理器插件会为每一个字段实例化一个实例
普通视图插件有12种:
access、argument_default、argument_validator、cache、display_extender、display、exposed_form、pager、query、row、style、wizard
管理器如下:
Drupal\views\Plugin\ViewsPluginManager
均实现普通视图插件接口:
Drupal\views\Plugin\views\ViewsPluginInterface
默认普通视图插件基类:
\Drupal\views\Plugin\views\PluginBase
处理器插件有7种:
field、argument、sort、filter、relationship、area、join
管理器类如下:
Drupal\views\Plugin\ViewsHandlerManager
所有处理器插件均实现处理器插件接口('join'除外):
Drupal\views\Plugin\views\ViewsHandlerInterface(该接口继承自普通视图插件接口)
默认处理器插件基类:
\Drupal\views\Plugin\views\HandlerBase(继承自普通视图插件基类)
系统中,各类型插件均进一步提供了自己的基类,她们均继承以上基类,这大大方便了自定义插件。
处理器插件在管理器的getHandler($info, $override)方法中实例化,而普通插件为createInstance($plugin_id);不管是处理器插件还是普通视图插件,在实例化后都将随即执行初始化方法以注入配置信息:
init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL);
向导插件:
用于辅助构建视图添加表单(即/admin/structure/views/add),如提供基本表选项、验证提交值等;
也用于构造初始状态的视图对象,设置各种默认选项等
管理器服务id:plugin.manager.views.wizard
管理器类:Drupal\views\Plugin\ViewsPluginManager
插件目录:src/Plugin/views/wizard
有以下默认模块实现了该插件:
Array
(
[0] => block_content
[1] => comment
[2] => watchdog
[3] => file_managed
[4] => media
[6] => node
[8] => taxonomy_term
[9] => users
[11] => standard:block_content_field_revision
[12] => standard:taxonomy_term_field_revision
)
如果有自定义的视图基本表,那么会以插件派生的方式为其定义插件,默认插件类如下:
Drupal\views\Plugin\views\wizard\Standard
为了提高用户体验,视图的新建表单设计的很复杂,为此系统专门提供了向导插件,其核心功能调用了其他类型插件,核心知识点在其他插件介绍中说明,这里不再专门介绍。
显示插件:
用于处理视图的整体显示,显示插件代表着视图在哪个地方进行渲染,换句话说即是视图输出如何提供给Drupal的其他部分,每个插件代表着视图的一个显示类型,不同显示类型意味在不同地方输出视图,每一个视图都有一个默认显示类型,该类型用来储存视图各显示实例间的共享信息
管理器服务id:plugin.manager.views.display
管理器类:Drupal\views\Plugin\ViewsPluginManager
插件目录:src/Plugin/views/display
默认有以下插件实现(即有这些显示类型):
Array
(
[0] => attachment
[1] => block
[2] => default
[3] => embed
[4] => entity_reference
[5] => feed
[6] => page
)
显示插件除实现“视图插件接口”外,还需要实现显示插件接口:
\Drupal\views\Plugin\views\display\DisplayPluginInterface
系统提供了以下默认基类:
\Drupal\views\Plugin\views\display\DisplayPluginBase
显示插件默认在以下方法中初始化:
\Drupal\views\DisplayPluginCollection::initializePlugin($display_id)
视图渲染过程中会按次序执行以下方法:
initDisplay(ViewExecutable $view, array &$display, array &$options = NULL);
实例化时传递了配置信息,即视图实体中的数据,实例化后随即运行初始化方法,如果不是默认显示实例,还会在其“default_display”属性上保存默认显示实例对象
preExecute()
初始化后即运行执行前方法
preview()
该方法返回视图输出的渲染数组
显示扩展插件:
用于对视图下任意显示实例执行一些相同操作,而不管是什么显示类型,比如向视图所有显示实例的渲染输出添加某缓存元数据,显示扩展是跨显示类型存在的,只要显示实例配置了她,且系统有开启她就会被运用
管理器服务id:plugin.manager.views.display_extender
管理器类:Drupal\views\Plugin\ViewsPluginManager
插件目录:src/Plugin/views/display_extender
默认基类:\Drupal\views\Plugin\views\display_extender\DisplayExtenderPluginBase
系统仅提供了以下默认插件:
\Drupal\views\Plugin\views\display_extender\DefaultDisplayExtender
该插件什么也不做
查询插件:
负责构建和执行数据库查询的中心对象,供其他处理器添加、修改查询参数,如字段、排序、过滤等等,最终执行查询
管理器服务id:plugin.manager.views.query
管理器类:Drupal\views\Plugin\ViewsPluginManager
插件目录:src/Plugin/views/query
默认基类:\Drupal\views\Plugin\views\query\QueryPluginBase
默认情况下,系统只提供了一个插件:
\Drupal\views\Plugin\views\query\Sql
样式插件:
用于控制视图结果集如何渲染,通常可以认为如何进行布局处理
管理器服务id:plugin.manager.views.style
管理器类:Drupal\views\Plugin\ViewsPluginManager
插件目录:src/Plugin/views/style
默认基类:\Drupal\views\Plugin\views\style\StylePluginBase
默认在显示插件中实例化:
$style_plugin = $display_handler->getPlugin('style');
有些样式插件(不是全部)有行插件,行插件用于渲染单条记录(单个数据单元),是否使用行插件取决于样式插件
行插件:
行插件用于渲染单条记录,单条记录即一行数据,行即一个数据单元,如按某视图模式输出一个完整的实体,行插件由样式插件调用执行,但并非所有样式插件都使用行插件,如“html_list”、“grid”使用行插件,而“table”则不使用行。
管理器服务id:plugin.manager.views.row
管理器类:Drupal\views\Plugin\ViewsPluginManager
插件目录:src/Plugin/views/row
默认基类:\Drupal\views\Plugin\views\row\RowPluginBase
字段(处理器)插件:
字段插件负责字段的查询和显示(字段即数据单元中的项),她通过查询插件对象向最终执行的SQL中添加字段信息,得到数据后将负责显示处理,比如覆写、包装等,部分字段插件是为功能性而存在,其并不真的从数据库获取数据,这样的字段通常不参与构造SQL查询,显示时也可以显示为各种控件,比如节点批量应用动作的字段插件:“node_bulk_form”。
管理器服务id:plugin.manager.views.field
管理器类:Drupal\views\Plugin\ViewsHandlerManager
插件目录:src/Plugin/views/field
默认基类:\Drupal\views\Plugin\views\field\FieldPluginBase
过滤器(处理器)插件:
用于处理数据过滤
管理器服务id:plugin.manager.views.filter
管理器类:Drupal\views\Plugin\ViewsHandlerManager
插件目录:src/Plugin/views/filter
默认基类:\Drupal\views\Plugin\views\filter\FilterPluginBase
排序(处理器)插件:
用于处理数据排序,构建SQL的排序部分
管理器服务id:plugin.manager.views.sort
管理器类:Drupal\views\Plugin\ViewsHandlerManager
插件目录:src/Plugin/views/sort
默认基类:\Drupal\views\Plugin\views\sort\SortPluginBase
访问控制插件:
用于视图的访问控制,默认提供了三种控制:基于角色、基于权限、总是允许。
管理器服务id:plugin.manager.views.access
管理器类:Drupal\views\Plugin\ViewsPluginManager
插件目录:src/Plugin/views/access
默认基类:\Drupal\views\Plugin\views\access\AccessPluginBase
区域(处理器)插件:
用于在视图首、尾、无结果时的中心区域(header、footer、empty)提供各种类型的内容,每种类型的内容都是一个插件
管理器服务id:plugin.manager.views.area
管理器类:Drupal\views\Plugin\ViewsHandlerManager
插件目录:src/Plugin/views/area
默认基类:\Drupal\views\Plugin\views\area\AreaPluginBase
分页器插件:
用于控制数据量、渲染分页器等
管理器服务id:plugin.manager.views.pager
管理器类:Drupal\views\Plugin\ViewsPluginManager
插件目录:src/Plugin/views/pager
默认基类:\Drupal\views\Plugin\views\pager\PagerPluginBase
参数(处理器)插件:
也可称为上下文过滤器插件,用于通过视图执行时的上下文值(环境值)对数据进行过滤
管理器服务id:plugin.manager.views.argument
管理器类:Drupal\views\Plugin\ViewsHandlerManager
插件目录:src/Plugin/views/argument
默认基类:\Drupal\views\Plugin\views\argument\ArgumentPluginBase
参数默认值插件:
用于为上下文过滤器提供默认参数值
管理器服务id:plugin.manager.views.argument_default
管理器类:Drupal\views\Plugin\ViewsPluginManager
插件目录:src/Plugin/views/argument_default
默认基类:\Drupal\views\Plugin\views\argument_default\ArgumentDefaultPluginBase
参数验证插件:
用于对上下文过滤器中获得的参数值进行合法性验证,有时甚至做参数转化
管理器服务id:plugin.manager.views.argument_validator
管理器类:Drupal\views\Plugin\ViewsPluginManager
插件目录:src/Plugin/views/argument_validator
默认基类:\Drupal\views\Plugin\views\argument_validator\ArgumentValidatorPluginBase
关联(处理器)插件:
用于处理字段关联,是join处理器的上层逻辑
管理器服务id:plugin.manager.views.relationship
管理器类:Drupal\views\Plugin\ViewsHandlerManager
插件目录:src/Plugin/views/relationship
默认基类:\Drupal\views\Plugin\views\relationship\RelationshipPluginBase
结联(处理器)插件:
是关联处理的底层实现,被关联插件调用,用于参与构造查询对象
管理器服务id:plugin.manager.views.join
管理器类:Drupal\views\Plugin\ViewsHandlerManager
插件目录:src/Plugin/views/join
默认基类:\Drupal\views\Plugin\views\join\JoinPluginBase
公开表单插件:
用于处理公开的过滤、排序、分页表单
管理器服务id:plugin.manager.views.exposed_form
管理器类:Drupal\views\Plugin\ViewsPluginManager
插件目录:src/Plugin/views/exposed_form
默认基类:\Drupal\views\Plugin\views\exposed_form\ExposedFormPluginBase
缓存插件:
用于处理视图的缓存,默认提供了:不缓存、基于标签、基于时间三种
管理器服务id:plugin.manager.views.cache
管理器类:Drupal\views\Plugin\ViewsPluginManager
插件目录:src/Plugin/views/cache
默认基类:\Drupal\views\Plugin\views\cache\CachePluginBase
视图相关钩子:
除本文前文所提到的钩子外,还有以下钩子:
视图失效缓存钩子:
钩子名:views_invalidate_cache
无修改钩,无参数,在视图对象被保存后执行
派发位置:函数views_invalidate_cache()
视图编辑表单顶部显示实例选项卡右侧链接修改钩子:
钩子名:views_ui_display_top_links
仅有修改钩,接收链接渲染数组、视图UI对象、显示实例ID
派发位置:方法\Drupal\views_ui\ViewEditForm::renderDisplayTop
视图编辑表单顶部显示实例导航栏修改钩子:
钩子名:views_ui_display_top
仅有修改钩,模块和主题均可实现该钩子,参数有导航栏渲染数组、视图UI对象、显示实例ID
派发位置:方法\Drupal\views_ui\ViewEditForm::renderDisplayTop
视图编辑表单实例配置区修改钩子:
钩子名:views_ui_display_tab
仅有修改钩,模块和主题均可实现该钩子,参数有渲染数组、视图UI对象、实例ID
派发位置:方法\Drupal\views_ui\ViewEditForm::getDisplayTab
视图查看前钩子:
钩子名:views_pre_view
没有修改钩,参数:视图可执行对象、显示实例id、视图参数(数组,以引用传递)
派发位置:方法\Drupal\views\ViewExecutable::preExecute
视图执行前钩子:
钩子名:views_pre_execute
执行即执行查询,没有修改钩,仅一个参数:视图可执行对象
派发位置:\Drupal\views\ViewExecutable::execute
视图执行后钩子:
钩子名:views_post_execute
没有修改钩,仅一个参数:视图可执行对象
派发位置:\Drupal\views\ViewExecutable::execute
视图渲染前钩子:
钩子名:views_pre_render
没有修改钩,主题可实现该钩子,仅一个参数:视图可执行对象
派发位置:\Drupal\views\ViewExecutable::render
视图渲染后钩子:
钩子名:views_post_render
没有修改钩,主题可实现该钩子,三个参数:视图可执行对象、输出的渲染数组(引用传递)、缓存插件对象
派发位置:\Drupal\views\ViewExecutable::render
视图构建前钩子:
钩子名:views_pre_build
在执行查询构建前派发,参数:视图可执行对象
派发位置:\Drupal\views\ViewExecutable::build
视图构建后钩子:
钩子名:views_post_build
在执行查询构建后派发,参数:视图可执行对象
在可执行对象的build_info属性上保存着查询对象(Drupal\Core\Database\Driver\mysql\Select)
派发位置:\Drupal\views\ViewExecutable::build
查询源数据修改钩子:
产生的元数据会被保存到查询对象源数据键“views_substitutions”下,元数据主要供查询标签钩子使用,详见数据库篇
钩子名:views_query_substitutions
没有修改钩,仅一个参数:视图可执行对象
派发位置:\Drupal\views\Plugin\views\query\Sql::query
查询修改钩子:
钩子名:views_query_alter
系统在调用查询插件对象的构建(build)方法的前一刻执行,参数有:视图可执行对象、查询插件对象
派发位置:\Drupal\views\Plugin\views\query\Sql::alter
处理器选项修改钩子:
用于修改处理器插件的选项数组
钩子名:views_handler_options
参数:选项数组、视图可执行对象
派发位置:\Drupal\views\Plugin\views\HandlerBase::buildOptionsForm
视图分析钩子:
用于执行视图分析,返回一些信息给用户,当用户点击配置界面的分析视图时能看到这些信息,默认安装下仅视图UI模块实现了分析钩子
钩子名:views_analyze
参数:视图可执行对象
派发位置:\Drupal\views\Analyzer::getMessages
视图路由的处理:
在视图实体新建、删除、缓存失效或状态改变时,会执行以下方法:
\Drupal::service('router.builder') ->setRebuildNeeded();
这将导致系统调用路由构建器在本次请求结束时去重建路由,重建即删除整个路由数据表,重新收集全部路由数据并写入,重建的具体时机是在kernel.terminate事件派发时,在核心析构订阅器“kernel_destruct_subscriber”中进行,视图模块在路由定义文件中设置了以下路由回调:
route_callbacks:
- 'views.route_subscriber:routes'
这将调用“views.route_subscriber”服务的“routes”方法去重建路由
后记:
视图是一个较复杂庞大的系统,由于篇幅限制无法详述,但本文已理出了基本脉络,如需精通某部分内容需要进一步继续努力,新手在理解她时会感觉较为困难,不过没关系,视图系统不是一蹴而就的,她经过了漫长的发展研究才到今天的样子,没有人看一遍就能十分理解她,因此鼓励新手不要灰心,慢慢来,反复来,任何人类辉煌看起来叹为观止,但背后均是长时间的发展、研究。
反馈互动