144.批量更新BulkUpdate
批量更新(Bulk update)和“批处理API”是不同的概念,“批处理”是将任务分派在多个请求中执行,目的是避免执行大任务时PHP超时,而本文主题“批量更新”是指对一条或多条数据执行某个操作,如批量删除节点、一次性将某权限授予多个用户、批量置顶内容等等,在默认的内容管理页(/admin/content)或用户列表页(/admin/people)就列出了一些操作,选择一些条目和需要应用的操作后点击“应用于选中项(Apply to selected items)”就可以执行选择的操作了,这里被执行的操作也叫做“动作action”,各动作由动作插件提供。
动作插件管理器
服务id:plugin.manager.action
类:Drupal\Core\Action\ActionManager
插件目录:src/Plugin/Action
插件接口:Drupal\Core\Action\ActionInterface
动作插件用于实际执行某个动作,即提供执行某个动作的具体逻辑,但不代表实现了某个动作插件就能在系统中直接应用该动作,是否能够运用某动作受控于动作配置实体(见下);动作可分为两大类:
高级动作:
高级动作又称为可配置动作,这种动作的执行需要额外参数,参数的提供时机不是在动作应用页面,而是预设储存在动作配置实体中,在动作配置实体的编辑页面中,插件需提供配置表单来收集所需的参数
简单动作:
和高级动作相比,简单动作不需要额外参数
自定义动作插件:
系统提供了以下通用基类:
\Drupal\Core\Action\ActionBase
由于系统主要内容数据均是以实体方式提供,所以许多动作实现也是针对实体的(针对非实体数据的动作见下文),为此进一步提供了如下基类:
\Drupal\Core\Action\Plugin\Action\EntityActionBase
自定义的动作插件通常可以继承自以上基类,放置在模块目录“src/Plugin/Action”中,释文中提供以下关键内容:
id:插件id
label:给人类看的名称
action_label:基本label,有用到默认派生器时需要指定
confirm_form_route_name:可选,提供执行动作确认的确认表单路由名
deriver:可选,派生器类
type:通常是可运用该动作的实体类型ID,但如果动作有特殊用途或不针对实体也可以是自定义值,如“system”,必选,如果一个动作可以运用于多种实体类型,此时通常需要提供派生器为每一个实体类型生成插件定义,提供派生器时在基插件中该项可选,但派生后必须存在
系统默认提供了以下派生器基类:
\Drupal\Core\Action\Plugin\Action\Derivative\EntityActionDeriverBase
自定义的派生器通常会继承该基类,继承后只需要实现“isApplicable”方法以指定动作是否能运用于某实体类型即可
如果自定义的动作是高级动作,即需要提供额外参数的动作,那么插件可继承以下可配置默认基类:
\Drupal\Core\Action\ConfigurableActionBase
该基类实现了以下关键接口:
\Drupal\Core\Plugin\PluginFormInterface
\Drupal\Component\Plugin\ConfigurableInterface
\Drupal\Component\Plugin\ConfigurablePluginInterface
自定义插件可参考以下系统提供的插件:
\Drupal\Core\Action\Plugin\Action\PublishAction
\Drupal\node\Plugin\Action\UnpublishByKeywordNode
动作配置实体:
实体类型id:action
实体类:\Drupal\system\Entity\Action
动作配置实体用于控制某动作的启禁用状态,也为高级动作储存所需的配置参数,系统默认建立了一些简单动作的动作配置实体以允许常规功能(如发布、删除等),为高级动作建立动作配置实体则需要启用核心模块:“action”,该模块和动作配置实体的关系类似于视图UI模块和视图实体的关系,用于为动作提供用户操作接口,在默认标准安装中没有启用,启用后将建立菜单:管理>配置>系统>动作,路径如下:
/admin/config/system/actions
该菜单将显示已建立的动作配置实体及添加操作,和视图一样,当配置好动作实体后,可以卸载动作模块而不影响动作使用,下文将假设动作模块处于启用状态。
动作配置实体各处理器设置如下:
Array
(
[access] => Drupal\Core\Entity\EntityAccessControlHandler
[storage] => Drupal\Core\Config\Entity\ConfigEntityStorage
[form] => Array
(
[add] => Drupal\action\Form\ActionAddForm
[edit] => Drupal\action\Form\ActionEditForm
[delete] => Drupal\action\Form\ActionDeleteForm
)
[list_builder] => Drupal\action\ActionListBuilder
)
启用某个动作即是建立一个使用对应动作插件的动作配置实体,取消某动作即是删除或禁用对应的动作配置实体,一个动作插件可对应多个动作配置实体;高级动作所需的配置在动作配置实体编辑表单中设置,系统是否能应用某个动作是面向动作配置实体的,而不面向动作插件,某动作启用后,在动作的应用页面将出现在可选应用动作的选择下拉列表中
注意事项:
高级动作插件的表单构建、提交方法的实现:
构建配置表单时,无需考虑配置项在表单中的位置,且提交时保存到插件自己的配置属性即可,这是因为动作配置实体实现了以下接口:
EntityWithPluginCollectionInterface
实现该接口的实体在保存时会将插件的配置保存到特定属性下,这里是“configuration”键,详见:
\Drupal\Core\Config\Entity\ConfigEntityBase::preSave
可参考:\Drupal\node\Plugin\Action\UnpublishByKeywordNode
部分简单动作配置实体的自动维护:
部分简单动作的动作配置实体系统会自动维护,比如添加、移除角色;参考以下函数:
user_user_role_insert(RoleInterface $role)
user_user_role_delete(RoleInterface $role)
system_post_update_change_action_plugins()
system_post_update_change_delete_action_plugins()
动作的调用:
用到动作的页面(动作应用页),比如内容管理页,动作是如何出现在这里的呢?通常是借助视图模块(详见视图篇)。
在视图模块的视图数据钩子(views_views_data())实现中,为每一个有可用动作的实体类型注册了批量更新字段,默认的视图字段插件为:bulk_form,该逻辑如下:
foreach (\Drupal::entityTypeManager()->getDefinitions() as $entity_type => $entity_info) {
$actions = array_filter(\Drupal::entityTypeManager()->getStorage('action')->loadMultiple(), function (ActionConfigEntityInterface $action) use ($entity_type) {
return $action->getType() == $entity_type;
});
if (empty($actions)) {
continue;
}
$data[$entity_info->getBaseTable()][$entity_type . '_bulk_form'] = [
'title' => t('Bulk update'),
'help' => t('Allows users to apply an action to one or more items.'),
'field' => [
'id' => 'bulk_form',
],
];
}
页面的渲染会用到以下表单:
\Drupal\views\Form\ViewsForm
如果用户点击应用某动作,默认的执行入口如下:
\Drupal\views\Plugin\views\field\BulkForm::viewsFormSubmit(&$form, FormStateInterface $form_state)
在程序中调用动作:
程序代码示例如下:
$actionEntityID = 'geiyonghuxianshiyitiaoxinxi';
$actionEntity = \Drupal::entityTypeManager()->getStorage('action')->load($actionEntityID);
$entities = [1];
$actionEntity->execute($entities);
$definition = $actionEntity->getPluginDefinition();
if (!empty($definition['confirm_form_route_name'])) {
//重定向页面,继续完成动作
}
系统默认提供的动作:
取消发布包含关键词的内容:
插件id:node_unpublish_by_keyword_action
类:\Drupal\node\Plugin\Action\UnpublishByKeywordNode
仅能用于节点,只要在完整视图模式中页面会输出包含关键词的内容即取消发布
给当前用户显示一个消息:
插件id:action_message_action
类:Drupal\Core\Action\Plugin\Action\MessageAction
该动作一般给程序使用,该插件实现上有bug,调用占位符服务时忘记传递用户上下文数据,因此关于用户的占位符无法替换
更改内容的作者:
插件id:node_assign_owner_action
类:\Drupal\node\Plugin\Action\AssignOwnerNode
仅应用于节点,这是一个高级动作,需要在动作实体编辑页中先配置更改到的用户
重定向url:
插件id:action_goto_action
类:\Drupal\Core\Action\Plugin\Action\GotoAction
在系统核心响应事件中添加一个实现重定向URL的侦听器,从而实现页面重定向
发送邮件:
插件id:action_send_email_action
类:\Drupal\Core\Action\Plugin\Action\EmailAction
向某个邮件地址发送一封邮件,地址和邮件内容在动作配置实体中设置,如果该邮件属于某个账户的,那么会用该账户的首选语言发送,否则采用站点默认语言
取消发布包含设定关键词的评论:
插件id:comment_unpublish_by_keyword_action
类:\Drupal\comment\Plugin\Action\UnpublishByKeywordComment
仅能用于评论,只要在评论的完整视图模式中输出会包含关键词,那么该评论即取消发布
补充:
1、通过本篇所述知识点,推荐读者尝试实现以下两个功能:
自定义一个批量替换字段内容的动作,参考模块:
https://www.drupal.org/project/views_bulk_edit
https://www.drupal.org/project/bulk_update_fields
https://www.drupal.org/project/views_bulk_operations
自定义一个动作,在用户列表中添加批量发送邮件功能,参考系统默认提供的发送邮件动作
2、通常“动作”是作用于实体的,如果是非实体的数据如何运用动作呢?比如一个自定义的数据表,优雅的实现通常需要结合视图模块提供的基础功能,先实现一个该表的批量更新视图字段,修改视图数据定义,再依据这些实现去自定义专用的动作。