Batch Processing with a Custom Drush Command in Drupal 9
In this post we are going to create a custom drush command that loads the nodes of a content type passed in as an argument ($type). then a batch process will simulate a long operation on each node.
Create the custom Drush command to launch the batch:
Drush command is composed of two files:
/examples_batch.services.yml:
services:
examples_batch.batch.manager:
class: Drupal\examples_batch\Service\batchManager
examples_batch.commands:
class: Drupal\examples_batch\Commands\batchCommands
tags:
- { name: drush.command }
arguments: ['@entity_type.manager', '@logger.factory', '@examples_batch.batch.manager']
This is a Symfony service definition, where our Drush command definition goes into, you'll can see that in our example we inject two core services in our command class: entity_type.manager to access the nodes to process and logger.factory to log some pre-process and post-process information.
batchCommands.php: (src/Commands/batchCommands.php)
<?php
namespace Drupal\examples_batch\Commands;
use Drush\Commands\DrushCommands;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\examples_batch\Service\batchManager;
/**
* Declares Drush commands process.
*/
class batchCommands extends DrushCommands {
/**
* Entity type service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
private $entityTypeManager;
/**
* Logger service.
*
* @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
*/
private $loggerChannelFactory;
/**
* batch service.
*
* @var \Drupal\examples_batch\Service\batchManager
*/
private $batchManager;
/**
* Constructs a new UpdateVideosStatsController object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* Entity type service.
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $loggerChannelFactory
* Logger service.
* @param \Drupal\examples_batch\Service\batchManager $batchManager
* batch service.
*/
public function __construct(EntityTypeManagerInterface $entityTypeManager, LoggerChannelFactoryInterface $loggerChannelFactory, batchManager $batchManager) {
$this->entityTypeManager = $entityTypeManager;
$this->loggerChannelFactory = $loggerChannelFactory;
$this->batchManager = $batchManager;
}
/**
*
* @command examples_batch:watcher
* @aliases examples_batch
*
*/
public function watcher($type) {
$operations = [];
$this->loggerChannelFactory->get('examples_batch')->info('Update nodes batch operations start');
$this->logger()->notice("Batch operations start.");
$nodes = $this->batchManager->getNodes($type);
foreach ($nodes as $id) {
$operations[] = ['\Drupal\examples_batch\Service\batchManager::processItem', [$id, t('Updating node @id', ['@id' => $id])]];
}
$batch = [
'title' => t('Updating node @type', ['@type' => $type]),
'operations' => $operations,
'finished' => '\Drupal\examples_batch\Service\batchManager::processFinished',
];
batch_set($batch);
drush_backend_batch_process();
$this->logger()->notice("Batch operations end.");
$this->loggerChannelFactory->get('examples_batch')->info('Update batch operations end.');
}
}
In this class we are going to define the custom Drush commands of our module. This class uses the Annotated method for commands.?
The first we inject our services in the __construct() method:?
Next, in the watcher() annotated method we define our command with annotations @command and @aliases, the main part of this command is the creation of the operations array for our batch processing also pointing to the two callback functions, Once the batch operations are added as new batch sets, we process the batch sets with the function drush_backend_batch_process().
Finally, we show information to the user and log some information for a later use.
领英推荐
Create a batchManager class for the batch operations:
src/Service/batchManager.php
<?php
namespace Drupal\examples_batch\Service;
use Drupal\node\Entity\Node;
/**
* Service for batch services.
*/
class batchManager {
public function getNodes($type) {
$results = \Drupal::entityQuery('node')->condition('type', $type)->condition('status', 1)->accessCheck(false)->execute();
if(count($results) > 0) {
return $results;
}
return [];
}
/**
* Batch process callback.
*
* @param int $id
* id for node.
* @param string $details
* information for status.
* @param object $context
* Context for operations.
*/
public static function processItem($id, $details, &$context) {
sleep(3);
$entity = Node::load($id);
$message = t('Batch cancel for node: @id', ['@id' => $id]);
if(!empty($entity->status->value)) {
$entity->set('status', 0);
$entity->save();
$message = t('Running Batch: @details', ['@details' => $details]);
}
$context['results'][] = $id;
$context['message'] = $message;
}
/**
* Batch Finished callback.
*
* @param bool $success
* Success of the operation.
* @param array $results
* Array of results for post processing.
* @param array $operations
* Array of operations.
*/
public static function processFinished($success, $results, $operations) {
$message = NULL;
if ($success) {
$message = t('@count results processed.', ['@count' => count($results)]);
} else {
$message = t('Finished with an error.');
}
\Drupal::messenger()->addMessage($message);
}
}
In this class we define callbacks functions:
processItem(): In the processItem method we are going to process each element of our batch. As you can see, in this method we just simulate a long operation with the sleep() PHP function. here we could load each node or connect with an external API. We also grab some information for post-processing.
processFinished(): In this function we display relevant information to the user, and we can even save the unprocessed operations for a later process.
Some of the annotations available for use are:
@command: This annotation is used to define the Drush command.
@aliases: An alias for your command.
@param: Defines the input parameters.?
@option: Defines the options available for the commands.