Community Documentation

Batch API

Last updated March 27, 2013. Created by gpk on October 3, 2007.
Edited by quicksketch, flacoman91, hansfn, Pierre Paul Lefebvre. Log in to edit this page.

Here's an example of how to use the Batch API, originally introduced in Drupal 6. In this example, you would probably call batch_example() from a form submit handler, where the form submission provided the $options you want to use to update the nodes.

PS! This is only an example. Don't forget to actually read the API documentation.

<?php
/**
* The $batch can include the following values. Only 'operations'
* and 'finished' are required, all others will be set to default values.
*
* @param operations
*   An array of callbacks and arguments for the callbacks.
*   There can be one callback called one time, one callback
*   called repeatedly with different arguments, different
*   callbacks with the same arguments, one callback with no
*   arguments, etc. (Use an empty array if you want to pass
*   no arguments.)
*
* @param finished
*   A callback to be used when the batch finishes.
*
* @param title
*   A title to be displayed to the end user when the batch starts.
*
* @param init_message
*   An initial message to be displayed to the end user when the batch starts.
*
* @param progress_message
*   A progress message for the end user. Placeholders are available.
*   Placeholders note the progression by operation, i.e. if there are
*   2 operations, the message will look like:
*    'Processed 1 out of 2.'
*    'Processed 2 out of 2.'
*   Placeholders include:
*     @current, @remaining, @total and @percentage
*
* @param error_message
*   The error message that will be displayed to the end user if the batch
*   fails.
*
* @param file
*   Path to file containing the callbacks declared above. Always needed when
*   the callbacks are not in a .module file.
*
*/
function batch_example($options1, $options2, $options3, $options4) {
 
$batch = array(
   
'operations' => array(
      array(
'batch_example_process', array($options1, $options2)),
      array(
'batch_example_process', array($options3, $options4)),
      ),
   
'finished' => 'batch_example_finished',
   
'title' => t('Processing Example Batch'),
   
'init_message' => t('Example Batch is starting.'),
   
'progress_message' => t('Processed @current out of @total.'),
   
'error_message' => t('Example Batch has encountered an error.'),
   
'file' => drupal_get_path('module', 'batch_example') . '/batch_example.inc',
  );
 
batch_set($batch);

 
// If this function was called from a form submit handler, stop here,
  // FAPI will handle calling batch_process().

  // If not called from a submit handler, add the following,
  // noting the url the user should be sent to once the batch
  // is finished.
  // IMPORTANT:
  // If you set a blank parameter, the batch_process() will cause an infinite loop

 
batch_process('node/1');
}

/**
* Batch Operation Callback
*
* Each batch operation callback will iterate over and over until
* $context['finished'] is set to 1. After each pass, batch.inc will
* check its timer and see if it is time for a new http request,
* i.e. when more than 1 minute has elapsed since the last request.
* Note that $context['finished'] is set to 1 on entry - a single pass
* operation is assumed by default.
*
* An entire batch that processes very quickly might only need a single
* http request even if it iterates through the callback several times,
* while slower processes might initiate a new http request on every
* iteration of the callback.
*
* This means you should set your processing up to do in each iteration
* only as much as you can do without a php timeout, then let batch.inc
* decide if it needs to make a fresh http request.
*
* @param options1, options2
*   If any arguments were sent to the operations callback, they
*   will be the first argments available to the callback.
*
* @param context
*   $context is an array that will contain information about the
*   status of the batch. The values in $context will retain their
*   values as the batch progresses.
*
* @param $context['sandbox']
*   Use the $context['sandbox'] rather than $_SESSION to store the
*   information needed to track information between successive calls to
*   the current operation. If you need to pass values to the next operation
*   use $context['results'].
*
*   The values in the sandbox will be stored and updated in the database
*   between http requests until the batch finishes processing. This will
*   avoid problems if the user navigates away from the page before the
*   batch finishes.
*
* @param $context['results']
*   The array of results gathered so far by the batch processing. This
*   array is highly useful for passing data between operations. After all
*   operations have finished, these results may be referenced to display
*   information to the end-user, such as how many total items were
*   processed.
*
* @param $context['message']
*   A text message displayed in the progress page.
*
* @param $context['finished']
*   A float number between 0 and 1 informing the processing engine
*   of the completion level for the operation.
*
*   1 (or no value explicitly set) means the operation is finished
*   and the batch processing can continue to the next operation.
*/
function batch_example_process($options1, $options2, &$context) {
  if (!isset(
$context['sandbox']['progress'])) {
   
$context['sandbox']['progress'] = 0;
   
$context['sandbox']['current_node'] = 0;
   
$context['sandbox']['max'] = db_result(db_query('SELECT COUNT(DISTINCT nid) FROM {node}'));
  }

 
// For this example, we decide that we can safely process
  // 5 nodes at a time without a timeout.
 
$limit = 5;

 
// With each pass through the callback, retrieve the next group of nids.
 
$result = db_query_range("SELECT nid FROM {node} WHERE nid > %d ORDER BY nid ASC", $context['sandbox']['current_node'], 0, $limit);
  while (
$row = db_fetch_array($result)) {

   
// Here we actually perform our processing on the current node.
   
$node = node_load($row['nid'], NULL, TRUE);
   
$node->value1 = $options1;
   
$node->value2 = $options2;
   
node_save($node);

   
// Store some result for post-processing in the finished callback.
   
$context['results'][] = check_plain($node->title);

   
// Update our progress information.
   
$context['sandbox']['progress']++;
   
$context['sandbox']['current_node'] = $node->nid;
   
$context['message'] = t('Now processing %node', array('%node' => $node->title));
  }

 
// Inform the batch engine that we are not finished,
  // and provide an estimation of the completion level we reached.
 
if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
   
$context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
  }
}

