{ Drupal Batches – Notify existing users }

The Drupal 8+ batch system is incredibly useful when dealing with large sets of data. It’s why the popular Feeds module makes use of it, along with several other site builder favorites such as Views Bulk Operations. Of course, if you are writing your own module or want to avoid Feeds (it’s not covered under Drupal security policies after all) it’s good to know how to use it.

The unusual part is much of it is actually quite procedural. To complete a batched implementation, you are required to call

batch_set($batch);

The rest of it is essentially filling a multidimensional array with values to compute and deciding which functions to invoke. Let us imagine we are working on a module called:

example_module

We have a controller called ExampleController with two main methods, processUser and resetUser:

public function processUsers() {
    $batch = [
      'title' => $this->t('Importing User Data...'),
      'operations' => [],
      'finished' => '\Drupal\example_module\ExampleController::_import_batch_finished',
      'init_message' => $this->t('Import process is starting.'),
      'progress_message' => $this->t('Processed @current out of @total. Estimated time: @estimate.'),
      'error_message' => $this->t('Error occurred. Failed to import.'),
    ];

    /** @var UserStorage $user_storage */
    $user_storage = $this->entityTypeManager()->getStorage('user');
    $users = $user_storage->loadMultiple();

    /** @var User $user */
    foreach ($users as $user) {
      if ($user->hasRole('some_role')) {
        $batch['operations'][] = [[$this, 'resetUser'], [$user]];
      }
    }
    batch_set($batch);
  }

 

public function resetUser(User $user) {
    $op = 'password_reset';
    _user_mail_notify($op, $user);
  }

Then, we added a static method for it to invoke in $batch[‘finished’]

public static function _import_batch_finished($success, $results, $operations) {
    if ($success) {
      if (isset($results['count'])) {
        \Drupal::messenger()->addMessage(t('Total content created/updated') . ' : ' . $results['count'], 'status', TRUE);
      }
      if(isset($results['error'])) {
        \Drupal::messenger()->addMessage(t('Content has not been created/updated for the below Row Number.'), 'warning', TRUE);
        foreach($results['error'] as $key => $result) {
          foreach($result as $value) {
            \Drupal::messenger()->addMessage(t('Row number') . ' : ' . $key .  ' - ' . $value, 'warning', TRUE);
          }
        }
      }
    } else {
      \Drupal::messenger()->addMessage(t('Finished with an error.'), 'error', TRUE);
      foreach($results['error'] as $key => $result) {
        foreach($result as $value) {
          \Drupal::messenger()->addMessage(t('Row number') . ' : ' . $key .  ' - ' . $value, 'warning', TRUE);
        }
      }
    }
  }

So we are setting up $batch, getting an array of user entities, then looping through each one and telling it to invoke the resetUser method every time the user entity has a role of “some_role”. In actuality, resetUser isn’t invoked immediately. It’s put into a queue that kicks off at the end of the loop as soon as

batch_set($batch)

is called. This logic specifically sends out password reset emails to all matching users, which can be useful since Drupal doesn’t provide a way to notify the user after their account has been created.

If you have a lot of users, this would most likely time out when hitting PHP’s max execution time limit, depending on your hosting situation. But with the batched solution, we can avoid that, and it doesn’t need to lock up your server while it runs.