diff --git a/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php b/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php index f04db50c8ced7..8ecd220a2f0b9 100644 --- a/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php +++ b/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php @@ -34,6 +34,7 @@ class ServerLogCommand extends Command private $handler; protected static $defaultName = 'server:log'; + protected static $defaultDescription = 'Starts a log server that displays logs in real time'; public function isEnabled() { @@ -60,7 +61,7 @@ protected function configure() ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The line format', ConsoleFormatter::SIMPLE_FORMAT) ->addOption('date-format', null, InputOption::VALUE_REQUIRED, 'The date format', ConsoleFormatter::SIMPLE_DATE) ->addOption('filter', null, InputOption::VALUE_REQUIRED, 'An expression to filter log. Example: "level > 200 or channel in [\'app\', \'doctrine\']"') - ->setDescription('Starts a log server that displays logs in real time') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' %command.name% starts a log server to display in real time the log messages generated by your application: diff --git a/src/Symfony/Bridge/Twig/Command/DebugCommand.php b/src/Symfony/Bridge/Twig/Command/DebugCommand.php index 8962a41dd366e..88fae85aaf102 100644 --- a/src/Symfony/Bridge/Twig/Command/DebugCommand.php +++ b/src/Symfony/Bridge/Twig/Command/DebugCommand.php @@ -33,6 +33,7 @@ class DebugCommand extends Command { protected static $defaultName = 'debug:twig'; + protected static $defaultDescription = 'Shows a list of twig functions, filters, globals and tests'; private $twig; private $projectDir; @@ -60,7 +61,7 @@ protected function configure() new InputOption('filter', null, InputOption::VALUE_REQUIRED, 'Show details for all entries matching this filter'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (text or json)', 'text'), ]) - ->setDescription('Shows a list of twig functions, filters, globals and tests') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command outputs a list of twig functions, filters, globals and tests. diff --git a/src/Symfony/Bridge/Twig/Command/LintCommand.php b/src/Symfony/Bridge/Twig/Command/LintCommand.php index 505f05959bb68..39d79f0b1bba6 100644 --- a/src/Symfony/Bridge/Twig/Command/LintCommand.php +++ b/src/Symfony/Bridge/Twig/Command/LintCommand.php @@ -35,6 +35,7 @@ class LintCommand extends Command { protected static $defaultName = 'lint:twig'; + protected static $defaultDescription = 'Lints a template and outputs encountered errors'; private $twig; @@ -48,7 +49,7 @@ public function __construct(Environment $twig) protected function configure() { $this - ->setDescription('Lints a template and outputs encountered errors') + ->setDescription(self::$defaultDescription) ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt') ->addOption('show-deprecations', null, InputOption::VALUE_NONE, 'Show deprecations as errors') ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') diff --git a/src/Symfony/Bundle/DebugBundle/Resources/config/services.php b/src/Symfony/Bundle/DebugBundle/Resources/config/services.php index abde96d0625ec..d0f57c092872e 100644 --- a/src/Symfony/Bundle/DebugBundle/Resources/config/services.php +++ b/src/Symfony/Bundle/DebugBundle/Resources/config/services.php @@ -11,7 +11,9 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Monolog\Formatter\FormatterInterface; use Symfony\Bridge\Monolog\Command\ServerLogCommand; +use Symfony\Bridge\Monolog\Formatter\ConsoleFormatter; use Symfony\Bridge\Twig\Extension\DumpExtension; use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector; use Symfony\Component\HttpKernel\EventListener\DumpListener; @@ -127,9 +129,12 @@ 'html' => inline_service(HtmlDescriptor::class)->args([service('var_dumper.html_dumper')]), ], ]) - ->tag('console.command', ['command' => 'server:dump']) + ->tag('console.command') ->set('monolog.command.server_log', ServerLogCommand::class) - ->tag('console.command', ['command' => 'server:log']) ; + + if (class_exists(ConsoleFormatter::class) && interface_exists(FormatterInterface::class)) { + $container->services()->get('monolog.command.server_log')->tag('console.command'); + } }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php index 4c9f968736c59..d1de7c651b9b1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php @@ -30,6 +30,7 @@ class AboutCommand extends Command { protected static $defaultName = 'about'; + protected static $defaultDescription = 'Displays information about the current project'; /** * {@inheritdoc} @@ -37,7 +38,7 @@ class AboutCommand extends Command protected function configure() { $this - ->setDescription('Displays information about the current project') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOT' The %command.name% command displays information about the current Symfony project. diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php index 70ad92343eb25..ae8ca199879fc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php @@ -40,6 +40,7 @@ class AssetsInstallCommand extends Command public const METHOD_RELATIVE_SYMLINK = 'relative symlink'; protected static $defaultName = 'assets:install'; + protected static $defaultDescription = 'Installs bundles web assets under a public directory'; private $filesystem; private $projectDir; @@ -64,7 +65,7 @@ protected function configure() ->addOption('symlink', null, InputOption::VALUE_NONE, 'Symlinks the assets instead of copying it') ->addOption('relative', null, InputOption::VALUE_NONE, 'Make relative symlinks') ->addOption('no-cleanup', null, InputOption::VALUE_NONE, 'Do not remove the assets of the bundles that no longer exist') - ->setDescription('Installs bundles web assets under a public directory') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOT' The %command.name% command installs bundle assets into a given directory (e.g. the public directory). diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php index b9a829471f098..5b9271920e7ae 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php @@ -36,6 +36,7 @@ class CacheClearCommand extends Command { protected static $defaultName = 'cache:clear'; + protected static $defaultDescription = 'Clears the cache'; private $cacheClearer; private $filesystem; @@ -58,7 +59,7 @@ protected function configure() new InputOption('no-warmup', '', InputOption::VALUE_NONE, 'Do not warm up the cache'), new InputOption('no-optional-warmers', '', InputOption::VALUE_NONE, 'Skip optional cache warmers (faster)'), ]) - ->setDescription('Clears the cache') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command clears the application cache for a given environment and debug mode: diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php index 50aacd9bbd73d..2ffde574f16b0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php @@ -28,6 +28,7 @@ final class CachePoolClearCommand extends Command { protected static $defaultName = 'cache:pool:clear'; + protected static $defaultDescription = 'Clears cache pools'; private $poolClearer; @@ -47,7 +48,7 @@ protected function configure() ->setDefinition([ new InputArgument('pools', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'A list of cache pools or cache pool clearers'), ]) - ->setDescription('Clears cache pools') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command clears the given cache pools or cache pool clearers. diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolDeleteCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolDeleteCommand.php index 2a7a2fe513040..6ac82925e6a3d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolDeleteCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolDeleteCommand.php @@ -26,6 +26,7 @@ final class CachePoolDeleteCommand extends Command { protected static $defaultName = 'cache:pool:delete'; + protected static $defaultDescription = 'Deletes an item from a cache pool'; private $poolClearer; @@ -46,7 +47,7 @@ protected function configure() new InputArgument('pool', InputArgument::REQUIRED, 'The cache pool from which to delete an item'), new InputArgument('key', InputArgument::REQUIRED, 'The cache key to delete from the pool'), ]) - ->setDescription('Deletes an item from a cache pool') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% deletes an item from a given cache pool. diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolListCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolListCommand.php index 7b725411d5015..4a4b1eb2fa49e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolListCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolListCommand.php @@ -24,6 +24,7 @@ final class CachePoolListCommand extends Command { protected static $defaultName = 'cache:pool:list'; + protected static $defaultDescription = 'List available cache pools'; private $poolNames; @@ -40,7 +41,7 @@ public function __construct(array $poolNames) protected function configure() { $this - ->setDescription('List available cache pools') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command lists all available cache pools. EOF diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolPruneCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolPruneCommand.php index 65f3ff6b5802e..bc5b7f861fb72 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolPruneCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolPruneCommand.php @@ -25,6 +25,7 @@ final class CachePoolPruneCommand extends Command { protected static $defaultName = 'cache:pool:prune'; + protected static $defaultDescription = 'Prunes cache pools'; private $pools; @@ -44,7 +45,7 @@ public function __construct(iterable $pools) protected function configure() { $this - ->setDescription('Prunes cache pools') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command deletes all expired items from all pruneable pools. diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php index 8feb2dd9c51b2..a79a1106d6e6d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php @@ -29,6 +29,7 @@ class CacheWarmupCommand extends Command { protected static $defaultName = 'cache:warmup'; + protected static $defaultDescription = 'Warms up an empty cache'; private $cacheWarmer; @@ -48,7 +49,7 @@ protected function configure() ->setDefinition([ new InputOption('no-optional-warmers', '', InputOption::VALUE_NONE, 'Skip optional cache warmers (faster)'), ]) - ->setDescription('Warms up an empty cache') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command warms up the cache. diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php index c68f17e120bbd..8a526cb99143d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php @@ -33,6 +33,7 @@ class ConfigDebugCommand extends AbstractConfigCommand { protected static $defaultName = 'debug:config'; + protected static $defaultDescription = 'Dumps the current configuration for an extension'; /** * {@inheritdoc} @@ -44,7 +45,7 @@ protected function configure() new InputArgument('name', InputArgument::OPTIONAL, 'The bundle name or the extension alias'), new InputArgument('path', InputArgument::OPTIONAL, 'The configuration option path'), ]) - ->setDescription('Dumps the current configuration for an extension') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command dumps the current configuration for an extension/bundle. diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php index 62dcc856e0f56..c1b0cf1626c19 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php @@ -36,6 +36,7 @@ class ConfigDumpReferenceCommand extends AbstractConfigCommand { protected static $defaultName = 'config:dump-reference'; + protected static $defaultDescription = 'Dumps the default configuration for an extension'; /** * {@inheritdoc} @@ -48,7 +49,7 @@ protected function configure() new InputArgument('path', InputArgument::OPTIONAL, 'The configuration option path'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (yaml or xml)', 'yaml'), ]) - ->setDescription('Dumps the default configuration for an extension') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command dumps the default configuration for an extension/bundle. diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php index 7c330dbdf4f85..8b41fc1dce7a0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php @@ -35,6 +35,7 @@ class ContainerDebugCommand extends Command use BuildDebugContainerTrait; protected static $defaultName = 'debug:container'; + protected static $defaultDescription = 'Displays current services for an application'; /** * {@inheritdoc} @@ -57,7 +58,7 @@ protected function configure() new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'), new InputOption('deprecations', null, InputOption::VALUE_NONE, 'Displays deprecations generated when compiling and warming up the container'), ]) - ->setDescription('Displays current services for an application') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command displays all configured public services: diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php index f059df1ee62fe..02ef668cfbee6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php @@ -30,6 +30,7 @@ final class ContainerLintCommand extends Command { protected static $defaultName = 'lint:container'; + protected static $defaultDescription = 'Ensures that arguments injected into services match type declarations'; /** * @var ContainerBuilder @@ -42,7 +43,7 @@ final class ContainerLintCommand extends Command protected function configure() { $this - ->setDescription('Ensures that arguments injected into services match type declarations') + ->setDescription(self::$defaultDescription) ->setHelp('This command parses service definitions and ensures that injected values match the type declarations of each services\' class.') ; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php index 32bd630f32516..8aceb5c0a66df 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php @@ -30,6 +30,7 @@ class DebugAutowiringCommand extends ContainerDebugCommand { protected static $defaultName = 'debug:autowiring'; + protected static $defaultDescription = 'Lists classes/interfaces you can use for autowiring'; private $supportsHref; private $fileLinkFormatter; @@ -50,7 +51,7 @@ protected function configure() new InputArgument('search', InputArgument::OPTIONAL, 'A search filter'), new InputOption('all', null, InputOption::VALUE_NONE, 'Show also services that are not aliased'), ]) - ->setDescription('Lists classes/interfaces you can use for autowiring') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command displays the classes and interfaces that you can use as type-hints for autowiring: diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php index a053d96dd8fdb..b828dee78139e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php @@ -33,6 +33,7 @@ class EventDispatcherDebugCommand extends Command private const DEFAULT_DISPATCHER = 'event_dispatcher'; protected static $defaultName = 'debug:event-dispatcher'; + protected static $defaultDescription = 'Displays configured listeners for an application'; private $dispatchers; public function __construct(ContainerInterface $dispatchers) @@ -54,7 +55,7 @@ protected function configure() new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'), ]) - ->setDescription('Displays configured listeners for an application') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command displays all configured listeners: diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php index 16acf7a7db9c4..24c306c9d901d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php @@ -36,6 +36,7 @@ class RouterDebugCommand extends Command use BuildDebugContainerTrait; protected static $defaultName = 'debug:router'; + protected static $defaultDescription = 'Displays current routes for an application'; private $router; private $fileLinkFormatter; @@ -59,7 +60,7 @@ protected function configure() new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw route(s)'), ]) - ->setDescription('Displays current routes for an application') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% displays the configured routes: diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php index 1e2fefbbacb26..85ce52608d0d4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php @@ -31,6 +31,7 @@ class RouterMatchCommand extends Command { protected static $defaultName = 'router:match'; + protected static $defaultDescription = 'Helps debug routes by simulating a path info match'; private $router; private $expressionLanguageProviders; @@ -55,7 +56,7 @@ protected function configure() new InputOption('scheme', null, InputOption::VALUE_REQUIRED, 'Sets the URI scheme (usually http or https)'), new InputOption('host', null, InputOption::VALUE_REQUIRED, 'Sets the URI host'), ]) - ->setDescription('Helps debug routes by simulating a path info match') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% shows which routes match a given request and which don't and for what reason: diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsDecryptToLocalCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsDecryptToLocalCommand.php index 1571c7f1b7c79..0ca168b5fd177 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsDecryptToLocalCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsDecryptToLocalCommand.php @@ -27,6 +27,7 @@ final class SecretsDecryptToLocalCommand extends Command { protected static $defaultName = 'secrets:decrypt-to-local'; + protected static $defaultDescription = 'Decrypts all secrets and stores them in the local vault'; private $vault; private $localVault; @@ -42,7 +43,7 @@ public function __construct(AbstractVault $vault, AbstractVault $localVault = nu protected function configure() { $this - ->setDescription('Decrypts all secrets and stores them in the local vault') + ->setDescription(self::$defaultDescription) ->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces overriding of secrets that already exist in the local vault') ->setHelp(<<<'EOF' The %command.name% command decrypts all secrets and copies them in the local vault. diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsEncryptFromLocalCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsEncryptFromLocalCommand.php index e1c8904c698b5..d9a635f210391 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsEncryptFromLocalCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsEncryptFromLocalCommand.php @@ -26,6 +26,7 @@ final class SecretsEncryptFromLocalCommand extends Command { protected static $defaultName = 'secrets:encrypt-from-local'; + protected static $defaultDescription = 'Encrypts all local secrets to the vault'; private $vault; private $localVault; @@ -41,7 +42,7 @@ public function __construct(AbstractVault $vault, AbstractVault $localVault = nu protected function configure() { $this - ->setDescription('Encrypts all local secrets to the vault') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command encrypts all locally overridden secrets to the vault. diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsGenerateKeysCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsGenerateKeysCommand.php index 18dba29ac9797..abd052918e2eb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsGenerateKeysCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsGenerateKeysCommand.php @@ -29,6 +29,7 @@ final class SecretsGenerateKeysCommand extends Command { protected static $defaultName = 'secrets:generate-keys'; + protected static $defaultDescription = 'Generates new encryption keys'; private $vault; private $localVault; @@ -44,7 +45,7 @@ public function __construct(AbstractVault $vault, AbstractVault $localVault = nu protected function configure() { $this - ->setDescription('Generates new encryption keys') + ->setDescription(self::$defaultDescription) ->addOption('local', 'l', InputOption::VALUE_NONE, 'Updates the local vault.') ->addOption('rotate', 'r', InputOption::VALUE_NONE, 'Re-encrypts existing secrets with the newly generated keys.') ->setHelp(<<<'EOF' diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php index 9848ab993331e..f828776b16da3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php @@ -30,6 +30,7 @@ final class SecretsListCommand extends Command { protected static $defaultName = 'secrets:list'; + protected static $defaultDescription = 'Lists all secrets'; private $vault; private $localVault; @@ -45,7 +46,7 @@ public function __construct(AbstractVault $vault, AbstractVault $localVault = nu protected function configure() { $this - ->setDescription('Lists all secrets') + ->setDescription(self::$defaultDescription) ->addOption('reveal', 'r', InputOption::VALUE_NONE, 'Display decrypted values alongside names') ->setHelp(<<<'EOF' The %command.name% command list all stored secrets. diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsRemoveCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsRemoveCommand.php index 2f06cb4592545..1ea079b3dc338 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsRemoveCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsRemoveCommand.php @@ -29,6 +29,7 @@ final class SecretsRemoveCommand extends Command { protected static $defaultName = 'secrets:remove'; + protected static $defaultDescription = 'Removes a secret from the vault'; private $vault; private $localVault; @@ -44,7 +45,7 @@ public function __construct(AbstractVault $vault, AbstractVault $localVault = nu protected function configure() { $this - ->setDescription('Removes a secret from the vault') + ->setDescription(self::$defaultDescription) ->addArgument('name', InputArgument::REQUIRED, 'The name of the secret') ->addOption('local', 'l', InputOption::VALUE_NONE, 'Updates the local vault.') ->setHelp(<<<'EOF' diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsSetCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsSetCommand.php index 95b8f6a0f622b..3753691a7379b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsSetCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsSetCommand.php @@ -30,6 +30,7 @@ final class SecretsSetCommand extends Command { protected static $defaultName = 'secrets:set'; + protected static $defaultDescription = 'Sets a secret in the vault'; private $vault; private $localVault; @@ -45,7 +46,7 @@ public function __construct(AbstractVault $vault, AbstractVault $localVault = nu protected function configure() { $this - ->setDescription('Sets a secret in the vault') + ->setDescription(self::$defaultDescription) ->addArgument('name', InputArgument::REQUIRED, 'The name of the secret') ->addArgument('file', InputArgument::OPTIONAL, 'A file where to read the secret from or "-" for reading from STDIN') ->addOption('local', 'l', InputOption::VALUE_NONE, 'Updates the local vault.') diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php index ec17387d9b078..8cb80babe61cb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php @@ -47,6 +47,7 @@ class TranslationDebugCommand extends Command public const MESSAGE_EQUALS_FALLBACK = 2; protected static $defaultName = 'debug:translation'; + protected static $defaultDescription = 'Displays translation messages information'; private $translator; private $reader; @@ -83,7 +84,7 @@ protected function configure() new InputOption('only-unused', null, InputOption::VALUE_NONE, 'Displays only unused messages'), new InputOption('all', null, InputOption::VALUE_NONE, 'Load messages from all registered bundles'), ]) - ->setDescription('Displays translation messages information') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command helps finding unused or missing translation messages and comparing them with the fallback ones by inspecting the diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index 9274d7e70baf6..2e4dd776b779b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -42,6 +42,7 @@ class TranslationUpdateCommand extends Command private const SORT_ORDERS = [self::ASC, self::DESC]; protected static $defaultName = 'translation:update'; + protected static $defaultDescription = 'Updates the translation file'; private $writer; private $reader; @@ -85,7 +86,7 @@ protected function configure() new InputOption('sort', null, InputOption::VALUE_OPTIONAL, 'Return list of messages sorted alphabetically', 'asc'), new InputOption('as-tree', null, InputOption::VALUE_OPTIONAL, 'Dump the messages as a tree-like structure: The given value defines the level where to switch to inline YAML'), ]) - ->setDescription('Updates the translation file') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command extracts translation strings from templates of a given bundle or the default translations directory. It can display them or merge diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php index cec930da1c0da..d3cc460318e4b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php @@ -30,6 +30,7 @@ class WorkflowDumpCommand extends Command { protected static $defaultName = 'workflow:dump'; + protected static $defaultDescription = 'Dump a workflow'; /** * {@inheritdoc} @@ -43,7 +44,7 @@ protected function configure() new InputOption('label', 'l', InputOption::VALUE_REQUIRED, 'Labels a graph'), new InputOption('dump-format', null, InputOption::VALUE_REQUIRED, 'The dump format [dot|puml]', 'dot'), ]) - ->setDescription('Dump a workflow') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command dumps the graphical representation of a workflow in different formats diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php index 81531599f1f6c..5aea86ba7a06b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php @@ -62,76 +62,76 @@ ->tag('kernel.event_subscriber') ->set('console.command.about', AboutCommand::class) - ->tag('console.command', ['command' => 'about']) + ->tag('console.command') ->set('console.command.assets_install', AssetsInstallCommand::class) ->args([ service('filesystem'), param('kernel.project_dir'), ]) - ->tag('console.command', ['command' => 'assets:install']) + ->tag('console.command') ->set('console.command.cache_clear', CacheClearCommand::class) ->args([ service('cache_clearer'), service('filesystem'), ]) - ->tag('console.command', ['command' => 'cache:clear']) + ->tag('console.command') ->set('console.command.cache_pool_clear', CachePoolClearCommand::class) ->args([ service('cache.global_clearer'), ]) - ->tag('console.command', ['command' => 'cache:pool:clear']) + ->tag('console.command') ->set('console.command.cache_pool_prune', CachePoolPruneCommand::class) ->args([ [], ]) - ->tag('console.command', ['command' => 'cache:pool:prune']) + ->tag('console.command') ->set('console.command.cache_pool_delete', CachePoolDeleteCommand::class) ->args([ service('cache.global_clearer'), ]) - ->tag('console.command', ['command' => 'cache:pool:delete']) + ->tag('console.command') ->set('console.command.cache_pool_list', CachePoolListCommand::class) ->args([ null, ]) - ->tag('console.command', ['command' => 'cache:pool:list']) + ->tag('console.command') ->set('console.command.cache_warmup', CacheWarmupCommand::class) ->args([ service('cache_warmer'), ]) - ->tag('console.command', ['command' => 'cache:warmup']) + ->tag('console.command') ->set('console.command.config_debug', ConfigDebugCommand::class) - ->tag('console.command', ['command' => 'debug:config']) + ->tag('console.command') ->set('console.command.config_dump_reference', ConfigDumpReferenceCommand::class) - ->tag('console.command', ['command' => 'config:dump-reference']) + ->tag('console.command') ->set('console.command.container_debug', ContainerDebugCommand::class) - ->tag('console.command', ['command' => 'debug:container']) + ->tag('console.command') ->set('console.command.container_lint', ContainerLintCommand::class) - ->tag('console.command', ['command' => 'lint:container']) + ->tag('console.command') ->set('console.command.debug_autowiring', DebugAutowiringCommand::class) ->args([ null, service('debug.file_link_formatter')->nullOnInvalid(), ]) - ->tag('console.command', ['command' => 'debug:autowiring']) + ->tag('console.command') ->set('console.command.event_dispatcher_debug', EventDispatcherDebugCommand::class) ->args([ tagged_locator('event_dispatcher.dispatcher'), ]) - ->tag('console.command', ['command' => 'debug:event-dispatcher']) + ->tag('console.command') ->set('console.command.messenger_consume_messages', ConsumeMessagesCommand::class) ->args([ @@ -141,7 +141,7 @@ service('logger')->nullOnInvalid(), [], // Receiver names ]) - ->tag('console.command', ['command' => 'messenger:consume']) + ->tag('console.command') ->tag('monolog.logger', ['channel' => 'messenger']) ->set('console.command.messenger_setup_transports', SetupTransportsCommand::class) @@ -149,19 +149,19 @@ service('messenger.receiver_locator'), [], // Receiver names ]) - ->tag('console.command', ['command' => 'messenger:setup-transports']) + ->tag('console.command') ->set('console.command.messenger_debug', DebugCommand::class) ->args([ [], // Message to handlers mapping ]) - ->tag('console.command', ['command' => 'debug:messenger']) + ->tag('console.command') ->set('console.command.messenger_stop_workers', StopWorkersCommand::class) ->args([ service('cache.messenger.restart_workers_signal'), ]) - ->tag('console.command', ['command' => 'messenger:stop-workers']) + ->tag('console.command') ->set('console.command.messenger_failed_messages_retry', FailedMessagesRetryCommand::class) ->args([ @@ -171,35 +171,35 @@ service('event_dispatcher'), service('logger'), ]) - ->tag('console.command', ['command' => 'messenger:failed:retry']) + ->tag('console.command') ->set('console.command.messenger_failed_messages_show', FailedMessagesShowCommand::class) ->args([ abstract_arg('Receiver name'), abstract_arg('Receiver'), ]) - ->tag('console.command', ['command' => 'messenger:failed:show']) + ->tag('console.command') ->set('console.command.messenger_failed_messages_remove', FailedMessagesRemoveCommand::class) ->args([ abstract_arg('Receiver name'), abstract_arg('Receiver'), ]) - ->tag('console.command', ['command' => 'messenger:failed:remove']) + ->tag('console.command') ->set('console.command.router_debug', RouterDebugCommand::class) ->args([ service('router'), service('debug.file_link_formatter')->nullOnInvalid(), ]) - ->tag('console.command', ['command' => 'debug:router']) + ->tag('console.command') ->set('console.command.router_match', RouterMatchCommand::class) ->args([ service('router'), tagged_iterator('routing.expression_language_provider'), ]) - ->tag('console.command', ['command' => 'router:match']) + ->tag('console.command') ->set('console.command.translation_debug', TranslationDebugCommand::class) ->args([ @@ -211,7 +211,7 @@ [], // Translator paths [], // Twig paths ]) - ->tag('console.command', ['command' => 'debug:translation']) + ->tag('console.command') ->set('console.command.translation_update', TranslationUpdateCommand::class) ->args([ @@ -224,22 +224,22 @@ [], // Translator paths [], // Twig paths ]) - ->tag('console.command', ['command' => 'translation:update']) + ->tag('console.command') ->set('console.command.validator_debug', ValidatorDebugCommand::class) ->args([ service('validator'), ]) - ->tag('console.command', ['command' => 'debug:validator']) + ->tag('console.command') ->set('console.command.workflow_dump', WorkflowDumpCommand::class) - ->tag('console.command', ['command' => 'workflow:dump']) + ->tag('console.command') ->set('console.command.xliff_lint', XliffLintCommand::class) - ->tag('console.command', ['command' => 'lint:xliff']) + ->tag('console.command') ->set('console.command.yaml_lint', YamlLintCommand::class) - ->tag('console.command', ['command' => 'lint:yaml']) + ->tag('console.command') ->set('console.command.form_debug', \Symfony\Component\Form\Command\DebugCommand::class) ->args([ @@ -250,48 +250,48 @@ [], // All type guessers are stored here by FormPass service('debug.file_link_formatter')->nullOnInvalid(), ]) - ->tag('console.command', ['command' => 'debug:form']) + ->tag('console.command') ->set('console.command.secrets_set', SecretsSetCommand::class) ->args([ service('secrets.vault'), service('secrets.local_vault')->nullOnInvalid(), ]) - ->tag('console.command', ['command' => 'secrets:set']) + ->tag('console.command') ->set('console.command.secrets_remove', SecretsRemoveCommand::class) ->args([ service('secrets.vault'), service('secrets.local_vault')->nullOnInvalid(), ]) - ->tag('console.command', ['command' => 'secrets:remove']) + ->tag('console.command') ->set('console.command.secrets_generate_key', SecretsGenerateKeysCommand::class) ->args([ service('secrets.vault'), service('secrets.local_vault')->ignoreOnInvalid(), ]) - ->tag('console.command', ['command' => 'secrets:generate-keys']) + ->tag('console.command') ->set('console.command.secrets_list', SecretsListCommand::class) ->args([ service('secrets.vault'), service('secrets.local_vault'), ]) - ->tag('console.command', ['command' => 'secrets:list']) + ->tag('console.command') ->set('console.command.secrets_decrypt_to_local', SecretsDecryptToLocalCommand::class) ->args([ service('secrets.vault'), service('secrets.local_vault')->ignoreOnInvalid(), ]) - ->tag('console.command', ['command' => 'secrets:decrypt-to-local']) + ->tag('console.command') ->set('console.command.secrets_encrypt_from_local', SecretsEncryptFromLocalCommand::class) ->args([ service('secrets.vault'), service('secrets.local_vault'), ]) - ->tag('console.command', ['command' => 'secrets:encrypt-from-local']) + ->tag('console.command') ; }; diff --git a/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php b/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php index de23fdd618a11..8352bc41a8b4c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php +++ b/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php @@ -34,6 +34,7 @@ class UserPasswordEncoderCommand extends Command { protected static $defaultName = 'security:encode-password'; + protected static $defaultDescription = 'Encodes a password'; private $encoderFactory; private $userClasses; @@ -52,7 +53,7 @@ public function __construct(EncoderFactoryInterface $encoderFactory, array $user protected function configure() { $this - ->setDescription('Encodes a password') + ->setDescription(self::$defaultDescription) ->addArgument('password', InputArgument::OPTIONAL, 'The plain password to encode.') ->addArgument('user-class', InputArgument::OPTIONAL, 'The User entity class path associated with the encoder used to encode the password.') ->addOption('empty-salt', null, InputOption::VALUE_NONE, 'Do not generate a salt or let the encoder generate one.') diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/console.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/console.php index a5ea6868a8bb6..61bc1f553e582 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/console.php +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/console.php @@ -20,6 +20,6 @@ service('security.encoder_factory'), abstract_arg('encoders user classes'), ]) - ->tag('console.command', ['command' => 'security:encode-password']) + ->tag('console.command') ; }; diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/console.php b/src/Symfony/Bundle/TwigBundle/Resources/config/console.php index 9abd75da19ffc..0dc7ebdb7a5ad 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/console.php +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/console.php @@ -24,10 +24,10 @@ param('twig.default_path'), service('debug.file_link_formatter')->nullOnInvalid(), ]) - ->tag('console.command', ['command' => 'debug:twig']) + ->tag('console.command') ->set('twig.command.lint', LintCommand::class) ->args([service('twig')]) - ->tag('console.command', ['command' => 'lint:twig']) + ->tag('console.command') ; }; diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index 1b995abb20a59..440b93dc5c6fe 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -13,6 +13,7 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\HelpCommand; +use Symfony\Component\Console\Command\LazyCommand; use Symfony\Component\Console\Command\ListCommand; use Symfony\Component\Console\Command\SignalableCommandInterface; use Symfony\Component\Console\CommandLoader\CommandLoaderInterface; @@ -489,8 +490,10 @@ public function add(Command $command) return null; } - // Will throw if the command is not correctly initialized. - $command->getDefinition(); + if (!$command instanceof LazyCommand) { + // Will throw if the command is not correctly initialized. + $command->getDefinition(); + } if (!$command->getName()) { throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_debug_type($command))); diff --git a/src/Symfony/Component/Console/CHANGELOG.md b/src/Symfony/Component/Console/CHANGELOG.md index 17a709a6e27e7..37689ab692db1 100644 --- a/src/Symfony/Component/Console/CHANGELOG.md +++ b/src/Symfony/Component/Console/CHANGELOG.md @@ -4,8 +4,10 @@ CHANGELOG 5.3 --- - * Added `GithubActionReporter` to render annotations in a Github Action - * Added `InputOption::VALUE_NEGATABLE` flag to handle `--foo`/`--no-foo` options. + * Add `GithubActionReporter` to render annotations in a Github Action + * Add `InputOption::VALUE_NEGATABLE` flag to handle `--foo`/`--no-foo` options + * Add the `Command::$defaultDescription` static property and the `description` attribute + on the `console.command` tag to allow the `list` command to instantiate commands lazily 5.2.0 ----- diff --git a/src/Symfony/Component/Console/Command/Command.php b/src/Symfony/Component/Console/Command/Command.php index b530f4d89fd2e..a1809acd2322a 100644 --- a/src/Symfony/Component/Console/Command/Command.php +++ b/src/Symfony/Component/Console/Command/Command.php @@ -39,6 +39,11 @@ class Command */ protected static $defaultName; + /** + * @var string|null The default command description + */ + protected static $defaultDescription; + private $application; private $name; private $processTitle; @@ -65,6 +70,17 @@ public static function getDefaultName() return $class === $r->class ? static::$defaultName : null; } + /** + * @return string|null The default command description or null when no default description is set + */ + public static function getDefaultDescription(): ?string + { + $class = static::class; + $r = new \ReflectionProperty($class, 'defaultDescription'); + + return $class === $r->class ? static::$defaultDescription : null; + } + /** * @param string|null $name The name of the command; passing null means it must be set in configure() * @@ -78,6 +94,10 @@ public function __construct(string $name = null) $this->setName($name); } + if ('' === $this->description) { + $this->setDescription(static::getDefaultDescription() ?? ''); + } + $this->configure(); } @@ -298,6 +318,8 @@ public function setCode(callable $code) * This method is not part of public API and should not be used directly. * * @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments + * + * @internal */ public function mergeApplicationDefinition(bool $mergeArgs = true) { @@ -554,11 +576,14 @@ public function getProcessedHelp() */ public function setAliases(iterable $aliases) { + $list = []; + foreach ($aliases as $alias) { $this->validateName($alias); + $list[] = $alias; } - $this->aliases = $aliases; + $this->aliases = \is_array($aliases) ? $aliases : $list; return $this; } diff --git a/src/Symfony/Component/Console/Command/LazyCommand.php b/src/Symfony/Component/Console/Command/LazyCommand.php new file mode 100644 index 0000000000000..763133e81e12c --- /dev/null +++ b/src/Symfony/Component/Console/Command/LazyCommand.php @@ -0,0 +1,211 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Nicolas Grekas + */ +final class LazyCommand extends Command +{ + private $command; + private $isEnabled; + + public function __construct(string $name, array $aliases, string $description, bool $isHidden, \Closure $commandFactory, ?bool $isEnabled = true) + { + $this->setName($name) + ->setAliases($aliases) + ->setHidden($isHidden) + ->setDescription($description); + + $this->command = $commandFactory; + $this->isEnabled = $isEnabled; + } + + public function ignoreValidationErrors(): void + { + $this->getCommand()->ignoreValidationErrors(); + } + + public function setApplication(Application $application = null): void + { + if ($this->command instanceof parent) { + $this->command->setApplication($application); + } + + parent::setApplication($application); + } + + public function setHelperSet(HelperSet $helperSet): void + { + if ($this->command instanceof parent) { + $this->command->setHelperSet($helperSet); + } + + parent::setHelperSet($helperSet); + } + + public function isEnabled(): bool + { + return $this->isEnabled ?? $this->getCommand()->isEnabled(); + } + + public function run(InputInterface $input, OutputInterface $output): int + { + return $this->getCommand()->run($input, $output); + } + + /** + * @return $this + */ + public function setCode(callable $code): self + { + $this->getCommand()->setCode($code); + + return $this; + } + + /** + * @internal + */ + public function mergeApplicationDefinition(bool $mergeArgs = true): void + { + $this->getCommand()->mergeApplicationDefinition($mergeArgs); + } + + /** + * @return $this + */ + public function setDefinition($definition): self + { + $this->getCommand()->setDefinition($definition); + + return $this; + } + + public function getDefinition(): InputDefinition + { + return $this->getCommand()->getDefinition(); + } + + public function getNativeDefinition(): InputDefinition + { + return $this->getCommand()->getNativeDefinition(); + } + + /** + * @return $this + */ + public function addArgument(string $name, int $mode = null, string $description = '', $default = null): self + { + $this->getCommand()->addArgument($name, $mode, $description, $default); + + return $this; + } + + /** + * @return $this + */ + public function addOption(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null): self + { + $this->getCommand()->addOption($name, $shortcut, $mode, $description, $default); + + return $this; + } + + /** + * @return $this + */ + public function setProcessTitle(string $title): self + { + $this->getCommand()->setProcessTitle($title); + + return $this; + } + + /** + * @return $this + */ + public function setHelp(string $help): self + { + $this->getCommand()->setHelp($help); + + return $this; + } + + public function getHelp(): string + { + return $this->getCommand()->getHelp(); + } + + public function getProcessedHelp(): string + { + return $this->getCommand()->getProcessedHelp(); + } + + public function getSynopsis(bool $short = false): string + { + return $this->getCommand()->getSynopsis($short); + } + + /** + * @return $this + */ + public function addUsage(string $usage): self + { + $this->getCommand()->addUsage($usage); + + return $this; + } + + public function getUsages(): array + { + return $this->getCommand()->getUsages(); + } + + /** + * @return mixed + */ + public function getHelper(string $name) + { + return $this->getCommand()->getHelper($name); + } + + public function getCommand(): parent + { + if (!$this->command instanceof \Closure) { + return $this->command; + } + + $command = $this->command = ($this->command)(); + $command->setApplication($this->getApplication()); + + if (null !== $this->getHelperSet()) { + $command->setHelperSet($this->getHelperSet()); + } + + $command->setName($this->getName()) + ->setAliases($this->getAliases()) + ->setHidden($this->isHidden()) + ->setDescription($this->getDescription()); + + // Will throw if the command is not correctly initialized. + $command->getDefinition(); + + return $command; + } +} diff --git a/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php b/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php index 77ae6f9d47869..42ec2eabad472 100644 --- a/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php +++ b/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php @@ -12,11 +12,14 @@ namespace Symfony\Component\Console\DependencyInjection; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Command\LazyCommand; use Symfony\Component\Console\CommandLoader\ContainerCommandLoader; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\TypedReference; /** @@ -52,7 +55,7 @@ public function process(ContainerBuilder $container) $class = $container->getParameterBag()->resolveValue($definition->getClass()); if (isset($tags[0]['command'])) { - $commandName = $tags[0]['command']; + $aliases = $tags[0]['command']; } else { if (!$r = $container->getReflectionClass($class)) { throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); @@ -60,7 +63,14 @@ public function process(ContainerBuilder $container) if (!$r->isSubclassOf(Command::class)) { throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class)); } - $commandName = $class::getDefaultName(); + $aliases = $class::getDefaultName(); + } + + $aliases = explode('|', $aliases ?? ''); + $commandName = array_shift($aliases); + + if ($isHidden = '' === $commandName) { + $commandName = array_shift($aliases); } if (null === $commandName) { @@ -74,16 +84,19 @@ public function process(ContainerBuilder $container) continue; } + $description = $tags[0]['description'] ?? null; + unset($tags[0]); $lazyCommandMap[$commandName] = $id; $lazyCommandRefs[$id] = new TypedReference($id, $class); - $aliases = []; foreach ($tags as $tag) { if (isset($tag['command'])) { $aliases[] = $tag['command']; $lazyCommandMap[$tag['command']] = $id; } + + $description = $description ?? $tag['description'] ?? null; } $definition->addMethodCall('setName', [$commandName]); @@ -91,6 +104,29 @@ public function process(ContainerBuilder $container) if ($aliases) { $definition->addMethodCall('setAliases', [$aliases]); } + + if ($isHidden) { + $definition->addMethodCall('setHidden', [true]); + } + + if (!$description) { + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + if (!$r->isSubclassOf(Command::class)) { + throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class)); + } + $description = $class::getDefaultDescription(); + } + + if ($description) { + $definition->addMethodCall('setDescription', [$description]); + + $container->register('.'.$id.'.lazy', LazyCommand::class) + ->setArguments([$commandName, $aliases, $description, $isHidden, new ServiceClosureArgument($lazyCommandRefs[$id])]); + + $lazyCommandRefs[$id] = new Reference('.'.$id.'.lazy'); + } } $container diff --git a/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php b/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php index 5e59f8fab3746..c0ecacd451e1d 100644 --- a/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php +++ b/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Command\LazyCommand; use Symfony\Component\Console\CommandLoader\ContainerCommandLoader; use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; @@ -20,6 +21,7 @@ use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\TypedReference; class AddConsoleCommandPassTest extends TestCase @@ -118,6 +120,39 @@ public function visibilityProvider() ]; } + public function testProcessFallsBackToDefaultDescription() + { + $container = new ContainerBuilder(); + $container + ->register('with-defaults', DescribedCommand::class) + ->addTag('console.command') + ; + + $pass = new AddConsoleCommandPass(); + $pass->process($container); + + $commandLoader = $container->getDefinition('console.command_loader'); + $commandLocator = $container->getDefinition((string) $commandLoader->getArgument(0)); + + $this->assertSame(ContainerCommandLoader::class, $commandLoader->getClass()); + $this->assertSame(['cmdname' => 'with-defaults'], $commandLoader->getArgument(1)); + $this->assertEquals([['with-defaults' => new ServiceClosureArgument(new Reference('.with-defaults.lazy'))]], $commandLocator->getArguments()); + $this->assertSame([], $container->getParameter('console.command.ids')); + + $initCounter = DescribedCommand::$initCounter; + $command = $container->get('console.command_loader')->get('cmdname'); + + $this->assertInstanceOf(LazyCommand::class, $command); + $this->assertSame(['cmdalias'], $command->getAliases()); + $this->assertSame('Just testing', $command->getDescription()); + $this->assertTrue($command->isHidden()); + $this->assertTrue($command->isEnabled()); + $this->assertSame($initCounter, DescribedCommand::$initCounter); + + $this->assertSame('', $command->getHelp()); + $this->assertSame(1 + $initCounter, DescribedCommand::$initCounter); + } + public function testProcessThrowAnExceptionIfTheServiceIsAbstract() { $this->expectException(\InvalidArgumentException::class); @@ -250,3 +285,18 @@ class NamedCommand extends Command { protected static $defaultName = 'default'; } + +class DescribedCommand extends Command +{ + public static $initCounter = 0; + + protected static $defaultName = '|cmdname|cmdalias'; + protected static $defaultDescription = 'Just testing'; + + public function __construct() + { + ++self::$initCounter; + + parent::__construct(); + } +} diff --git a/src/Symfony/Component/Form/Command/DebugCommand.php b/src/Symfony/Component/Form/Command/DebugCommand.php index 4150feaf8ce85..9eac585dc8548 100644 --- a/src/Symfony/Component/Form/Command/DebugCommand.php +++ b/src/Symfony/Component/Form/Command/DebugCommand.php @@ -32,6 +32,7 @@ class DebugCommand extends Command { protected static $defaultName = 'debug:form'; + protected static $defaultDescription = 'Displays form type information'; private $formRegistry; private $namespaces; @@ -64,7 +65,7 @@ protected function configure() new InputOption('show-deprecated', null, InputOption::VALUE_NONE, 'Display deprecated options in form types'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt or json)', 'txt'), ]) - ->setDescription('Displays form type information') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command displays information about form types. diff --git a/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php b/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php index 03320b6f66e15..b289a29c0ef40 100644 --- a/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php +++ b/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php @@ -36,6 +36,7 @@ class ConsumeMessagesCommand extends Command { protected static $defaultName = 'messenger:consume'; + protected static $defaultDescription = 'Consumes messages'; private $routableBus; private $receiverLocator; @@ -71,7 +72,7 @@ protected function configure(): void new InputOption('sleep', null, InputOption::VALUE_REQUIRED, 'Seconds to sleep before asking for new messages after no messages were found', 1), new InputOption('bus', 'b', InputOption::VALUE_REQUIRED, 'Name of the bus to which received messages should be dispatched (if not passed, bus is determined automatically)'), ]) - ->setDescription('Consumes messages') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command consumes messages and dispatches them to the message bus. diff --git a/src/Symfony/Component/Messenger/Command/DebugCommand.php b/src/Symfony/Component/Messenger/Command/DebugCommand.php index 31b19d0bff948..047ceaf6ee18d 100644 --- a/src/Symfony/Component/Messenger/Command/DebugCommand.php +++ b/src/Symfony/Component/Messenger/Command/DebugCommand.php @@ -26,6 +26,7 @@ class DebugCommand extends Command { protected static $defaultName = 'debug:messenger'; + protected static $defaultDescription = 'Lists messages you can dispatch using the message buses'; private $mapping; @@ -43,7 +44,7 @@ protected function configure() { $this ->addArgument('bus', InputArgument::OPTIONAL, sprintf('The bus id (one of "%s")', implode('", "', array_keys($this->mapping)))) - ->setDescription('Lists messages you can dispatch using the message buses') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command displays all messages that can be dispatched using the message buses: diff --git a/src/Symfony/Component/Messenger/Command/FailedMessagesRemoveCommand.php b/src/Symfony/Component/Messenger/Command/FailedMessagesRemoveCommand.php index 951b7d499ed1b..df21b9bcd4f5a 100644 --- a/src/Symfony/Component/Messenger/Command/FailedMessagesRemoveCommand.php +++ b/src/Symfony/Component/Messenger/Command/FailedMessagesRemoveCommand.php @@ -27,6 +27,7 @@ class FailedMessagesRemoveCommand extends AbstractFailedMessagesCommand { protected static $defaultName = 'messenger:failed:remove'; + protected static $defaultDescription = 'Remove given messages from the failure transport'; /** * {@inheritdoc} @@ -39,7 +40,7 @@ protected function configure(): void new InputOption('force', null, InputOption::VALUE_NONE, 'Force the operation without confirmation'), new InputOption('show-messages', null, InputOption::VALUE_NONE, 'Display messages before removing it (if multiple ids are given)'), ]) - ->setDescription('Remove given messages from the failure transport') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% removes given messages that are pending in the failure transport. diff --git a/src/Symfony/Component/Messenger/Command/FailedMessagesRetryCommand.php b/src/Symfony/Component/Messenger/Command/FailedMessagesRetryCommand.php index 87426edd9dbaa..f56eeb3345b4d 100644 --- a/src/Symfony/Component/Messenger/Command/FailedMessagesRetryCommand.php +++ b/src/Symfony/Component/Messenger/Command/FailedMessagesRetryCommand.php @@ -35,6 +35,7 @@ class FailedMessagesRetryCommand extends AbstractFailedMessagesCommand { protected static $defaultName = 'messenger:failed:retry'; + protected static $defaultDescription = 'Retries one or more messages from the failure transport'; private $eventDispatcher; private $messageBus; @@ -59,7 +60,7 @@ protected function configure(): void new InputArgument('id', InputArgument::IS_ARRAY, 'Specific message id(s) to retry'), new InputOption('force', null, InputOption::VALUE_NONE, 'Force action without confirmation'), ]) - ->setDescription('Retries one or more messages from the failure transport') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% retries message in the failure transport. diff --git a/src/Symfony/Component/Messenger/Command/FailedMessagesShowCommand.php b/src/Symfony/Component/Messenger/Command/FailedMessagesShowCommand.php index bf8a72f906367..4a97159cf7539 100644 --- a/src/Symfony/Component/Messenger/Command/FailedMessagesShowCommand.php +++ b/src/Symfony/Component/Messenger/Command/FailedMessagesShowCommand.php @@ -28,6 +28,7 @@ class FailedMessagesShowCommand extends AbstractFailedMessagesCommand { protected static $defaultName = 'messenger:failed:show'; + protected static $defaultDescription = 'Shows one or more messages from the failure transport'; /** * {@inheritdoc} @@ -39,7 +40,7 @@ protected function configure(): void new InputArgument('id', InputArgument::OPTIONAL, 'Specific message id to show'), new InputOption('max', null, InputOption::VALUE_REQUIRED, 'Maximum number of messages to list', 50), ]) - ->setDescription('Shows one or more messages from the failure transport') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% shows message that are pending in the failure transport. diff --git a/src/Symfony/Component/Messenger/Command/SetupTransportsCommand.php b/src/Symfony/Component/Messenger/Command/SetupTransportsCommand.php index 84395892fdf9b..71dd7ab5fd1a8 100644 --- a/src/Symfony/Component/Messenger/Command/SetupTransportsCommand.php +++ b/src/Symfony/Component/Messenger/Command/SetupTransportsCommand.php @@ -25,6 +25,7 @@ class SetupTransportsCommand extends Command { protected static $defaultName = 'messenger:setup-transports'; + protected static $defaultDescription = 'Prepares the required infrastructure for the transport'; private $transportLocator; private $transportNames; @@ -41,7 +42,7 @@ protected function configure() { $this ->addArgument('transport', InputArgument::OPTIONAL, 'Name of the transport to setup', null) - ->setDescription('Prepares the required infrastructure for the transport') + ->setDescription(self::$defaultDescription) ->setHelp(<<%command.name% command setups the transports: diff --git a/src/Symfony/Component/Messenger/Command/StopWorkersCommand.php b/src/Symfony/Component/Messenger/Command/StopWorkersCommand.php index 7df9b9d2014f0..c861513402b6a 100644 --- a/src/Symfony/Component/Messenger/Command/StopWorkersCommand.php +++ b/src/Symfony/Component/Messenger/Command/StopWorkersCommand.php @@ -25,6 +25,7 @@ class StopWorkersCommand extends Command { protected static $defaultName = 'messenger:stop-workers'; + protected static $defaultDescription = 'Stops workers after their current message'; private $restartSignalCachePool; @@ -42,7 +43,7 @@ protected function configure(): void { $this ->setDefinition([]) - ->setDescription('Stops workers after their current message') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command sends a signal to stop any messenger:consume processes that are running. diff --git a/src/Symfony/Component/Translation/Command/XliffLintCommand.php b/src/Symfony/Component/Translation/Command/XliffLintCommand.php index dc2e9a9d936dd..f246b7ea33491 100644 --- a/src/Symfony/Component/Translation/Command/XliffLintCommand.php +++ b/src/Symfony/Component/Translation/Command/XliffLintCommand.php @@ -31,6 +31,7 @@ class XliffLintCommand extends Command { protected static $defaultName = 'lint:xliff'; + protected static $defaultDescription = 'Lints a XLIFF file and outputs encountered errors'; private $format; private $displayCorrectFiles; @@ -53,7 +54,7 @@ public function __construct(string $name = null, callable $directoryIteratorProv protected function configure() { $this - ->setDescription('Lints a XLIFF file and outputs encountered errors') + ->setDescription(self::$defaultDescription) ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt') ->setHelp(<<addArgument('class', InputArgument::REQUIRED, 'A fully qualified class name or a path') ->addOption('show-all', null, InputOption::VALUE_NONE, 'Show all classes even if they have no validation constraints') - ->setDescription('Displays validation constraints for classes') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% 'App\Entity\Dummy' command dumps the validators for the dummy class. diff --git a/src/Symfony/Component/VarDumper/Command/ServerDumpCommand.php b/src/Symfony/Component/VarDumper/Command/ServerDumpCommand.php index c8a61da98c5b6..8b50bc3c7f16e 100644 --- a/src/Symfony/Component/VarDumper/Command/ServerDumpCommand.php +++ b/src/Symfony/Component/VarDumper/Command/ServerDumpCommand.php @@ -35,6 +35,7 @@ class ServerDumpCommand extends Command { protected static $defaultName = 'server:dump'; + protected static $defaultDescription = 'Starts a dump server that collects and displays dumps in a single place'; private $server; @@ -58,7 +59,7 @@ protected function configure() $this ->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format (%s)', $availableFormats), 'cli') - ->setDescription('Starts a dump server that collects and displays dumps in a single place') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' %command.name% starts a dump server that collects and displays dumps in a single place for debugging you application: diff --git a/src/Symfony/Component/Yaml/Command/LintCommand.php b/src/Symfony/Component/Yaml/Command/LintCommand.php index 94a84b754d213..5375bf460d3c1 100644 --- a/src/Symfony/Component/Yaml/Command/LintCommand.php +++ b/src/Symfony/Component/Yaml/Command/LintCommand.php @@ -33,6 +33,7 @@ class LintCommand extends Command { protected static $defaultName = 'lint:yaml'; + protected static $defaultDescription = 'Lints a file and outputs encountered errors'; private $parser; private $format; @@ -54,7 +55,7 @@ public function __construct(string $name = null, callable $directoryIteratorProv protected function configure() { $this - ->setDescription('Lints a file and outputs encountered errors') + ->setDescription(self::$defaultDescription) ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format') ->addOption('parse-tags', null, InputOption::VALUE_NONE, 'Parse custom tags')