131. 批处理batch
批处理用于将一个大的重型任务分派在多个请求中分步逐渐完成,以防止php超时或内存超限,同时向用户显示进度信息,这样的使用场景较多,比如模块安装、系统更新、内容采集等;就具体执行上,批处理开始后用户会被重定向到一个显示进度条的页面,该页面叫做“批处理执行页”,在客户端(通常是浏览器)有JS支持时,批处理执行页不断向服务器发起AJAX请求以逐步执行任务,请求返回进度信息,在没有JS支持时,批处理执行页将不断自我刷新以执行任务,不论是AJAX还是自我刷新,每次请求后批处理执行页都会更新进度信息以告知用户进度如何。
通过多次请求逐步完成任务是批处理最常使用的情况,这样的批处理叫做“渐进progressive”式批处理,drupal的实现中也支持“非渐进non-progressive”式的批处理,即在一个请求中完成所有任务,非渐进的批处理无法向用户显示进度信息,通常不用批处理也能完成,因此这很少使用。
在绝大多数情况下,批处理操作是在一个表单提交后执行,因此Batch API 被集成到Form API,但Batch API可以被单独使用,不限于表单,比如更新脚本、或点击一个按钮。
批处理和队列、计划任务的区别:
1、批处理和队列都可以用来分步完成重型任务,但通常队列是在用户无感知的后台静默执行,而批处理是在“批处理执行页”的触发下执行,该页面向用户显示进度消息
2、批处理用到了队列,队列可视为更加底层的概念
3、批处理是在当前用户会话下显式执行,而计划任务是在匿名用户账户下后台静默执行
可能读者已经见过批处理的执行,在安装系统或模块后,会使用批处理功能进行翻译更新,页面显示正在更新配置翻译,批处理执行页url类似如下:
http://www.dp.com/zh-hans/batch?id=103&op=start
为了读者有更加深刻和直接的理解,这里提供两个示例代码。
示例一:表单批处理示例:
为演示起见,推荐你安装本系列配套模块“yunke_help”,这里假设你已安装并在此基础上进行演示
一、首先在模块文件(yunke_help.module)中添加几个函数用来执行实际的任务,如下:
/**
* 会反复执行的批处理回调,是否反复执行由回调决定
* @param $context 系统传递的上下文数组
*/
function yunkeBatch1(&$context)
{
if (empty($context['sandbox'])) {
$context['sandbox']['count'] = 0;
}
//执行一些事情,比如数据库操作,为演示作用,这里暂停100毫秒
usleep(100000);
$context['sandbox']['count']++;
//在同一个回调反复调用中$context['sandbox']可用来传递数据
$context['finished']=$context['sandbox']['count']/100;
//$context['finished']用来指示当前回调是否还需要被继续调用,值>=1即不再调用
$context['message']='正在处理:'.$context['sandbox']['count'];
//在进度条上方显示的消息
$context['results'][]=$context['sandbox']['count'];
//收集处理结果,将传递给完成回调
}
/**
* 执行一次的回调,除最后一个参数外,其他参数在批定义中传递
* @param $count 在批定义中传递的参数
* @param $context 系统传递的上下文数组
*/
function yunkeBatch2($count,&$context)
{
//执行一些事情,为演示作用,这里暂停200毫秒
usleep(100000);
//只调用一次时不必理会$context['finished'],她每次调用的初始值为1
$context['message']='正在处理:'.$count;
$context['results'][]=$count;
}
/**
* 批处理完成回调,所有批集执行完成后统一执行她们的完成回调
* @param $success 布尔值,是否处理成功
* @param $results 处理结果
* @param $operations 未成功时剩余未调用的回调,成功时为一个空数组
*/
function yunkeBatchFinished($success, $results, $operations){
//完成回调,如果返回一个重定向响应,在批处理完成后将重定向到其位置
$messenger = \Drupal::messenger();
if ($success) {
$messenger->addMessage(t('成功处理:@count', ['@count' => count($results)]));
$messenger->addMessage(t('他们是:(%final)', ['%final' => implode(',',$results)]));
}
else {
$error_operation = reset($operations);
$messenger->addMessage(
t('处理失败,失败时的操作为: @operation 参数为:@args',
[
'@operation' => $error_operation[0],
'@args' => print_r($error_operation[0], TRUE),
]
)
);
}
}
二、其次建立一个执行批处理的表单src/Form/YunkeForm.php,内容如下:
<?php
/**
* 演示批处理
*/
namespace Drupal\yunke_help\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
class YunkeForm extends FormBase
{
public function getFormId()
{
return 'yunke_help_form';
}
public function buildForm(array $form, FormStateInterface $form_state)
{
$form['#title'] = '批处理执行演示';
$form['description'] = [
'#type' => 'markup',
'#markup' => '这里将演示几种批处理类型:反复执行一个批处理回调及连续执行多个批处理回调',
];
$form['batch'] = [
'#type' => 'select',
'#title' => '选择要执行的批处理',
'#options' => [
'batch_1' => '1反复执行一个回调',
'batch_2' => '2连续执行多个回调',
'batch_3' => '1,2混合',
],
];
$form['submit'] = [
'#type' => 'submit',
'#value' => '开始',
];
return $form;
}
public function validateForm(array & $form, FormStateInterface $form_state)
{
//演示起见,无需验证
}
public function submitForm(array & $form, FormStateInterface $form_state)
{
$value = $form_state->getValues()['batch'];
$batch = [];
switch ($value) {
case 'batch_1':
$batch = $this->buildBatch1();//返回批处理定义
break;
case 'batch_2':
$batch = $this->buildBatch2();//返回批处理定义
break;
case 'batch_3':
$batch = $this->buildBatch3();//返回批处理定义
break;
}
batch_set($batch);
//添加一个批集 由于表单系统集成了批处理API,因此提交处理器会进行后续功能
//batch_set($this->buildBatch3());//可以继续添加批集
}
public function buildBatch1()
{
$operations = [
['yunkeBatch1', []],
];
//构造批定义数组
$batch = [
'title' => '正在执行批处理(反复执行一个回调)',
'operations' => $operations,
'finished' => 'yunkeBatchFinished',
'init_message' => '初始化中',
'progress_message' => '已完成: @current 共: @total.',
'error_message' => '发生了一个错误',
];
return $batch;
}
public function buildBatch2()
{
$operations = [];
for ($i = 1; $i <= 100; $i++) {
$operations[] = ['yunkeBatch2', [$i]]; //可传递回调参数
}
$batch = [
'title' => '连续执行多个回调',
'operations' => $operations,
'finished' => 'yunkeBatchFinished',
'init_message' => '初始化中',
'progress_message' => '已完成: @current 共: @total.',
'error_message' => '发生了一个错误',
];
return $batch;
}
public function buildBatch3()
{
$operations = [
['yunkeBatch1', []],
];
for ($i = 1; $i <= 50; $i++) {
$operations[] = ['yunkeBatch2', [$i]];
}
$batch = [
'title' => '既反复执行一个回调,也连续执行多个回调',
'operations' => $operations,
'finished' => 'yunkeBatchFinished',
'init_message' => '初始化中',
'progress_message' => '已完成: @current 共: @total.',
'error_message' => '发生了一个错误',
];
return $batch;
}
}
三、执行批处理:
在控制器中执行前文建立的表单,控制器方法如下:
\Drupal\yunke_help\Controller\Test::test
在其中添加如下代码:
$form = \Drupal::formBuilder()->getForm("\Drupal\yunke_help\Form\YunkeForm");
return $form;
现在可以点击“yunke_help”模块首页的“测试”按钮了,或访问地址:
http://www.dp.com/yunke-help/test
选择一个选项提交表单以查看批处理的运行
示例二:非表单方式批处理示例:
以上示例是在表单提交的情况下执行的,我们也可以在点击一个页面按钮后直接开始,在以上示例的第一步基础上演示,此时已经添加了执行回调,然后跳过第二步,直接在第三步的控制器中添加以下代码:
$operations = [];
for ($i = 1; $i <= 100; $i++) {
$operations[] = ['yunkeBatch2', [$i]]; //可传递回调参数
}
$batch = [
'title' => '连续执行多个回调',
'operations' => $operations,
'finished' => 'yunkeBatchFinished',
'init_message' => '初始化中',
'progress_message' => '已完成: @current 共: @total.',
'error_message' => '发生了一个错误',
];
batch_set($batch);
return batch_process('<front>');
此时点击“测试”按钮后将直接开始批处理,执行完成后跳转到首页(可依据需求跳转到任意页面)
说明:
系统将整个批处理过程封装了起来,开发者无需理会内部过程,只需要面对三个批处理核心交互函数(接口函数)即可。
在流程上,要使用批处理功能先需要调用以下批处理设置函数进行批处理定义:
batch_set($batch_definition)
每调用一次就添加了一个批处理集合,简称批集,可多次调用以添加多个批集一起执行,批处理至少需要一个批集,进度显示是相对于批集的,一个或多个批集构成一次完整运行批处理的批设置,如果需要查看或修改批处理设置则使用以下函数(引用返回):
最后要启动批处理,调用以下批处理执行函数即可:
batch_process($redirect = NULL, Url $url = NULL, $redirect_callback = NULL)
以上三个接口函数下文会专门讲解;由于批处理的执行通常是由表单提交开始的,因此她们被放置在表单系统中(/core/includes/form.inc),表单API集成了批处理,在表单提交处理器中,如需使用批处理,则只需调用批处理设置函数即可,而不必调用批处理执行函数,系统发现有批设置时会自动调用处理
批定义数组(batch definition):
批处理设置函数用于添加一个批处理集合,她接受一个定义数组$batch_definition作为参数,该数组叫做批定义数组,或批定义,用于指定详细的批处理内容,数组中有以下键名(注:除了操作外其他全部是可选的):
operations(操作):
必选,被批处理系统执行的操作,一个由“批操作”构成的数组,如下所示:
array(
array('callback_batch_operation_1', array($arg1)),
array('callback_batch_operation_2', array($arg2_1, $arg2_2)),
)
每一个元素均是一个操作,每个操作由一个批操作回调和回调参数组成,批操作回调的实现见下文,如无需参数,则元素中参数项置为空数组即可
title(标题):
一个安全的、已翻译的字符串(或翻译对象),默认是“处理中”,用于处理页面显示,注意她和以下选项:
init_message、progress_message、error_message
的消毒处理是调用代码的责任,在可能包含用户输入时,消毒是必须的,此外如果批处理回调返回的信息中包含用户输入,如上下文变量$context中的results、message键(见下文),也需要消毒处理,通常使用:
\Drupal\Component\Utility\Html::escape()
\Drupal\Component\Utility\Xss::filter()
init_message(初始化消息):
在即将开始批处理时显示,要求同标题,默认为“初始化中”
progress_message(处理中消息):
当处理正在进行中时显示,默认为t('Completed @current of @total.'),要求同标题,可用的占位符有:@current(当前序号)、 @remaining(剩余)、@total(总数)、@percentage(百分比)、@estimate(当前批集估算还需时间,单位秒)、@elapsed(当前批集已执行时间,单位秒)。这些替换变量全部是相对于当前批集而言的,由于可以设置很多批集,她们并不是相对于总批处理任务;
其中剩余时间估算原理是用当前完成一个批处理回调的平均用时去乘以当前批集中剩余未处理回调的总数(在第一个都还未执行时,无法估算,显示“-”),因此有时该估算会严重失准
error_message(错误消息):
当发生错误时显示,要求同标题,默认为:t('An error has occurred.')
finished(完成回调):
批处理完成回调,在批处理完成或发生错误时执行,用于处理显示或分析等善后工作,值为一个回调设置,并非多个回调构成的数组,批定义中无参数传递,实现时需要的参数及详解见下文
file(须加载文件的路径)
当相关函数不在主“.module”文件中时,很有用,指定路径,在批处理运行时批系统自动加载,必须是一个相对系统根目录的相对路径,不带“/”前缀,加载实现如下:
include_once \Drupal::root() . '/' . $current_set['file'];
这在未来会改进以允许绝对路径
library(资源库):
一个数组,包含批处理所需的资源库,在批处理执行页中加载
queue(队列):
用于储存批集中批回调的队列,可选,一个数组,包含两个元素:
name:队列名,默认为('drupal_batch:' . $batch['id'] . ':' . $set_id,),变量为批id和集id
class:队列对象类名,如果步进参数为true时,默认将是
\Drupal\Core\Queue\Batch
否则将是:
\Drupal\Core\Queue\BatchMemory
构造函数按序接收两个参数:队列名和数据库连接
批集(batch sets)
一个数组,是批定义数组的超集(许多多出部分在程序内部使用),一个批定义对应一个批集,可以添加多个批集,在批处理运行时将依次执行,如果批处理已经在运行了,此时添加批集将紧接着在当前批集后面(其他未执行批集前面)执行,批集中系统在批定义数组基础上额外附加了以下键:
sandbox:用于传递数据,详见下文的批操作回调
results:收集处理结果,详见下文的批操作回调
success:该集处理是否已成功(回调是否已经成功调用完成),开始时默认为false
start:开始时间,值为microtime(TRUE);,单位秒,浮点数,精确到微秒
elapsed:已处理用时,单位毫秒,浮点数,精确到小数点后两位
total:当前批集中批操作总数(批操作回调数量)
count:计数,初始和总数相同,但在处理过程中会变化,成功一个将减少一个
form_submit:一个回调,可选的用在表单提交中,参数应以引用接收完整表单和表单状态对象,在批集执行前调用,可用来添加更多批集等,是批系统和表单系统交互的一个桥梁
批设置:
也就是调用batch_get()函数返回的值,批设置可包含多个批集,是批集的超集,一次批处理执行一个批设置,每个批设置有一个独一无二的id,有如下键:
sets:
是一个数组,每个元素是一个批集
has_form_submits:
布尔值,指示是否有表单提交
progressive:
一个布尔值,指示批处理是否渐进(是否需要一步一步的在多个请求中完成),默认为TRUE,当为FALSE时,批处理任务将在当前请求中完成,改变该值须在设置批定义后用&batch_get()完成,所有批集遵循相同设置
url:
URL对象,批处理执行页面(该页面会显示消息及进度等),默认为路由system.batch_page.html对应页
source_url:
一个URL对象,表示批处理启动时的页面,包含着查询参数
batch_redirect:
一个路径字符串或URL对象,指示批处理完成后重定向到哪里
theme:
字符串值,活动主题名
bug:本意是设置一个重定向回调来处理渐进式批处理的重定向逻辑,但云客发现关于这个选项的处理并不正确,系统中也未见使用场景,因此读者请忽略该选项
处理该选项的逻辑位于:_batch_finished()和batch_process函数的末尾,修正:系统应在该函数的调用处加返回语句
id:
每一个批处理在运行前都会分配一个id,默认是\Drupal::database()->nextId();,在修改钩子“batch”执行后赋值(该钩子见下文),如果存在该id说明批处理已经开始运行,注意:每个批设置对应一个批id,但批设置可能包含很多批集
current_set:
批处理运行中的当前集,默认为0
error_message:
发生错误时显示的消息,批设置中的错误消息和批集(批定义数组)中的错误消息不一样,当发生错误时,显示在批集中错误消息的后面,显示一个错误发生后的跳转链接(该链接去执行批完成回调)
destination:
如果启动批处理的页面的url中存在该查询参数,将复制到批设置数组中(并从请求对象中删除),否则没有,关于该参数的更多信息详见响应事件订阅器服务“redirect_response_subscriber”
running:
如果批处理正在执行,那么会存在该键名,值为true,如果没有运行则不会存在该键名(而不是false)
批操作回调:
一个函数,用于实际执行批处理操作,依次接收批定义时传递的参数,此外最后一个参数为系统附加,称为上下文变量$context,是一个数组,用于收集执行过程中的信息,以引用方式传递,其键名如下:
当一个批集中,同一个批操作回调需要执行多次时,该回调不必在批集中设置多次,仅设置一次即可;比如有一千个节点实体需要处理,每次只处理一百个,处理逻辑是一样的,因此批处理回调完全相同,此时批集中该回调不必设置十次,只需要设置一次即可;那么批系统如何知道该回调需要继续执行呢?这就是该选项的作用,她应是一个在0和1之间的实数,表示在该批处理回调这个级别的完成进度,每一次回调运行时,她的初始值均是1,回调完成执行后应依据进度设置她的值,如果设置为大于等于1则表示该回调的工作已经完成(如不明确设置则保持默认值为1,此时也表示完成),批系统将从队列中删除该批处理回调,回调不再会被执行,否则如果小于1,则并不删除,批系统将继续执行该回调
sandbox:
在前面的finished选项解释中,我们知道同一个批处理回调可以被反复调用,以完成该回调级别的进度工作,那么每一次调用之间需要传递数据怎么办?比如数据库查询的偏移量,该选项的作用就用于此,里面可以储存批处理回调需要的任意数据,她以引用方式连接到批集中,而批集设置会储存到数据库中,因此该项数据可以跨请求存在,但仅用于在同一个批回调的反复调用之间传递数据,不能跨不同回调之间存在(同一个回调在批集中设置两次或以上,那么算是不同回调),在另一个回调开始前该项会被重置为空数组,该变量被批系统初始化为空数组,回调可以用empty函数进行判断并设置初始值,注意:回调不要用$_SESSION代替此选项,虽然也可以跨请求,但需要开发者自行清理数据,而该选项会在不同回调间自动清理
message:
批处理回调通过该选项来传递批处理执行页中显示的文本消息,一个字符串值或翻译对象,也就是进度条上方的label消息
results:
以引用方式连接到批集的results项上(因此储存于数据库中,可以跨请求存在),用于收集当前批集执行过程中,到当前为止收集的处理结果,批处理回调应追加数据,而不要操作已有数据,在当前批集处理完成后将被传递给批完成回调,完成回调可以据此做总结分析或显示一些有用的信息,比如一共处理了多少条目
批完成回调:
一个函数,在批操作完成或发生错误时执行,批完成回调通常用于向用户显示信息、分析处理结果、清理善后等,是可选的,按顺序接收以下四个参数:
$success:
布尔值,指示批操作是否成功,来自批集的“success”项,这里“成功”的含义仅指批处理回调是否调用完成(在队列中被消费完成),而不是指批处理的目的是否达成,当所有批操作执行完成后系统会自动将该值设置为true,批处理回调一般不需要处理该值,批处理任务的目的是否成功或成功多少,由完成回调分析后显示给用户。
$results:
在批处理过程中上下文参数收集的处理结果($context['results']),通常为数组
$operations:
如果失败了,包含未处理的所有批处理回调,来自:$queue->getAllItems();,第一个元素为失败时的那个操作,成功时为空数组
$elapsed:
一个被翻译过的时间字符串,表示处理该批集的时耗
所有批集完成后再统一运行她们的完成回调,批完成回调可以返回一个重定向响应,如果批设置中有多个批集的完成回调都返回了重定向响应,那么以最后一个为准,在批处理为渐进式时,返回的重定向响应才有作用,其优先级高于其他任何设置(比如批设置中的重定向路径)
批处理核心交互函数介绍:
主要函数:
function batch_set($batch_definition)
添加批集定义,如果在批处理已经开始时调用,那么新添加的批集会紧接着当前批集完成后(未执行批集的前面)执行
function &batch_get()
以引用方式取回整个批设置
function batch_process($redirect = NULL, Url $url = NULL, $redirect_callback = NULL)
开始执行批处理,在表单提交处理器中不需要调用该函数,系统会自动调用,如果是其他场景需要明确调用以启动批处理,参数全部是可选的,含义如下:
$redirect:
一个字符串或URL对象,用于指示完成批处理后重定向到哪里,批完成回调返回的重定向响应优先级最高,其次是该参数设置,如果省略将重定向到开始批处理的页面(任意查询参数将被自动附加),到首页可以用“<front>”,如果是在表单处理器中,因为不必调用该函数,因此只需要在表单状态对象中设置重定向或在批完成回调中返回重定向响应即可(后者优先级高),重定向响应对象如下:
\Symfony\Component\HttpFoundation\RedirectResponse
$url:
一个url对象,表示批处理执行页的url,通常不需要,系统有默认的批处理执行页,有特殊需求时可以指定
$redirect_callback:
重定向回调,详见批设置中的redirect_callback选项
在该函数中,启动批处理前,会派发修改钩子“batch”,模块可以修改批设置数组,此时尚未赋值批id
function _batch_populate_queue(&$batch, $set_id)
将单个批集中的批操作填充到队列中,并删除批集中的操作项(operations),默认的如果是渐进的批处理将使用以下队列:
Drupal\Core\Queue\Batch
否则使用:
Drupal\Core\Queue\BatchMemory
默认队列名为
('drupal_batch:' . $batch['id'] . ':' . $set_id,)
详见默认批队列
function _batch_queue($batch_set)
实例化队列对象,将其静态缓存并返回
默认批队列:
关于队列的更多知识请参看本系列队列主题
渐进式批处理默认队列:
类:Drupal\Core\Queue\Batch
继承自系统默认的数据库批队列,同样使用默认的“queue”数据表,但做了如下调整:
1、没有了租约时间的限制
2、队列条目可以被反复取回,直到被删除为止
3、添加了取出全部条目的方法:getAllItems()
非渐进式批处理默认队列:
类:Drupal\Core\Queue\BatchMemory
继承自内存式队列,这种队列特点是不能跨请求,队列数据驻留在内存中;同样做了和渐进式批处理默认队列相同的修改
批设置储存服务:
服务id:batch.storage
类:Drupal\Core\Batch\BatchStorage
用于储存、取回、更新、清理批设置,使用数据库表“batch”,该表有以下字段:
bid:
批设置ID,整数,主键,不能为NULL
token:
字符串值,保护令牌,确保只有提交批设置的用户才能执行批处理,由跨域保护服务产生:
\Drupal::service('csrf_token')->get($batch['id']);
timestamp:
时间戳,表示批设置被储存的时间(储存时,当前请求的请求时间)
batch:
批设置数组,被序列化保存
方法说明如下:
public function create(array $batch)
将批设置保存到数据库中,储存的批设置中,各批集的操作已经被放入了队列,并已从批设置中删除
public function cleanup()
清理无效的批设置,保存时间超过10天(864000秒)的批设置被视为无效的
public function update(array $batch)
更新批设置
public function delete($id)
删除批设置,参数为批id
public function load($id)
加载批设置,成功时返回批设置数组(已解序列化),失败时返回false
bug提示:该储存器在类常量TABLE_NAME中定义了表名,但在方法中却直接使用“batch”做表名,而没有使用常量,这是不对的
批处理执行页:
批处理前端页面,用于触发后端真实执行批处理,首先输出一个完整的批处理执行页面,然后依据是否有JS支持分两种情况:
在有JS支持时将通过AJAX不停向后端发送请求去执行批操作,并获取执行进度、提示消息等反馈到页面,在没有JS支持时,采用刷新meta标签不停更新整个页面;
输出完整批处理执行页默认为以下路由:
system.batch_page.html:
path: '/batch'
defaults:
_controller: '\Drupal\system\Controller\BatchController::batchPage'
_title_callback: '\Drupal\system\Controller\BatchController::batchPageTitle'
requirements:
_access: 'TRUE'
_format: 'html'
options:
_admin_route: TRUE
在有JS支持时,发送的AJAX请求路由为:
system.batch_page.json:
path: '/batch'
defaults:
_controller: '\Drupal\system\Controller\BatchController::batchPage'
requirements:
_access: 'TRUE'
_format: 'json'
options:
_admin_route: TRUE
AJAX路由和完整页路由使用相同控制器,但仅获取json格式的数据,如果没有JS支持,那么刷新跳转时,依然跳转到完整页路由;
以上两个路由均由系统模块提供,批处理执行页面用到了page 元素类型('#type' => 'page'),因此页面不会附加无关的块(本系列配套模块yunke_help的首页显示也用到了这一点,感兴趣的读者可以深入研究),默认情况下整个页面采用以下模板输出:
core/themes/classy/templates/misc/progress-bar.html.twig
使用了批处理前端库core/drupal.batch以提供批处理执行页的js功能支持,库定义如下:
drupal.batch:
version: VERSION
js:
misc/batch.js: { cache: false }
dependencies:
- core/jquery
- core/drupal
- core/drupalSettings
- core/drupal.ajax
- core/drupal.progress
- core/jquery.once
请读者查看以下文件了解其逻辑:
/core/misc/batch.es6.js
该库比较简单,建立在前端进度条库之上(可参考本系列前端部分:进度条主题),有JS支持时,完全依赖进度条库的API处理(POST请求),没有JS支持时,该库无任何作用,将用meta标签反复刷新完整页面
注意:不管是否有js支持,都不会造成服务器并发(每个请求完成后才开始下一个请求),在有js时每次收到请求后间隔10毫秒发送下一次更新请求,没有js时,浏览器得到响应后立即开始下一次请求
批系统函数介绍:
批系统执行细节涉及的函数储存在以下文件中:
/core/includes/batch.inc
通常我们不必关注这些细节,了解接口函数即可,因此这些函数都以下划线开始,但为了介绍原理,说明如下:
function _batch_page(Request $request)
构建前端批处理执行页的核心方法,有三种可能的返回值:
布尔false:请求没有传递批ID时返回
响应对象:当没有批设置时重定向到首页的重定向响应,或AJAX请求的json响应
渲染数组:返回批处理执行页面
查询参数“op”的值有4种:
start:首次请求批处理执行页时使用,此时会返回完整的页面
do_nojs:当客户端禁用了JS时使用,此时采用meta标签不停刷新页面,因此也需返回完整页面
do:在客户端有js支持时,发送的AJAX请求使用,此时仅需返回json更新信息即可
finished:批处理完成后使用,执行批处理完成回调
需要注意:不管有一个还是有多个批集,她们的资源库是一起加载到批处理执行页中的
function _batch_shutdown()
关机函数(在脚本结束后运行),当执行发生致命错误而停止时,无法更新批设置,因此系统将该函数注册为关机函数,关机函数不论脚本是否发生异常或致命错误,在结束时总是运行
function _batch_needs_update($new_value = NULL)
设置或获取批设置是否需要更新,在需要时必须调用该方法,关机函数据此判断是否需要更新
function &_batch_current_set()
返回批处理过程中,当前正在处理的批集,以引用方式返回
function _batch_progress_page()
返回批处理执行页的完整页面内容,由于执行批处理时可能会发生严重的php错误导致处理中断,此时用户可能收不到反馈,所以在该方法中提前准备了回退显示的错误消息,将其缓存在输出缓冲中,一旦错误发生就会自动输出,如果没有发生错误,该消息将被清除;批处理前端库在该方法中添加
function _batch_do()
在前端批处理执行页支持JS时,返回由JS发起的AJAX请求的响应,json格式,包含标准的进度条更新信息:
['status' => TRUE, 'percentage' => $percentage, 'message' => $message, 'label' => $label]
其中label显示在进度条上方(页面标题下方),message显示在进度条下方
function _batch_process()
执行批处理任务的核心方法,循环执行批集中的批处理回调,直到时间超过一秒,或一个批集处理完成,然后返回一个信息数组,有三个元素:
百分比(当前批集完成百分比)、执行消息(处理中消息总结)、label(当前批回调返回的消息)
如果为非渐进式批处理,该方法将直接处理批完成回调
function _batch_next_set()
将批处理切换到下一个批集,如果该集存在“form_submit”回调,将在执行前执行该回调,如果存在下一个集则返回true,否则返回NULL
读者可能已经注意到:批设置中,第一个批集如果存在“form_submit”回调,则得不到执行,可能认为这是一个bug,但该回调的调用仅是为了执行表单的提交处理器,在那种场景下第一个批集不应该设置此回调
function _batch_finished()
批处理执行完成后或发生错误时调用,统一执行批集的完成回调,清理批设置储存、会话数据和批设置静态变量;批完成回调可以返回重定向响应,在批处理为渐进式时,该重定向响应才有作用,且优先级高于其他任何设置,当有多个批集的完成回调都返回重定向响应时,以最后一个为准,在没有完成回调返回重定向响应时,重定向确定逻辑如下:
1、批设置中表单状态对象(如没有表单状态对象则实例化一个),如有重定向设置,则以此设置为准
2、如没有则以批设置中的“batch_redirect”项为准
3、如还是没有,则以批设置中的“source_url”项为准
4、如果“batch_redirect”项或“source_url”项不是Url对象且不是有效地址,将使用当前页,通常是批处理执行页
批处理重定向优先级:
渐进式批处理完成后,重定向的优先级如下,依次递减:
1、批完成回调返回的重定向响应,如果有多批集多个返回,则以最后一个为准
2、如果是在表单提交中执行的批处理,那么其次是表单状态对象中设置的重定向
3、批设置中的“batch_redirect”项设置
4、批设置中的“source_url”项设置(启动批处理的页面)
5、批处理执行页
批构建器:
用于在程序上辅助构造批定义数组
类:\Drupal\Core\Batch\BatchBuilder
其toArray()方法返回的值直接用于批设置函数,该类比较简单,不多讲
补充:
1、前端禁用JS的情况下,批处理执行页使用meta刷新标签实现跳转,如:
<noscript><meta http-equiv="Refresh" content="0; URL=/zh-hans/batch?id=113&op=do_nojs" />
</noscript>
2、批处理每一个请求的执行时间被控制在1秒左右,单个回调超过一秒时时间会延长,如果单个回调的执行时间超过php允许的最大执行时间,那么即使采用批处理仍然会超时中断,在这样的情况下,回调需要去延长最大执行时间,最佳实践是让回调的工作控制在一秒内
3、批处理执行页中的进度百分比是指在当前批集中的完成百分比,而不是整个批处理任务的完成百分比,由于可以设置很多批集,因此百分比在切换批集后可能会回退变少
4、如果在批处理执行页的进度显示期间关闭或离开页面,则批处理回调会驻留在队列中,在计划任务执行时,列队垃圾回收中,十天以前的批处理回调会被清除,详见:
\Drupal\Core\Queue\DatabaseQueue::garbageCollection
但批设置储存器中的数据会一直残留,可清理数据库表“batch”,或使用本系列配套模块“yunke_help”删除(V2.1.0及以上版本)
5、在批处理过程中,用户不应该关闭批处理执行页,这会导致处理工作遗漏,如发生意外导致中断时,比如断网,应该刷新继续执行,如页面已经被关闭,可使用如下地址继续执行:
http://www.dp.com/batch?id=122&op=start
其中id值应该替换为被中断的批id,如不知道请查看数据库表“batch”中的“bid”字段
6、由于批处理可能会被意外中断,因此批处理回调需要确保数据正确性,开发者需要特别注意
BUG提示:
1、连续执行多个批集时,后续批集无法更新批处理执行页面的标题
2、批定义数组中无url_options、progressive选项,但注释文档中说有,需要修正
3、批设置的“redirect_callback”选项实现有问题,见该选项的说明