Batch API

Drupals internal batch API can be really helpful for handling large cumbersome processes on your web server. Rather than submitting a form and waiting for one of these processes to finish before reaching the next page, the batch API can be utilized to break the process down across multiple page loads. This not only cuts down on the server load, but will prevent the page from timing out. Progress bars will be displayed to the user while the process runs which will keep them informed of where they are at in the process.

The example posted comprises of a drupal menu system and a form for selecting the type of operations to perform. The options available are updating Article Content or Page content. The main logic updates the title of the node of the corresponding type selected with a number when performing the batch operation.

<?php
/**
 * Implements hook_menu().
 */
function ji_custom_menu() {
 
$items = array();
 
$items['batch_processing'] = array(
   
'title' => 'Batch Processing Check',
   
'description' => 'Testing Batch Processing',
   
'page callback' => 'drupal_get_form',
   
'page arguments' => array('ji_custom_batch_form'),
   
'access arguments' => array('access content'),
   
'type' => MENU_NORMAL_ITEM,
  );

  return
$items;
}

/**
* An example form
*/
function ji_custom_batch_form($form, &$form_state) {
 
$form  = array();

 
$batch_options = array('article' => 'Update Article Content', 'page' => 'Update Basic Page Content');
 
$form['batch_type'] = array(
      
'#type' => 'select',
      
'#title' => t('Selected'),
      
'#options' => $batch_options,
   );

 
$form['submit'] = array(
   
'#type' => 'submit',
   
'#value' => 'Submit',
  );

  return
$form;
}

/**
* Form Submit Handler
*/
function ji_custom_batch_form_submit($form, $form_state) {
 
$selected_batch = $form_state['values']['batch_type'];
 
ji_custom_process_batch($selected_batch);
}

/**
* 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.)
*/
function ji_custom_process_batch($selected_batch) {
 
$batch = array(
   
'operations' => array(
       
// You can actually chain together multiple batch processes here
     
array('ji_custom_batch_check', array($selected_batch)),
    )
   
// A title to be displayed to the end user when the batch starts.
   
, 'title' => t('Batch API Check')
   
// A callback to be used when the batch finishes.
   
, 'finished' => 'ji_custom_batch_check_finished'
   
// An initial message to be displayed to the end user when the batch starts.
   
,'init_message' => t('Custom Batch Check is starting.')
   
// A progress message for the end user. Placeholders are available.
   
, 'progress_message' => t('Processed @current out of @total.')
   
// The error message that will be displayed to the end user if the batch fails
   
, 'error_message' => t('Example Batch has encountered an error.')

  );

 
batch_set($batch);
 
// If this function was called from a form submit handler, stop here,

  // Start the batch process and tell it where to send the user when it's done.
  // In this case, we’ll send the user back to the batch_processing  page.
 
batch_process('batch_processing');

}

/**
* Batch 'finished' callback
*/
function ji_custom_batch_check_finished($success, $results, $operations){
  if (
$success) {
   
$message = format_plural(count($results), 'One node created', '@count nodes created.');
   
// 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');
  }

}

/**
* Main Logic Part of the Batch Implementation.
* Gets node list for the type selected. Loads the node, updates the title and saves the node
*
* @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.
*/
function ji_custom_batch_check($selected_batch, &$context){
 
/*
  Set the number of nodes you want to create within a single iteration of the batch. This number
  should be pretty low as it is the reason why we're creating the batch process in the first place.
  */
 
$limit = 10;

 
$query = db_select('node', 'node')
    ->
fields('node', array('nid','title'))
    ->
condition('node.status', 1)
    ->
condition('node.type',$selected_batch);

 
$result = $query->execute();

 
/*
  Set the variables that need to persist throughout the batch process. These need to be set inside
  the $context array because that's what persists throughout the process.
  */
 
if (!isset($context['sandbox']['progress'])) {
   
$context['sandbox']['progress'] = 0;
   
$context['sandbox']['current_node'] = 0;
   
$context['sandbox']['max'] = $result->rowCount();
  }

 
drupal_set_message(t("The type: $selected_batch"), 'status', FALSE);

 
$query = db_select('node', 'node')
    ->
fields('node', array('nid','title'))
    ->
condition('node.status', 1)
    ->
condition('node.type',$selected_batch)
    ->
condition('node.nid', $context['sandbox']['current_node'], '>')
    ->
range(0, $limit)
    ->
orderBy('node.nid','ASC');

 
$result = $query->execute();

  foreach (
$result as $record) {
   
$node = node_load($record->nid);
   
$node->title = $node->title.' #'.$context ['sandbox']['progress'];
   
node_save($node);

   
// Store some result for post-processing in the finished callback.
   
$context['results'][] = check_plain($node->title); // Keep a running tab of all of the nodes created.
    // Update our progress information.
   
$context['sandbox']['progress']++; // Used to keep track of how many nodes we've already created.
   
$context['sandbox']['current_node'] = $node->nid;
   
$context['message'] = "Created node " . $record->title. " ".$context ['sandbox']['progress'];

  }

 
// 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'];
  }
}
?>

--
Sources:
http://envisioninteractive.com/drupal/drupal-batch-api-in-a-nutshell/
https://drupal.org/node/180528
https://api.drupal.org/api/drupal/includes%21form.inc/group/batch/7