/**
* Batch 'finished' callback
*/
function batch_example_finished($success, $results, $operations) {
  if (
$success) {
   
// Here we do something meaningful with the results.
   
$message = count($results) .' processed.';
   
$message .= theme('item_list', $results);  // D6 syntax
   
drupal_set_message($message);
  }
  else {
   
// An error occurred.
    // $operations contains the operations that remained unprocessed.
   
$error_operation = reset($operations);
   
$message = t('An error occurred while processing %error_operation with arguments: @arguments', array('%error_operation' => $error_operation[0], '@arguments' => print_r($error_operation[1], TRUE)));
   
drupal_set_message($message, 'error');
  }
 
}
?>

Another example that might be of interest is GiantRobot / csvimport which uses file upload and Batch API to parse a CSV file line by line. Also an updated version for Drupal 7 is available.

Comments

Stop batch processing after set duration?

Is it possible to execute a batch process, but then monitor the progress of the batch and if the duration exceeds a given time, fire the batch complete function and re-execute the rest of the batch in the background?

In effect the user experience would be:

1. User clicks button which executes processing of batch, and is taken to the /batch page
2. The tasks process, but if the total execution time reaches 30 seconds before the batch is complete the user is taken to a summary page to be shown what has been processed so far
3. Whilst reviewing what has been completed so far, the remaining tasks (if any) from the batch execute in the background.
3a. If the whole batch completed in (2) then the user is still taken to the same summary page, and the list shows all the tasks which were completed.

I believe the background part can be done by setting the batch to progressive = FALSE, but I've read there are some problems doing this in D6 - is the above possible?

Any help much appreciated.

Background Batch

Background Process contains the module Background Batch, which runs a batch job in the background. Maybe not exactly as you describe, but check it out.

Store custom class object instances in 'sandbox'

'Sandbox' results in __PHP_Incomplete_Class Object error when storing custom class instances and it failes silently.. I figured it out the hard way! I posted on my blog on how to resolve this at http://blog.yawd.eu/2011/use-classes-static-variables-drupal-6-batch-api/. I also included a couple of notes based on my adventure with the Batch API. Hope this helps!

Software engineer - http://www.yawd.eu

Works on browser page but not as cronjob

Hello,

I have a module to make an operation by batch api. When I activate it on the browser, I can see the progress bar moving and it does its job ok. But if I put the function in a cronjob, I know that it starts, but doesn't do anything. Does batch only work when it is visible on a browser? Here is my module code:

<?php
function update_cnr_cron(){

 
$batch = array(
   
'operations' => array(
      array(
'update_cnr_updater', array())
      ),
   
'finished' => 'batch_example_finished',
   
'title' => t('Updating cnr nodes'),
   
'init_message' => t('Update cnr is starting.'),
   
'progress_message' => t('Processed @current out of @total.'),
   
'error_message' => t('Update cnr has encountered an error.'),
  );
 
batch_set($batch);

 
batch_process('node/1');
?>

@gielfeldt, your offered

@gielfeldt, your offered module for the other question, I think, also works for my case. On the module definition page:

The bundled module Background Batch runs all batch jobs as a background process.

I am now checking the progress of the batch on page admin/settings/batch/overview , and it looks it is working...

Thanks..

Progress bar not updating

I'm trying to make this work in a custom module.
The problem is that my batch is kind of heavy and need to call other functions, also written in the same .module .
The batch works properly, however $context seems to be lost : progress bar is not updating and keep showing the init_message text.
As all my functions are located in the same .module file, i presume i don't need to include any file in the batch array. But then how to include my other functions in the batch process without losing $context?

Transactions

Is it possible to manage all the process as a transaction? (commit, rollback)
In each operation, I save a node, but I need you to save all nodes or if there is an error not save any.
My tables are InnoDB but I don't know how it do.
Thanks

nobody click here