Skip to main content
Image
Drupal 8

Create custom commands in Drush

Hello! If you want to learn Drupal, you are in a good place.

This time, I want to share how to create your own Drush command from PHP and launch a batch process. Don't worry if I overwhelm you with so many new terms, it's not that big of a deal!

What is a drush command

To know what a drush command is, you first need to know what drush is.

Drush is a console tool -THE TOOL- used in Drupal, which serves to speed up daily or repetitive tasks; in short, drush makes life easier for the developer working on a Drupal project.

Among many other things, drush brings by default scripts that allow us to delete caches, create temporary users, put the site in maintenance, enable/disable modules, update user passwords, and a long etc...

But what if that's still not enough what drush offers by default? Well, you can create your own drush command!

Use cases

Launching a batch process from your own drush command is a correct method when it comes to having to execute any massive modification on your website, such as updating a field of N content whose content type is X, or removing all users from a certain role. Anything is possible, since we have the Drupal API ahead of us.

Let's put it into practice

We are going to create a drush command that automatically modifies the title of all the contents of a certain type of content. To begin with, we are going to define the following file structure:

\migration_commands
		\src
			BatchService.php
			\Commands
				GeneralCommands.php
		drush.services.yml
		migration_commands.info.yml
  • BatchService.php: will be the PHP class that defines the logic of the batch process.
  • GeneralCommands.php: will be the PHP class in which all the commands that you want to create are defined.
  • drush.services.yml: here you define the drush.command service to be injected in the GeneralCommands.php
  • migration_commands.info.yml class: here you define the basic information of any Drupal module or theme.

First let's define the service as follows.

services:
  migration_commands.commands:
    class: \Drupal\migration_commands\Commands\GeneralCommands
    arguments: []
    tags:
      - { name: drush.command }

Here it is important to enter the correct path of the class in which we are going to create the commands; in this case, GeneralCommands. In the 'tags' directive we introduce the alias that we will use in the dependency injection, which will be 'drush.command'. You can read more about service tags here: https://www.drupal.org/docs/8/api/services-and-dependency-injection/service-tags

Now we are going to create the GeneralCommands class. Here we are going to create all the drush commands we want, but it will not be where the command logic itself is.

class GeneralCommands extends DrushCommands
{

/**  
 * Change node titles of content type. 
 *  
 * @command general-commands:change-node-titles 
 *  
 * @usage drush general-commands:change-node-titles
 * 
 * @aliases gc-change-titles
 */
	public function changeNodeTitles() {
	
		// Inicializar variables.
		$operations = [];
		$num_operations = 0;
		$batch_id = 1;
		$content_type = 'news';
	
		// Get processed node.
		$nodes = \Drupal::entityQuery('node')->condition('type', $content_type, 'IN')->execute();
		
		foreach ($nodes as $nid) {
			// Prepare operation.
			$operations[] = [
			'\Drupal\migration_commands\BatchService::processChangeNodeTitles',
			[
				$batch_id,
				$nid,
				t('Procesando nodo con id @nid, ['@nid' => $nid]),
				],
			];

			$num_operations++;
			$batch_id++;
		}
		
		// Create batch.
		$batch = [
			'title' => t('Comprobando @num items', ['@num' => $num_operations]),
			'operations' => $operations,
			'finished' => '\Drupal\migration_commands\BatchService::processChangeNodeTitlesFinished',
		];

		// Set batch.
		batch_set($batch);

		// Start batch.
		drush_backend_batch_process();

		// Logs
		$this->output()->writeln("Batch terminado: " . $batch_id);
		$this->output()->writeln("Proceso finalizado.");
	}

The comments part at the top of the function is where the name and alias of the command we are going to create are defined by annotations. From the foreach the process is always the same: create an operation for each node obtained in the previously launched query ($nodes). Each operation must have the class and function where the node X is going to be processed, the nid of the same one and other arguments that you want to send. Once all the operations are created, the batch is set and then executed, with "drush_backend_batch_process()".

Now we are going to create the BatchService class, which is where we will create the logic to be executed for each node that arrives in each operation (for each operation of the $operations array).

class BatchService
{

	public function processChangeNodeTitles($id, $nid, $operation_details, &$context) {
		$context['message'] = t('Ejecutando batch con id @id', ['@id' => $id]);

		// Load node by $nid.
		$node = Node::load($nid);
		
		// Change node title.
		$node->setTitle($node->getTitle() . ' ' . random_int(0, 20));
		$node->save();

		$context['results][] = $nid;
		$context['message'] = t('Finish batch @id with node id: @nid', [
			'@id' => $id,
			'@nid' => $nid,
		]);
	}

	public function processChangeNodeTitlesFinished($success, array $results, array $operations) {
		$messenger = \Drupal::messenger();
		if ($success) {
			$messenger->addMessage(t('@count modified nodes.', ['@count' => count($results)]));
		}
		else {
			$messenger->addMessage(t('Errors...'));
		}
}

In the method "processChangeNodeTitles()" is where we will create the logic of each node that comes to us through the argument $nid, which will be previously loaded as an entity and will be subjected to a change of the title field through the method "setTitle". We save the node, a couple of lines of logs to report, and that's it! The "processChangeNodeTitlesFinished" method will be triggered when the $operations array has processed all the nodes, and will be used to indicate the number of nodes processed by the BatchService or to warn us that an error has occurred.

To test this command, it is as simple as installing the custom module we have created and typing "drush". In the list, we can see in the category "_global" our command, if everything went well.

Conclusion

Did you find it easy? Difficult? Creating drush commands can be very useful in massive tasks, and even in some content migration processes, receiving data from different sources such as a csv or a plain text file.

Tags

Join the Drupal Sapiens Community

Save content that interests you, connect with others in the community, contribute to win prizes and much more.

Login or create an account here

Latest posts

Featured

Last news