Skip to content

Commit 3ed5581

Browse files
johncookpaul-m
authored andcommitted
Issue #2102643 by Mile23, John Cook, Kristen Pol: Port batch_example module to Drupal 8
1 parent 01c3ee7 commit 3ed5581

File tree

8 files changed

+425
-0
lines changed

8 files changed

+425
-0
lines changed

batch_example/batch_example.info.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
name: Batch Example
2+
type: module
3+
description: An example outlining how a module can define batch operations.
4+
package: Example modules
5+
core: 8.x
6+
dependencies:
7+
- examples
8+
- toolbar

batch_example/batch_example.install

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
/**
4+
* @file
5+
* Install, update, and uninstall functions for the batch_example module.
6+
*/
7+
8+
/**
9+
* Example of batch-driven update function.
10+
*
11+
* Because some update functions may require the batch API, the $sandbox
12+
* provides a place to store state. When $sandbox['#finished'] == TRUE,
13+
* calls to this update function are completed.
14+
*
15+
* The $sandbox param provides a way to store data during multiple invocations.
16+
* When the $sandbox['#finished'] == 1, execution is complete.
17+
*
18+
* This dummy 'update' function changes no state in the system. It simply
19+
* loads each node.
20+
*
21+
* To make this update function run again and again, execute the query
22+
* "update system set schema_version = 0 where name = 'batch_example';"
23+
* and then run /update.php.
24+
*
25+
* @ingroup batch_example
26+
*/
27+
function batch_example_update_8001(&$sandbox) {
28+
// Use the sandbox at your convenience to store the information needed
29+
// to track progression between successive calls to the function.
30+
if (!isset($sandbox['progress'])) {
31+
// The count of nodes visited so far.
32+
$sandbox['progress'] = 0;
33+
// Total nodes that must be visited.
34+
$sandbox['max'] = db_query('SELECT COUNT(nid) FROM {node}')->fetchField();
35+
// A place to store messages during the run.
36+
$sandbox['messages'] = [];
37+
// Last node read via the query.
38+
$sandbox['current_node'] = -1;
39+
}
40+
41+
// Process nodes by groups of 10 (arbitrary value).
42+
// When a group is processed, the batch update engine determines
43+
// whether it should continue processing in the same request or provide
44+
// progress feedback to the user and wait for the next request.
45+
$limit = 10;
46+
47+
// Retrieve the next group of nids.
48+
$query = db_select('node', 'n');
49+
$query->fields('n', ['nid']);
50+
$result = $query
51+
->where('n.nid > :nid', [':nid' => $sandbox['current_node']])
52+
->range(0, $limit)
53+
->orderBy('n.nid', 'ASC')
54+
->execute();
55+
foreach ($result as $row) {
56+
// Here we actually perform a dummy 'update' on the current node.
57+
$node = db_query('SELECT nid FROM {node} WHERE nid = :nid', [':nid' => $row->nid])->fetchField();
58+
59+
// Update our progress information.
60+
$sandbox['progress']++;
61+
$sandbox['current_node'] = $row->nid;
62+
}
63+
64+
// Set the "finished" status, to tell batch engine whether this function
65+
// needs to run again. If you set a float, this will indicate the progress
66+
// of the batch so the progress bar will update.
67+
$sandbox['#finished'] = ($sandbox['progress'] >= $sandbox['max']) ? TRUE : ($sandbox['progress'] / $sandbox['max']);
68+
69+
// Set up a per-run message; Make a copy of $sandbox so we can change it.
70+
// This is simply a debugging stanza to illustrate how to capture status
71+
// from each pass through hook_update_N().
72+
$sandbox_status = $sandbox;
73+
// Don't want them in the output.
74+
unset($sandbox_status['messages']);
75+
$sandbox['messages'][] = t('$sandbox=') . print_r($sandbox_status, TRUE);
76+
77+
if ($sandbox['#finished']) {
78+
// hook_update_N() may optionally return a string which will be displayed
79+
// to the user.
80+
$final_message = '<ul><li>' . implode('</li><li>', $sandbox['messages']) . "</li></ul>";
81+
return t('The batch_example demonstration update did what it was supposed to do: @message', ['@message' => $final_message]);
82+
}
83+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Define default links for this module.
2+
batch_example.form:
3+
title: Batch API Examples
4+
description: Batch examples using Drupal Batch API.
5+
route_name: batch_example.form

batch_example/batch_example.module

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
<?php
2+
3+
/**
4+
* @file
5+
* Outlines how a module can use the Batch API.
6+
*/
7+
8+
/**
9+
* @defgroup batch_example Example: Batch API
10+
* @ingroup examples
11+
* @{
12+
* Outlines how a module can use the Batch API.
13+
*
14+
* Batches allow heavy processing to be spread out over several page
15+
* requests, ensuring that the processing does not get interrupted
16+
* because of a PHP timeout, while allowing the user to receive feedback
17+
* on the progress of the ongoing operations. It also can reduce out of memory
18+
* situations.
19+
*
20+
* The @link batch_example.install .install file @endlink also shows how the
21+
* Batch API can be used to handle long-running hook_update_N() functions.
22+
*
23+
* Two harmless batches are defined:
24+
* - batch 1: Load the node with the lowest nid 100 times.
25+
* - batch 2: Load all nodes, 20 times and uses a progressive op, loading nodes
26+
* by groups of 5.
27+
*
28+
* @see batch
29+
*/
30+
31+
/**
32+
* Batch operation for batch 1: one at a time.
33+
*
34+
* This is the function that is called on each operation in batch 1.
35+
*/
36+
function batch_example_op_1($id, $operation_details, &$context) {
37+
// Simulate long process by waiting 1/50th of a second.
38+
usleep(20000);
39+
40+
// Store some results for post-processing in the 'finished' callback.
41+
// The contents of 'results' will be available as $results in the
42+
// 'finished' function (in this example, batch_example_finished()).
43+
$context['results'][] = $id;
44+
45+
// Optional message displayed under the progressbar.
46+
$context['message'] = t('Running Batch "@id" @details',
47+
['@id' => $id, '@details' => $operation_details]
48+
);
49+
}
50+
51+
/**
52+
* Batch operation for batch 2: five at a time.
53+
*
54+
* This is the function that is called on each operation in batch 2.
55+
*
56+
* After each group of 5 control is returned to the batch API for later
57+
* continuation.
58+
*/
59+
function batch_example_op_2($operation_details, &$context) {
60+
// Use the $context['sandbox'] at your convenience to store the
61+
// information needed to track progression between successive calls.
62+
if (empty($context['sandbox'])) {
63+
$context['sandbox'] = [];
64+
$context['sandbox']['progress'] = 0;
65+
$context['sandbox']['current_node'] = 0;
66+
67+
// Save node count for the termination message.
68+
$context['sandbox']['max'] = 30;
69+
}
70+
71+
// Process in groups of 5 (arbitrary value).
72+
// When a group of five is processed, the batch update engine determines
73+
// whether it should continue processing in the same request or provide
74+
// progress feedback to the user and wait for the next request.
75+
// That way even though we're already processing at the operation level
76+
// the operation itself is interruptible.
77+
$limit = 5;
78+
79+
// Retrieve the next group.
80+
$result = range($context['sandbox']['current_node'] + 1, $context['sandbox']['current_node'] + 1 + $limit);
81+
82+
foreach ($result as $row) {
83+
// Here we actually perform our dummy 'processing' on the current node.
84+
usleep(20000);
85+
86+
// Store some results for post-processing in the 'finished' callback.
87+
// The contents of 'results' will be available as $results in the
88+
// 'finished' function (in this example, batch_example_finished()).
89+
$context['results'][] = $row . ' ' . $operation_details;
90+
91+
// Update our progress information.
92+
$context['sandbox']['progress']++;
93+
$context['sandbox']['current_node'] = $row;
94+
$context['message'] = t('Running Batch "@id" @details',
95+
['@id' => $row, '@details' => $operation_details]
96+
);
97+
}
98+
99+
// Inform the batch engine that we are not finished,
100+
// and provide an estimation of the completion level we reached.
101+
if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
102+
$context['finished'] = ($context['sandbox']['progress'] >= $context['sandbox']['max']);
103+
}
104+
}
105+
106+
/**
107+
* Batch 'finished' callback used by both batch 1 and batch 2.
108+
*/
109+
function batch_example_finished($success, $results, $operations) {
110+
if ($success) {
111+
// Here we could do something meaningful with the results.
112+
// We just display the number of nodes we processed...
113+
drupal_set_message(t('@count results processed.', ['@count' => count($results)]));
114+
drupal_set_message(t('The final result was "%final"', ['%final' => end($results)]));
115+
}
116+
else {
117+
// An error occurred.
118+
// $operations contains the operations that remained unprocessed.
119+
$error_operation = reset($operations);
120+
drupal_set_message(
121+
t('An error occurred while processing @operation with arguments : @args',
122+
[
123+
'@operation' => $error_operation[0],
124+
'@args' => print_r($error_operation[0], TRUE),
125+
]
126+
)
127+
);
128+
}
129+
}
130+
131+
/**
132+
* @} End of "defgroup batch_example".
133+
*/
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
batch_example.form:
2+
path: 'examples/batch_example'
3+
defaults:
4+
_form: '\Drupal\batch_example\Form\BatchExampleForm'
5+
_title: 'Demo of Batch processing'
6+
requirements:
7+
_permission: 'access content'
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<?php
2+
3+
namespace Drupal\batch_example\Form;
4+
5+
use Drupal\Core\Form\FormBase;
6+
use Drupal\Core\Form\FormStateInterface;
7+
8+
/**
9+
* Form with examples on how to use cache.
10+
*/
11+
class BatchExampleForm extends FormBase {
12+
13+
/**
14+
* {@inheritdoc}
15+
*/
16+
public function getFormId() {
17+
return 'batch_example_form';
18+
}
19+
20+
/**
21+
* {@inheritdoc}
22+
*/
23+
public function buildForm(array $form, FormStateInterface $form_state) {
24+
25+
$form['description'] = [
26+
'#type' => 'markup',
27+
'#markup' => t('This example offers two different batches. The first does 1000 identical operations, each completed in on run; the second does 20 operations, but each takes more than one run to operate if there are more than 5 nodes.'),
28+
];
29+
$form['batch'] = [
30+
'#type' => 'select',
31+
'#title' => 'Choose batch',
32+
'#options' => [
33+
'batch_1' => t('batch 1 - 1000 operations'),
34+
'batch_2' => t('batch 2 - 20 operations.'),
35+
],
36+
];
37+
$form['submit'] = [
38+
'#type' => 'submit',
39+
'#value' => 'Go',
40+
];
41+
42+
return $form;
43+
44+
}
45+
46+
/**
47+
* {@inheritdoc}
48+
*/
49+
public function submitForm(array &$form, FormStateInterface $form_state) {
50+
// Gather our form value.
51+
$value = $form_state->getValues()['batch'];
52+
// Set the batch, using convenience methods.
53+
$batch = [];
54+
switch ($value) {
55+
case 'batch_1':
56+
$batch = $this->generateBatch1();
57+
break;
58+
59+
case 'batch_2':
60+
$batch = $this->generateBatch2();
61+
break;
62+
}
63+
batch_set($batch);
64+
}
65+
66+
/**
67+
* Generate Batch 1.
68+
*
69+
* Batch 1 will process one item at a time.
70+
*
71+
* This creates an operations array defining what batch 1 should do, including
72+
* what it should do when it's finished. In this case, each operation is the
73+
* same and by chance even has the same $nid to operate on, but we could have
74+
* a mix of different types of operations in the operations array.
75+
*/
76+
public function generateBatch1() {
77+
$num_operations = 1000;
78+
drupal_set_message(t('Creating an array of @num operations', ['@num' => $num_operations]));
79+
80+
$operations = [];
81+
// Set up an operations array with 1000 elements, each doing function
82+
// batch_example_op_1.
83+
// Each operation in the operations array means at least one new HTTP
84+
// request, running Drupal from scratch to accomplish the operation. If the
85+
// operation returns with $context['finished'] != TRUE, then it will be
86+
// called again.
87+
// In this example, $context['finished'] is always TRUE.
88+
for ($i = 0; $i < $num_operations; $i++) {
89+
// Each operation is an array consisting of
90+
// - The function to call.
91+
// - An array of arguments to that function.
92+
$operations[] = [
93+
'batch_example_op_1',
94+
[
95+
$i + 1,
96+
t('(Operation @operation)', ['@operation' => $i]),
97+
],
98+
];
99+
}
100+
$batch = [
101+
'title' => t('Creating an array of @num operations', ['@num' => $num_operations]),
102+
'operations' => $operations,
103+
'finished' => 'batch_example_finished',
104+
];
105+
return $batch;
106+
}
107+
108+
/**
109+
* Generate Batch 2.
110+
*
111+
* Batch 2 will process five items at a time.
112+
*
113+
* This creates an operations array defining what batch 2 should do, including
114+
* what it should do when it's finished. In this case, each operation is the
115+
* same and by chance even has the same $nid to operate on, but we could have
116+
* a mix of different types of operations in the operations array.
117+
*/
118+
public function generateBatch2() {
119+
$num_operations = 20;
120+
121+
$operations = [];
122+
// 20 operations, each one loads all nodes.
123+
for ($i = 0; $i < $num_operations; $i++) {
124+
$operations[] = [
125+
'batch_example_op_2',
126+
[t('(Operation @operation)', ['@operation' => $i])],
127+
];
128+
}
129+
$batch = [
130+
'operations' => $operations,
131+
'finished' => 'batch_example_finished',
132+
// @current, @remaining, @total, @percentage, @estimate and @elapsed.
133+
// These placeholders are replaced with actual values in _batch_process(),
134+
// using strtr() instead of t(). The values are determined based on the
135+
// number of operations in the 'operations' array (above), NOT by the
136+
// number of nodes that will be processed. In this example, there are 20
137+
// operations, so @total will always be 20, even though there are multiple
138+
// nodes per operation.
139+
// Defaults to t('Completed @current of @total.').
140+
'title' => t('Processing batch 2'),
141+
'init_message' => t('Batch 2 is starting.'),
142+
'progress_message' => t('Processed @current out of @total.'),
143+
'error_message' => t('Batch 2 has encountered an error.'),
144+
];
145+
return $batch;
146+
}
147+
148+
}

0 commit comments

Comments
 (0)