diff --git a/docs/commands.md b/docs/commands.md index 36a0231669..a46791f078 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -42,6 +42,20 @@ It is also possible to use [version ranges](https://getcomposer.org/doc/articles In Drush 9, the default services file, `drush.services.yml`, will be used in instances where there is no `services` section in the Drush extras of the project's composer.json file. In Drush 10, however, the services section must exist, and must name the services file to be used. If a future Drush extension is written such that it only works with Drush 10 and later, then its entry would read `"drush.services.yml": "^10"`, and Drush 9 would not load the extension's commands. It is all the same recommended that Drush 9 extensions explicitly declare their services file with an appropriate version constraint. +Altering Drush Command Info +=========================== + +Drush command info (annotations) can be altered from other modules. This is done by creating and registering 'command info alterers'. Alterers are class services that are able to intercept and manipulate an existing command annotation. + +In order to alter an existing command info, follow the next steps: + +1. In the module that wants to alter a command info, add a service class that implements the `\Consolidation\AnnotatedCommand\CommandInfoAltererInterface`. +1. In the module `drush.services.yml` declare a service pointing to this class and tag the service with the `drush.command_info_alterer` tag. +1. In the class implement the alteration logic the `alterCommandInfo()` method. +1. Along with the alter code, it's strongly recommended to log a debug message explaining what exactly was altered. This would allow the easy debugging. Also it's a good practice to inject the the logger in the class constructor. + +For an example, see the alterer class provided by the testing 'woot' module: `tests/resources/modules/d8/woot/src/WootCommandInfoAlterer.php`. + Global Drush Commands ============================== diff --git a/src/Boot/DrupalBoot8.php b/src/Boot/DrupalBoot8.php index 5a1edaf38b..dc030260f1 100644 --- a/src/Boot/DrupalBoot8.php +++ b/src/Boot/DrupalBoot8.php @@ -192,6 +192,17 @@ public function bootstrapDrupalFull() // The upshot is that the list of console commands is not available // until after $kernel->boot() is called. $container = \Drupal::getContainer(); + + // Set the command info alterers. + if ($container->has(DrushServiceModifier::DRUSH_COMMAND_INFO_ALTERER_SERVICES)) { + $serviceCommandInfoAltererlist = $container->get(DrushServiceModifier::DRUSH_COMMAND_INFO_ALTERER_SERVICES); + $commandFactory = Drush::commandFactory(); + foreach ($serviceCommandInfoAltererlist->getCommandList() as $altererHandler) { + $commandFactory->addCommandInfoAlterer($altererHandler); + $this->logger->debug(dt('Commands are potentially altered in !class.', ['!class' => get_class($altererHandler)])); + } + } + $serviceCommandlist = $container->get(DrushServiceModifier::DRUSH_CONSOLE_SERVICES); if ($container->has(DrushServiceModifier::DRUSH_CONSOLE_SERVICES)) { foreach ($serviceCommandlist->getCommandList() as $command) { diff --git a/src/Drupal/DrushServiceModifier.php b/src/Drupal/DrushServiceModifier.php index eaa4ebd213..43be28d986 100644 --- a/src/Drupal/DrushServiceModifier.php +++ b/src/Drupal/DrushServiceModifier.php @@ -12,6 +12,8 @@ class DrushServiceModifier implements ServiceModifierInterface const DRUSH_CONSOLE_SERVICES = 'drush.console.services'; // Holds list of command classes implemented with annotated commands const DRUSH_COMMAND_SERVICES = 'drush.command.services'; + // Holds list of command info alterer classes. + const DRUSH_COMMAND_INFO_ALTERER_SERVICES = 'drush.command_info_alterer.services'; // Holds list of classes implementing Drupal Code Generator classes const DRUSH_GENERATOR_SERVICES = 'drush.generator.services'; @@ -26,6 +28,8 @@ public function alter(ContainerBuilder $container) $container->addCompilerPass(new FindCommandsCompilerPass(self::DRUSH_CONSOLE_SERVICES, 'console.command')); $container->register(self::DRUSH_COMMAND_SERVICES, 'Drush\Command\ServiceCommandlist'); $container->addCompilerPass(new FindCommandsCompilerPass(self::DRUSH_COMMAND_SERVICES, 'drush.command')); + $container->register(self::DRUSH_COMMAND_INFO_ALTERER_SERVICES, 'Drush\Command\ServiceCommandlist'); + $container->addCompilerPass(new FindCommandsCompilerPass(self::DRUSH_COMMAND_INFO_ALTERER_SERVICES, 'drush.command_info_alterer')); $container->register(self::DRUSH_GENERATOR_SERVICES, 'Drush\Command\ServiceCommandlist'); $container->addCompilerPass(new FindCommandsCompilerPass(self::DRUSH_GENERATOR_SERVICES, 'drush.generator')); } @@ -42,6 +46,7 @@ public function check($container_definition) return isset($container_definition['services'][self::DRUSH_CONSOLE_SERVICES]) && isset($container_definition['services'][self::DRUSH_COMMAND_SERVICES]) && + isset($container_definition['services'][self::DRUSH_COMMAND_INFO_ALTERER_SERVICES]) && isset($container_definition['services'][self::DRUSH_GENERATOR_SERVICES]); } } diff --git a/tests/CommandInfoAlterTest.php b/tests/CommandInfoAlterTest.php new file mode 100644 index 0000000000..afd4b2e9c8 --- /dev/null +++ b/tests/CommandInfoAlterTest.php @@ -0,0 +1,36 @@ +setUpDrupal(1, true); + $this->setupModulesForTests(['woot'], Path::join(__DIR__, 'resources/modules/d8')); + $this->drush('pm-enable', ['woot']); + $this->drush('woot:altered', [], ['help' => true, 'debug' => true]); + $this->assertNotContains('woot-initial-alias', $this->getOutput()); + $this->assertContains('woot-new-alias', $this->getOutput()); + + // Check the debug messages. + $this->assertContains('[debug] Commands are potentially altered in Drupal\woot\WootCommandInfoAlterer.', $this->getErrorOutput()); + $this->assertContains("[debug] Module 'woot' changed the alias of 'woot:altered' command into 'woot-new-alias' in Drupal\woot\WootCommandInfoAlterer::alterCommandInfo().", $this->getErrorOutput()); + + // Try to run the command with the initial alias. + $this->drush('woot-initial-alias', [], [], null, null, self::EXIT_ERROR); + // Run the command with the altered alias. + $this->drush('woot-new-alias'); + } +} diff --git a/tests/resources/modules/d8/woot/drush.services.yml b/tests/resources/modules/d8/woot/drush.services.yml index 8b02ef4c68..fdacd54184 100644 --- a/tests/resources/modules/d8/woot/drush.services.yml +++ b/tests/resources/modules/d8/woot/drush.services.yml @@ -21,3 +21,8 @@ services: arguments: ['@module_handler'] tags: - { name: drush.generator } + woot.command_info_alter: + class: Drupal\woot\WootCommandInfoAlterer + arguments: ['@logger.factory'] + tags: + - { name: drush.command_info_alterer } diff --git a/tests/resources/modules/d8/woot/src/Commands/WootCommands.php b/tests/resources/modules/d8/woot/src/Commands/WootCommands.php index 2da37ee007..9f8596eb22 100644 --- a/tests/resources/modules/d8/woot/src/Commands/WootCommands.php +++ b/tests/resources/modules/d8/woot/src/Commands/WootCommands.php @@ -70,4 +70,14 @@ public function tryFormatters($options = ['format' => 'table', 'fields' => '']) ]; return new RowsOfFields($outputData); } + + /** + * This command info is altered. + * + * @command woot:altered + * @aliases woot-initial-alias + */ + public function wootAltered() + { + } } diff --git a/tests/resources/modules/d8/woot/src/WootCommandInfoAlterer.php b/tests/resources/modules/d8/woot/src/WootCommandInfoAlterer.php new file mode 100644 index 0000000000..276340db75 --- /dev/null +++ b/tests/resources/modules/d8/woot/src/WootCommandInfoAlterer.php @@ -0,0 +1,28 @@ +logger = $loggerFactory->get('drush'); + } + + public function alterCommandInfo(CommandInfo $commandInfo, $commandFileInstance) + { + if ($commandInfo->getName() === 'woot:altered') { + $commandInfo->setAliases('woot-new-alias'); + $this->logger->debug(dt("Module 'woot' changed the alias of 'woot:altered' command into 'woot-new-alias' in " . __METHOD__ . '().')); + } + } +}