Creating the batch

Inside the JsonImporter::import() method, once we get our hands on the $products array, let's replace the loop with the following:

$batch_builder = (new BatchBuilder()) 
  ->setTitle($this->t('Importing products')) 
  ->setFinishCallback([$this, 'importProductsFinished']); 
 
$batch_builder->addOperation([$this, 'clearMissing'], [$products]); 
$batch_builder->addOperation([$this, 'importProducts'], [$products]); 
batch_set($batch_builder->toArray());  

And the new use statement at the top:

use Drupal\Core\Batch\BatchBuilder;  

Creating a batch involves a number of steps, the first one being the creation of a batch definition, which is nothing more than an array with some data. Before version 8.6 of Drupal, the batch definition was created by actually defining an array. Now we use a dedicated batch builder object, but the end result is the same.

The batch can have a title that sets the title to be used on the progress page. Similarly, it can also have an optional init, progress and error message that can be set with corresponding methods, but that also come with sensible defaults. For more information as to what exactly you can do with them, and what other options you have, make sure you check out the BatchBuilder class and the batch_set global function.

The most important part of the batch definition is the list of operations in which we specify what needs to take place in the batch. These are defined as any kind of valid PHP callback and an array of arguments to pass to these callbacks. If the latter resides in a file that has not been loaded, the setFile() method can be used to specify a file path to include. Each operation runs on its own PHP request, in the sequence in which they are defined. Moreover, each operation can also run across multiple requests, similar to how we wrote our update hook earlier.

Our first operation will be responsible for removing from Drupal the products that no longer exist in the JSON response, while the latter will do the import. Both of these receive only one parameter—the array of products.

The finished key in the definition array (set using the setFinishCallback() method) is another callback that gets fired at the end of the batch processing, after all the operations are done.

Finally, we call the global batch_set() method, which statically sets the batch definition and marks it as ready to be run. There is just one more step to trigger the batch, and that is a call to batch_process(). But the reason we have not used it is that if the import runs as part of a form submission, the Form API triggers it automatically. So it won't work if we trigger it here as well. The reason why the Form API does it for us is that most of the time we want batches to run only as a result of an action being taken. And usually, this is done via forms. However, the other major possibility is to trigger the batch via a Drush command (which we can actually do). In this case, we need to use the drush_backend_batch_process() function instead.

So, what we will do first is check that we are in a command-line environment (aka Drush) and trigger it only in that case:

if (PHP_SAPI == 'cli') {
drush_backend_batch_process();
}

Otherwise, we leave it up to the Form API. In doing this, we can trigger the import both from a Form submit handler and via Drush, and we can have plugins that don't necessarily use batches.