Views API 数据视图

admin 提交于 周三, 06/07/2023 - 15:21

Views API 数据视图开发

在前面,我们学习了使用 Views(视图系统) 来显示数据列表, 系统管理员在管理后台通过可视化的配置管理,可以快速地建立强大的数据展示列表。

Views(视图系统) 的功能十分强大,此外它还具备非常丰富的编程接口, 使你可以为它添加各种扩展,以实现你的个性化需求。本节将详细介绍这方面的内容。

Views Data

Views(视图系统) 被设计成支持任意数据源,比如 SQL 数据库,Excel 表格,系统目录的文件等等。 开发者使用一种被称为 Views Data 的配置代码来向 Views(视图系统) 提供可检索的数据描述信息。 但通常 SQL 数据库是最常用的数据源,这也是 Drupal 默认使用的数据源,所以我们通常只关注 SQL 数据库数据源。

通常我们使用 hook_views_data() 来向 Views(视图系统) 提供 Views Data。这个 Hook 的代码文档详细说明了如何编写 views data。

为了方便我们学习,下面摘取了 云客 的中文版本的 hook_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;
}

实体的 views_data Handler

实体类型可以通过设置 views_data 处理器代替钩子实现, 处理器同样是返回与 hook_views_data() 相同的配置数组, 系统提供了默认的实体类型 views_data 处理器实现 \Drupal\views\EntityViewsData, 一般使用它就能向 Views 系统提供自定义实体类型数据源。