From 3bcabf43b4f1c98ae112741836d476b35065b881 Mon Sep 17 00:00:00 2001 From: Moshe Weitzman Date: Wed, 27 Dec 2023 15:40:58 -0500 Subject: [PATCH] Attribute also does the validate (#5818) --- composer.json | 2 +- composer.lock | 16 +++++----- src/Attributes/HookSelector.php | 1 + src/Attributes/ValidateConfigName.php | 15 ++++++---- src/Attributes/ValidateEntityLoad.php | 17 +++++++---- src/Attributes/ValidateFileExists.php | 24 +++++++++++---- src/Attributes/ValidateModulesEnabled.php | 14 +++++---- src/Attributes/ValidatePermissions.php | 23 +++++++++++---- src/Attributes/ValidatePhpExtensions.php | 14 +++++---- src/Attributes/ValidateQueueName.php | 36 +++++++++++++++++++++++ src/Attributes/ValidatorBase.php | 25 ++++++++++++++++ src/Attributes/ValidatorInterface.php | 10 +++++++ src/Commands/ValidatorsCommands.php | 9 +++++- src/Commands/config/ConfigCommands.php | 2 ++ src/Commands/core/QueueCommands.php | 7 +++-- 15 files changed, 172 insertions(+), 43 deletions(-) create mode 100644 src/Attributes/ValidateQueueName.php create mode 100644 src/Attributes/ValidatorBase.php create mode 100644 src/Attributes/ValidatorInterface.php diff --git a/composer.json b/composer.json index 1f0c4bc054..8a757e1810 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,7 @@ "composer-runtime-api": "^2.2", "chi-teck/drupal-code-generator": "^3.0", "composer/semver": "^1.4 || ^3", - "consolidation/annotated-command": "^4.9.1", + "consolidation/annotated-command": "^4.9.2", "consolidation/config": "^2.1.2", "consolidation/filter-via-dot-access-data": "^2.0.2", "consolidation/output-formatters": "^4.3.2", diff --git a/composer.lock b/composer.lock index 4c8aaeec2e..920ccfd317 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "338ce4b80d57c0a055bd5edd55611dca", + "content-hash": "a9c6f617e289f95553c5b68103e8b637", "packages": [ { "name": "chi-teck/drupal-code-generator", @@ -149,16 +149,16 @@ }, { "name": "consolidation/annotated-command", - "version": "4.9.1", + "version": "4.9.2", "source": { "type": "git", "url": "https://github.com/consolidation/annotated-command.git", - "reference": "e01152f698eff4cb5df3ebfe5e097ef335dbd3c9" + "reference": "b5255dcbee1de95036185062a103dabc622224de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/e01152f698eff4cb5df3ebfe5e097ef335dbd3c9", - "reference": "e01152f698eff4cb5df3ebfe5e097ef335dbd3c9", + "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/b5255dcbee1de95036185062a103dabc622224de", + "reference": "b5255dcbee1de95036185062a103dabc622224de", "shasum": "" }, "require": { @@ -199,9 +199,9 @@ "description": "Initialize Symfony Console commands from annotated command class methods.", "support": { "issues": "https://github.com/consolidation/annotated-command/issues", - "source": "https://github.com/consolidation/annotated-command/tree/4.9.1" + "source": "https://github.com/consolidation/annotated-command/tree/4.9.2" }, - "time": "2023-05-20T04:19:01+00:00" + "time": "2023-12-26T14:30:50+00:00" }, { "name": "consolidation/config", @@ -7688,5 +7688,5 @@ "platform-overrides": { "php": "8.1" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/src/Attributes/HookSelector.php b/src/Attributes/HookSelector.php index 741fbe2f95..2fde89cda3 100644 --- a/src/Attributes/HookSelector.php +++ b/src/Attributes/HookSelector.php @@ -6,6 +6,7 @@ use Attribute; +#[Deprecated('Create an Attribute class that commands can use.')] #[Attribute(Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class HookSelector extends \Consolidation\AnnotatedCommand\Attributes\HookSelector { diff --git a/src/Attributes/ValidateConfigName.php b/src/Attributes/ValidateConfigName.php index 1c11528ef7..db0ba33dea 100644 --- a/src/Attributes/ValidateConfigName.php +++ b/src/Attributes/ValidateConfigName.php @@ -5,11 +5,11 @@ namespace Drush\Attributes; use Attribute; -use Consolidation\AnnotatedCommand\Parser\CommandInfo; -use Drush\Commands\config\ConfigCommands; +use Consolidation\AnnotatedCommand\CommandData; +use Consolidation\AnnotatedCommand\CommandError; #[Attribute(Attribute::TARGET_METHOD)] -class ValidateConfigName +class ValidateConfigName extends ValidatorBase implements ValidatorInterface { /** * @param string $argumentName @@ -20,8 +20,13 @@ public function __construct( ) { } - public static function handle(\ReflectionAttribute $attribute, CommandInfo $commandInfo) + public function validate(CommandData $commandData) { - $commandInfo->addAnnotation(ConfigCommands::VALIDATE_CONFIG_NAME, $attribute->newInstance()->argumentName); + $configName = $commandData->input()->getArgument($this->argumentName); + $config = \Drupal::config($configName); + if ($config->isNew()) { + $msg = dt('Config !name does not exist', ['!name' => $configName]); + return new CommandError($msg); + } } } diff --git a/src/Attributes/ValidateEntityLoad.php b/src/Attributes/ValidateEntityLoad.php index f7cc915f94..a8edc12aee 100644 --- a/src/Attributes/ValidateEntityLoad.php +++ b/src/Attributes/ValidateEntityLoad.php @@ -5,11 +5,12 @@ namespace Drush\Attributes; use Attribute; -use Consolidation\AnnotatedCommand\Parser\CommandInfo; -use Drush\Commands\ValidatorsCommands; +use Consolidation\AnnotatedCommand\CommandData; +use Consolidation\AnnotatedCommand\CommandError; +use Drush\Utils\StringUtils; #[Attribute(Attribute::TARGET_METHOD)] -class ValidateEntityLoad +class ValidateEntityLoad extends ValidatorBase implements ValidatorInterface { /** * @param $entityType @@ -23,9 +24,13 @@ public function __construct( ) { } - public static function handle(\ReflectionAttribute $attribute, CommandInfo $commandInfo) + public function validate(CommandData $commandData) { - $args = $attribute->getArguments(); - $commandInfo->addAnnotation(ValidatorsCommands::VALIDATE_ENTITY_LOAD, "{$args['entityType']} {$args['argumentName']}"); + $names = StringUtils::csvToArray($commandData->input()->getArgument($this->argumentName)); + $loaded = \Drupal::entityTypeManager()->getStorage($this->entityType)->loadMultiple($names); + if ($missing = array_diff($names, array_keys($loaded))) { + $msg = dt('Unable to load the !type: !str', ['!type' => $this->entityType, '!str' => implode(', ', $missing)]); + return new CommandError($msg); + } } } diff --git a/src/Attributes/ValidateFileExists.php b/src/Attributes/ValidateFileExists.php index d602629b5a..2e2cc613c2 100644 --- a/src/Attributes/ValidateFileExists.php +++ b/src/Attributes/ValidateFileExists.php @@ -5,10 +5,11 @@ namespace Drush\Attributes; use Attribute; -use Consolidation\AnnotatedCommand\Parser\CommandInfo; +use Consolidation\AnnotatedCommand\CommandData; +use Consolidation\AnnotatedCommand\CommandError; #[Attribute(Attribute::TARGET_METHOD)] -class ValidateFileExists +class ValidateFileExists extends ValidatorBase implements ValidatorInterface { /** * @param $argName @@ -19,9 +20,22 @@ public function __construct( ) { } - public static function handle(\ReflectionAttribute $attribute, CommandInfo $commandInfo) + public function validate(CommandData $commandData) { - $args = $attribute->getArguments(); - $commandInfo->addAnnotation('validate-file-exists', $args['argName']); + $missing = []; + $argName = $this->argName; + if ($commandData->input()->hasArgument($argName)) { + $path = $commandData->input()->getArgument($argName); + } elseif ($commandData->input()->hasOption($argName)) { + $path = $commandData->input()->getOption($argName); + } + if (!empty($path) && !file_exists($path)) { + $missing[] = $path; + } + + if ($missing) { + $msg = dt('File(s) not found: !paths', ['!paths' => implode(', ', $missing)]); + return new CommandError($msg); + } } } diff --git a/src/Attributes/ValidateModulesEnabled.php b/src/Attributes/ValidateModulesEnabled.php index db15803572..d3cd6b1267 100644 --- a/src/Attributes/ValidateModulesEnabled.php +++ b/src/Attributes/ValidateModulesEnabled.php @@ -5,10 +5,11 @@ namespace Drush\Attributes; use Attribute; -use Consolidation\AnnotatedCommand\Parser\CommandInfo; +use Consolidation\AnnotatedCommand\CommandData; +use Consolidation\AnnotatedCommand\CommandError; #[Attribute(Attribute::TARGET_METHOD)] -class ValidateModulesEnabled +class ValidateModulesEnabled extends ValidatorBase implements ValidatorInterface { /** * @param $modules @@ -19,9 +20,12 @@ public function __construct( ) { } - public static function handle(\ReflectionAttribute $attribute, CommandInfo $commandInfo) + public function validate(CommandData $commandData) { - $args = $attribute->getArguments(); - $commandInfo->addAnnotation('validate-module-enabled', $args['modules'] ?? $args[0]); + $missing = array_filter($this->modules, fn($module) => !\Drupal::moduleHandler()->moduleExists($module)); + if ($missing) { + $msg = dt('The following modules are required: !modules', ['!modules' => implode(', ', $missing)]); + return new CommandError($msg); + } } } diff --git a/src/Attributes/ValidatePermissions.php b/src/Attributes/ValidatePermissions.php index 3673696205..db6a4c2e8b 100644 --- a/src/Attributes/ValidatePermissions.php +++ b/src/Attributes/ValidatePermissions.php @@ -5,10 +5,12 @@ namespace Drush\Attributes; use Attribute; -use Consolidation\AnnotatedCommand\Parser\CommandInfo; +use Consolidation\AnnotatedCommand\CommandData; +use Consolidation\AnnotatedCommand\CommandError; +use Drush\Utils\StringUtils; #[Attribute(Attribute::TARGET_METHOD)] -class ValidatePermissions +class ValidatePermissions extends ValidatorBase implements ValidatorInterface { /** * @param $argName @@ -19,9 +21,20 @@ public function __construct( ) { } - public static function handle(\ReflectionAttribute $attribute, CommandInfo $commandInfo) + public function validate(CommandData $commandData) { - $args = $attribute->getArguments(); - $commandInfo->addAnnotation('validate-permissions', $args['argName']); + $missing = []; + $arg_or_option_name = $this->argName; + if ($commandData->input()->hasArgument($arg_or_option_name)) { + $permissions = StringUtils::csvToArray($commandData->input()->getArgument($arg_or_option_name)); + } else { + $permissions = StringUtils::csvToArray($commandData->input()->getOption($arg_or_option_name)); + } + $all_permissions = array_keys(\Drupal::service('user.permissions')->getPermissions()); + $missing = array_diff($permissions, $all_permissions); + if ($missing) { + $msg = dt('Permission(s) not found: !perms', ['!perms' => implode(', ', $missing)]); + return new CommandError($msg); + } } } diff --git a/src/Attributes/ValidatePhpExtensions.php b/src/Attributes/ValidatePhpExtensions.php index 368993c900..79af336c1c 100644 --- a/src/Attributes/ValidatePhpExtensions.php +++ b/src/Attributes/ValidatePhpExtensions.php @@ -5,10 +5,11 @@ namespace Drush\Attributes; use Attribute; -use Consolidation\AnnotatedCommand\Parser\CommandInfo; +use Consolidation\AnnotatedCommand\CommandData; +use Consolidation\AnnotatedCommand\CommandError; #[Attribute(Attribute::TARGET_METHOD)] -class ValidatePhpExtensions +class ValidatePhpExtensions extends ValidatorBase implements ValidatorInterface { /** * @param $extensions @@ -19,9 +20,12 @@ public function __construct( ) { } - public static function handle(\ReflectionAttribute $attribute, CommandInfo $commandInfo) + public function validate(CommandData $commandData) { - $args = $attribute->getArguments(); - $commandInfo->addAnnotation('validate-php-extension', $args['extensions'] ?? $args[0]); + $missing = array_filter($this->extensions, fn($extension) => !extension_loaded($extension)); + if ($missing) { + $msg = dt('The following PHP extensions are required: !extensions', ['!extensions' => implode(', ', $missing)]); + return new CommandError($msg); + } } } diff --git a/src/Attributes/ValidateQueueName.php b/src/Attributes/ValidateQueueName.php new file mode 100644 index 0000000000..0381ce27dc --- /dev/null +++ b/src/Attributes/ValidateQueueName.php @@ -0,0 +1,36 @@ +input()->getArgument($this->argumentName); + if (!in_array($queueName, self::getQueues())) { + $msg = dt('Queue not found: !name', ['!name' => $queueName]); + return new CommandError($msg); + } + } + + public static function getQueues(): array + { + return array_keys(\Drupal::service('plugin.manager.queue_worker')->getDefinitions()); + } +} diff --git a/src/Attributes/ValidatorBase.php b/src/Attributes/ValidatorBase.php new file mode 100644 index 0000000000..69ffa9d475 --- /dev/null +++ b/src/Attributes/ValidatorBase.php @@ -0,0 +1,25 @@ +newInstance(); + $hookManager = Drush::getContainer()->get('hookManager'); + $hookManager->add( + // Use a Closure to acquire $commandData. + fn(CommandData $commandData) => $instance->validate($commandData), + $hookManager::ARGUMENT_VALIDATOR, + $commandInfo->getName() + ); + } +} diff --git a/src/Attributes/ValidatorInterface.php b/src/Attributes/ValidatorInterface.php new file mode 100644 index 0000000000..1e8f620687 --- /dev/null +++ b/src/Attributes/ValidatorInterface.php @@ -0,0 +1,10 @@ + self::REQ, 'items-limit' => self::REQ, 'lease-time' => self::REQ]): void { @@ -157,7 +159,7 @@ public function qList($options = ['format' => 'table']): RowsOfFields */ #[CLI\Command(name: self::DELETE, aliases: ['queue-delete'])] #[CLI\Argument(name: 'name', description: 'The name of the queue to delete, as defined in either hook_queue_info or hook_cron_queue_info.')] - #[CLI\HookSelector(name: self::VALIDATE_QUEUE, value: 'name')] + #[CLI\ValidateQueueName(argumentName: 'name')] #[CLI\Complete(method_name_or_callable: 'queueComplete')] public function delete($name): void { @@ -169,6 +171,7 @@ public function delete($name): void /** * Validate that a queue exists. */ + #[Deprecated('Use CLI/ValidateQueueName Attribute instead')] #[CLI\Hook(type: HookManager::ARGUMENT_VALIDATOR, selector: self::VALIDATE_QUEUE)] public function validateQueueName(CommandData $commandData) {