0

Following is the code where I'm willing to update the description of the field_meta_tags of all my products using the batch process.

It's getting executed & working as expected, but only for the first batch.

function mymodule_update_8001($sandbox) {
  if (!isset($sandbox['total'])) {
    $products = \Drupal::entityQuery('commerce_product')
      ->condition('type', 'default')
      ->execute();
    $sandbox['total'] = count($products);
    $sandbox['current'] = 0;
  }

  $products_per_batch = 25;

  $products = \Drupal::entityQuery('commerce_product')
    ->condition('type', 'default')
    ->range($sandbox['current'], $sandbox['current'] + $products_per_batch)
    ->execute();

  foreach ($products as $product_id) {
    $product = Drupal\commerce_product\Entity\Product::load($product_id);
    $product->set('field_meta_tags', serialize(['description' => '']));
    $product->save();
    $sandbox['current']++;
  }

  \Drupal::messenger()
    ->addMessage($sandbox['current'] . ' products processed.');

  $sandbox['#finished'] = empty($sandbox['total']) ? 1 : ($sandbox['current'] / $sandbox['total']);
}

Once the above hook_update_n($sandbox) is executed, getting the following output at the terminal:

 --------------- ----------- --------------- ------------- 
  Module          Update ID   Type            Description  
 --------------- ----------- --------------- ------------- 
  mymodule        8001        hook_update_n   Updates.     
 --------------- ----------- --------------- ------------- 

 // Do you wish to run the specified pending updates?: yes.                                                             

>  [notice] Update started: mymodule_update_8001
>  [notice] Update completed: mymodule_update_8001
>  [notice] Message: 25 products processed.
> 
 [success] Finished performing updates.

Not sure how $sandbox['#finished'] is getting the value as 1, since that's how I remember we ask the batch to stop processing.

4
  • "Not sure how $sandbox['#finished'] is getting the value as 1, since that's how I remember we ask the batch to stop processing." hook_update_N() is only run a single time. You seem to be mixing up batch processing and hook_update_N(). Nothing will tell hook_update_N() to run multiple times, it's only ever supposed to run a single time.
    – Jaypan
    Commented May 18, 2020 at 15:11
  • 1
    @Jaypan hook_update_N implementations can act as batch callbacks (and thus invoked multiple times in the same update script). There's a note on it in the docs
    – Clive
    Commented May 18, 2020 at 16:11
  • No kidding. I stand corrected. Thanks for the link Clive!
    – Jaypan
    Commented May 18, 2020 at 16:31
  • 2
    The declaration should be function mymodule_update_8001(&$sandbox), as arrays have weird pass by value/reference rules in PHP. So, I highly suspect in the UpdateManager(?), this isn't actually being called as a batch.
    – mpdonadio
    Commented May 18, 2020 at 23:44

4 Answers 4

1

So, for who seek the answer, it's quite simple. The issue of the original code is because the "&" is missing in declaration.

function mymodule_update_8001($sandbox) must be function mymodule_update_8001(&$sandbox)

Otherwise, it will only be launch one time.

0

There is no need for batches if you intend to run all entries in one process.

This is something we had use to update around 30.000 entries i quickly adapted from your example :

/**
 * Query all commerce_product of type default
 *
 * *
 * @param array $sandbox
 *
 * @return string
 *  Number of updated rows
 */
function mymodule_post_update_0801(array $sandbox) {
  /** @var $connection Drupal\Core\Database\Connection */
  $connection = \Drupal::service('database');
  $query = $connection->select('commerce_product', 'p');
  $query->fields('p', ['id'])
    ->condition('type', 'default');
  $toUpdate = $query->execute()->fetchAll();

  $result = array_map( 'update_description_value', $toUpdate);

  return sprintf('Number of row updated: %s', count($result));
}

/**
 * Update a product description
 *
 * @param stdClass $defaultProduct
 *  ->id
 *
 * @return string
 *  product id
 */
function update_description_value($productQueried) {
  /** @var $connection Drupal\Core\Database\Connection */
  $connection = \Drupal::service('database');

  $updated = $connection->update('commerce_product')
    ->fields(['field_meta_tags' => serialize(['description' => ''])])
    ->condition('id', $productQueried->id, '=')
    ->execute();

  return $productQueried->id;
}

Let me know if there is typo somewhere.

0

The drush updb command will not continue the batch if the update hook does not return a message.

changes required:

  • Add return t("Updated @progress/@max products.", ['@progress' => $sandbox['current'], '@max' => $sandbox['total']]);
  • in function declaration pass $sandbox parameter by reference

changes optional:

  • remove messenger()->addMessage() as progress is displayed with the return value. It is not good to use messages in update hooks, as messages will be displayed after the hook is finished (not during execution).
  • fix range() second parameter as ->range($sandbox['current'], $products_per_batch) The range parameters are not from-to, but start-length.
0

Have you considered creating a custom Drush command? If your module is custom, then maybe you don't need to execute the update in hook_update_HOOK. See https://www.drupal.org/project/drush9_batch_processing

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.