It's my first time working with the Batch API. I have a batch operation that I have almost working, but it gets stuck on the first item and I'm out of ideas on how to fix it.

What the batch thing does is iterate over a bunch of nodes and transform the fake-locations scattered over a dozen text fields into real Location CCK fields. When the batch process runs, it continually fixes the first node over and over again. My logs fill up with the Location module deleting orphaned locations, and if I dpm($node), it shows the first node over and over again.

I've checked my $batch variable, and the operations array does have all different nids.

Here's the code:

function rlh_tweaks_menu() {
    $items['location-fix'] = array(
        'title' => 'Location fixer',
        'page callback' => 'rlh_tweaks_location_fix',
        'access callback' => '_rlh_tweaks_user_one_only',

    return $items;

function rlh_tweaks_location_fix() {
    $batch = array(
        'finished' => '_rlh_tweaks_batch_finished',

    $query = new EntityFieldQuery();
    $query->entityCondition('entity_type', 'node')
        ->entityCondition('bundle', 'business');
    $result = $query->execute();

    foreach($result['node'] as $nid => $data) {
        $batch['operations'][] = array('_rlh_tweaks_batch_update_location', array($nid));


function _rlh_tweaks_batch_update_location($nid, &$context) {
    if (!isset($context['sandbox']['progress'])) {
            $context['sandbox']['progress'] = 0;
            $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT nid) FROM {node} WHERE type = \'business\'')->fetchField();

            // Here we actually perform our processing on the current node.

            $node = node_load($nid);
        $location = _rlh_tweaks_update_node_location($node);

        $lid = location_save($location);

        if(!empty($lid)) {
            $node->field_location[LANGUAGE_NONE]['0'] = location_load_location($lid);


            // Store some result for post-processing in the finished callback.
            $context['results'][] = check_plain($node->title);
            // Update our progress information.
            $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'];

function _rlh_tweaks_update_node_location($node) {
    $street_part_keys = array('street_no', 'pre_dir', 'street_name', 'post_dir', 'street_type');

    $all_parts = array('street_no', 'pre_dir', 'street_name', 'post_dir', 'street_type', 'suite_no', 'city', 'po_box', 'state', 'zip');

    $parts = array();

    foreach($all_parts as $key) {
        $parts[$key] = _rlh_tweaks_get_text_field_value($node, $key);

    $street_parts = array();

    foreach($street_part_keys as $key) {
        if(!empty($parts[$key])) {
            $street_parts[] = $parts[$key];

    $street = implode(' ', $street_parts);

    if(empty($street)) {
        if(empty($parts['po_box'])) {
        } else {
            $street = $parts['po_box'];

    $location = array(
        'street' => $street,
        'additional' => $parts['suite_no'],
        'city' => $parts['city'],
        'province' => $parts['state'],
        'postal_code' => $parts['zip'],
        'country' => 'us'

    return $location;
    if(!empty($lid)) {
        $node->field_location[LANGUAGE_NONE]['0'] = location_load_location($lid);

    return $node); */

function _rlh_tweaks_batch_finished($success, $results, $operations) {
    if ($success) {
        // Here we do something meaningful with the results.
        $message = t("!count items were processed.", array(
            '!count' => count($results),
        $message .= theme('item_list', array('items' => $results));
    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');

function _rlh_tweaks_get_text_field_value($node, $field_name) {
    $item = field_get_items('node', $node, 'field_' . $field_name);
    return $item[0]['safe_value'];

function _rlh_tweaks_user_one_only() {
    global $user;

    if($user->uid == '1') {
        return TRUE;

    return FALSE;

Why is this not moving on to the rest of the batch operations?

1 Answer 1


Your approach is slightly incorrect.

You should add a single batch operation and within that operation callback query the relevant node ids (once and only if not already set in sandbox) then add them to $context['sandbox'] for processing. Set and check the current node id in the sandbox to process the next set on each batch run.

At the moment, it can never reach the "max" count and finish as only a single $nid is passed at a time.

  • I had something like that originally, but had trouble, then saw an example in another question on here that seemed to do it the way I pasted above. Working now; thanks! Commented May 16, 2014 at 14:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.