Creating a Batch Service for Long-running Processes in Drupal
Create a process (update a Node) that uses the batch API, and can be called either from a form, or from a drush command.
The Batch API is essential in Drupal for performing heavy tasks (like updating thousands of nodes or importing a huge CSV) without hitting PHP execution timeouts. It breaks a large task into smaller "chunks" and updates the browser with a progress bar.
Here is the most modern and clean way to implement it using a Form and a dedicated Batch Service.
1. Create the Batch Definition (in a Form)
The batch is typically triggered inside a submitForm() method. We define the metadata and the specific operations to be performed.
src/Form/MyBatchForm.php
PHP
namespace Drupal\my_module\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
class MyBatchForm extends FormBase {
public function getFormId() { return 'my_batch_form'; }
public function buildForm(array $form, FormStateInterface $form_state) {
$form['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Start Heavy Processing'),
];
return $form;
}
public function submitForm(array &$form, FormStateInterface $form_state) {
// 1. Define the operations (the functions to call)
// We'll process 100 items by splitting them into chunks of 10.
$items = range(1, 100);
$chunks = array_chunk($items, 10);
$operations = [];
foreach ($chunks as $chunk) {
$operations[] = [
'\Drupal\my_module\BatchService::processChunk', // The callback
[$chunk], // The arguments passed to the callback
];
}
// 2. Setup the Batch array
$batch = [
'title' => $this->t('Processing Items...'),
'init_message' => $this->t('Starting batch.'),
'progress_message' => $this->t('Completed @current out of @total.'),
'error_message' => $this->t('Batch has encountered an error.'),
'operations' => $operations,
'finished' => '\Drupal\my_module\BatchService::finished',
];
// 3. Set the batch
batch_set($batch);
}
}
2. Create the Processing Service
It is best practice to put the actual logic in a separate class. These methods must be static because the Batch API calls them outside of the standard object context.
src/BatchService.php
PHP
namespace Drupal\my_module;
class BatchService {
/**
* Processes a chunk of data.
*/
public static function processChunk($chunk, &$context) {
// Initialize results on first run
if (!isset($context['results']['processed'])) {
$context['results']['processed'] = 0;
}
foreach ($chunk as $id) {
// PERFORM YOUR HEAVY LOGIC HERE (e.g., $node->save())
usleep(100000); // Simulating work
$context['results']['processed']++;
}
// Optional: Update the progress message dynamically
$context['message'] = t('Currently processing item "@id"', ['@id' => end($chunk)]);
}
/**
* Final callback after all operations are done.
*/
public static function finished($success, $results, $operations) {
if ($success) {
$message = t('@count items processed successfully.', ['@count' => $results['processed']]);
\Drupal::messenger()->addStatus($message);
}
else {
\Drupal::messenger()->addError(t('Finished with an error.'));
}
}
}
3. The Lifecycle of a Batch
Understanding how the data flows helps in debugging complex batches:
Initiation:
batch_set()registers the tasks in the database and prepares a redirect to/batch.The Loop: The browser hits
/batch?id=.... Drupal runs one operation from the list, returns a JSON response to the browser, and the browser immediately requests the next one.Sandbox: If an operation is too big for even one request, you can use
$context['sandbox']to track internal progress and set$context['finished'] = 0.5to tell Drupal to call the same operation again.Finished: Once all operations return
finished = 1(or return nothing), the final callback is triggered.
4. Pro-Tip: The "Progressive" Sandbox
If you have one single operation that needs to process 10,000 items (rather than pre-chunking), use the sandbox:
PHP
public static function processEverything(&$context) {
if (empty($context['sandbox'])) {
$context['sandbox']['progress'] = 0;
$context['sandbox']['max'] = 10000;
}
$limit = 50; // Process 50 per request
for ($i = 0; $i < $limit; $i++) {
// Process one item...
$context['sandbox']['progress']++;
}
// Tell Drupal how far along we are (0.0 to 1.0)
$context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
}
Next we Next we will see how to trigger this same batch from a custom Drush command.
To trigger a Drupal batch process from the command line, you use a Drush Command. The key difference from a web-based batch is that you call drush_backend_batch_process() instead of letting the Form API handle the redirect.
This allows Drush to spawn its own sub-processes to work through the chunks, providing a progress bar directly in your terminal.
1. Create the Drush Command Class
For Drush 12/13, place your command file in src/Drush/Commands/.
src/Drush/Commands/MyModuleCommands.php
Recent content
-
2 hours 34 minutes ago
-
1 week 1 day ago
-
1 week 1 day ago
-
1 week 1 day ago
-
1 week 1 day ago
-
1 week 6 days ago
-
2 weeks 1 day ago
-
2 weeks 1 day ago
-
2 weeks 1 day ago
-
2 weeks 2 days ago