Change Drush Command Annotations with an Alterer Service

The ability to alter the behavior of modules and themes in Drupal is fairly well-known, but Drush offers a powerful way to alter its commands as well.

Since Drush commands are methods in classes, you might think that altering a command is as straightforward as extending the class and overriding the command method. Keep in mind though that options are defined through annotations in comments, not in extendable code. The command method itself might only require a small change, but you would need to copy the entire docblock with all of the annotations for it to work. The extending module now owns all of that complexity and must keep it in sync with the parent module.

Consider the complexity of keeping the following annotation from the drush migrate:status command in sync with a custom command.

/**
* List all migrations with current status.
*
* @param string $migration_names
* Restrict to a comma-separated list of migrations (Optional).
* @param array $options
* Additional options for the command.
*
* @command migrate:status
*
* @option group A comma-separated list of migration groups to list
* @option tag Name of the migration tag to list
* @option names-only Only return names, not all the details (faster)
* @option continue-on-failure When a migration fails, continue processing
* remaining migrations.
*
* @default $options []
*
* @usage migrate:status
* Retrieve status for all migrations
* @usage migrate:status --group=beer
* Retrieve status for all migrations in a given group
* @usage migrate:status --tag=user
* Retrieve status for all migrations with a given tag
* @usage migrate:status --group=beer --tag=user
* Retrieve status for all migrations in the beer group
* and with the user tag.
* @usage migrate:status beer_term,beer_node
* Retrieve status for specific migrations
*
* @validate-module-enabled migrate_tools
*
* @aliases ms, migrate-status
*
* @field-labels
* group: Group
* id: Migration ID
* status: Status
* total: Total
* imported: Imported
* unprocessed: Unprocessed
* last_imported: Last Imported
* @default-fields group,id,status,total,imported,unprocessed,last_imported
*
* Migrations status formatted as table.
*/

Thankfully there is a way to alter the annotations of Drush commands with the help of the CommandInfoAltererInterface interface and a carefully named & namespaced class.

The Drush documentation outlines the process for Altering Command Info in great detail, so I won’t repeat it here. However, understanding what you can do with this ability is something that warrants exploration. Some of the methods that are available to you for altering commands are listed below:

  • addOption()
  • addAnnotation()
  • addArgumentDescription()
  • setAliases()
  • setHelp()
  • setDescription()
  • setHidden()
  • setName()
  • setExampleUsage()
  • setInjectedClasses()
  • setReturnType()

Note: If you are still on Drush 11.x, be sure to follow the 11.x documentation, as there is an extra step required.

For an even better understanding, let’s combine the methods above into a real example with the Drupal Orange DAM module:

namespace Drupal\example_orange_dam\Drush\CommandInfoAlterers;

use Consolidation\AnnotatedCommand\CommandInfoAltererInterface;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
use Drupal\example_orange_dam\EventSubscriber\ExampleMigrationStatusSubscriber;

/**
* Alter Orange Logic module drush commands.
*/

class ExampleOrangeDamCommandInfoAlterer implements CommandInfoAltererInterface {

/**
* Alter Orange Logic module drush commands.
*/

public function alterCommandInfo(CommandInfo $commandInfo, $commandFileInstance): void {
// Add a custom filter to DAM migration sync status command.
if ($commandInfo->getName() === 'orange-dam:migration-status') {
$commandInfo->addOption(
'digital-id',
'The digital identifier.',
[],
'default',
);
}
}

}

Hopefully this helps uncover some hidden functionality in Drush that sparks an idea for how you can improve your code with the help of command info altering!