From a59a80c06237be6d17ad96eded72a4763f138ae5 Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Thu, 1 May 2025 11:52:20 -0400 Subject: [PATCH 1/5] Document invokable command --- .../console/changing_default_command.rst | 14 ++--- components/console/events.rst | 26 ++++----- components/console/helpers/cursor.rst | 8 +-- components/console/helpers/questionhelper.rst | 8 +-- components/console/helpers/table.rst | 12 +++-- components/console/helpers/tree.rst | 11 ++-- components/console/logger.rst | 5 +- components/console/single_command_tool.rst | 11 ++-- components/process.rst | 9 +++- console.rst | 32 ++++++----- console/calling_commands.rst | 9 ++-- console/commands_as_services.rst | 19 ++----- console/hide_commands.rst | 3 +- console/lockable_trait.rst | 15 +++--- console/style.rst | 53 ++++++++----------- console/verbosity.rst | 13 ++--- logging/monolog_console.rst | 10 ++-- routing.rst | 18 +++---- scheduler.rst | 6 ++- 19 files changed, 124 insertions(+), 158 deletions(-) diff --git a/components/console/changing_default_command.rst b/components/console/changing_default_command.rst index c69995ea395..2195bbd2697 100644 --- a/components/console/changing_default_command.rst +++ b/components/console/changing_default_command.rst @@ -9,20 +9,14 @@ name to the ``setDefaultCommand()`` method:: use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; - use Symfony\Component\Console\Input\InputInterface; - use Symfony\Component\Console\Output\OutputInterface; + use Symfony\Component\Console\Style\SymfonyStyle; - #[AsCommand(name: 'hello:world')] + #[AsCommand(name: 'hello:world', description: 'Outputs "Hello World"')] class HelloWorldCommand extends Command { - protected function configure(): void + public function __invoke(SymfonyStyle $io): int { - $this->setDescription('Outputs "Hello World"'); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - $output->writeln('Hello World'); + $io->writeln('Hello World'); return Command::SUCCESS; } diff --git a/components/console/events.rst b/components/console/events.rst index e550025b7dd..4fbda573462 100644 --- a/components/console/events.rst +++ b/components/console/events.rst @@ -209,28 +209,24 @@ method:: for these constants to be available. If you use the Console component inside a Symfony application, commands can -handle signals themselves. To do so, implement the -:class:`Symfony\\Component\\Console\\Command\\SignalableCommandInterface` and subscribe to one or more signals:: +handle signals themselves. To do so, subscribe to :class:`Symfony\\Component\\Console\\Event\\ConsoleSignalEvent` event:: - // src/Command/SomeCommand.php + // src/Command/MyCommand.php namespace App\Command; - use Symfony\Component\Console\Command\Command; - use Symfony\Component\Console\Command\SignalableCommandInterface; + use Symfony\Component\Console\Attribute\AsCommand; + use Symfony\Component\EventDispatcher\Attribute\AsEventListener; - class SomeCommand extends Command implements SignalableCommandInterface + #[AsCommand(name: 'app:my-command')] + class MyCommand { // ... - public function getSubscribedSignals(): array + #[AsEventListener(ConsoleSignalEvent::class)] + public function handleSignal(ConsoleSignalEvent $event): void { - // return here any of the constants defined by PCNTL extension - return [\SIGINT, \SIGTERM]; - } - - public function handleSignal(int $signal): int|false - { - if (\SIGINT === $signal) { + // set here any of the constants defined by PCNTL extension + if (in_array($event->getHandlingSignal(), [\SIGINT, \SIGTERM], true)) { // ... } @@ -238,7 +234,7 @@ handle signals themselves. To do so, implement the // return an integer to set the exit code, or // false to continue normal execution - return 0; + $event->setExitCode(0); } } diff --git a/components/console/helpers/cursor.rst b/components/console/helpers/cursor.rst index c5cab6c6d0b..ceca608577d 100644 --- a/components/console/helpers/cursor.rst +++ b/components/console/helpers/cursor.rst @@ -13,16 +13,16 @@ of the output: // src/Command/MyCommand.php namespace App\Command; - use Symfony\Component\Console\Command\Command; + use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Cursor; - use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; - class MyCommand extends Command + #[AsCommand(name: 'my-command')] + class MyCommand { // ... - public function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(OutputInterface $output): int { // ... diff --git a/components/console/helpers/questionhelper.rst b/components/console/helpers/questionhelper.rst index c7e064b16ca..fc450d07b55 100644 --- a/components/console/helpers/questionhelper.rst +++ b/components/console/helpers/questionhelper.rst @@ -27,16 +27,16 @@ Suppose you want to confirm an action before actually executing it. Add the following to your command:: // ... + use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ConfirmationQuestion; - class YourCommand extends Command + #[AsCommand(name: 'app:my-command')] + class MyCommand { - // ... - - public function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(InputInterface $input, OutputInterface $output): int { $helper = $this->getHelper('question'); $question = new ConfirmationQuestion('Continue with this action?', false); diff --git a/components/console/helpers/table.rst b/components/console/helpers/table.rst index 9d6fdb0ee61..d05e5c981e6 100644 --- a/components/console/helpers/table.rst +++ b/components/console/helpers/table.rst @@ -22,15 +22,16 @@ When building a console application it may be useful to display tabular data: To display a table, use :class:`Symfony\\Component\\Console\\Helper\\Table`, set the headers, set the rows and then render the table:: + use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\Table; - use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; // ... - class SomeCommand extends Command + #[AsCommand(name: 'app:my-command')] + class MyCommand { - public function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(OutputInterface $output): int { $table = new Table($output); $table @@ -445,9 +446,10 @@ The only requirement to append rows is that the table must be rendered inside a use Symfony\Component\Console\Helper\Table; // ... - class SomeCommand extends Command + #[AsCommand(name: 'app:my-command')] + class MyCommand { - public function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(OutputInterface $output): int { $section = $output->section(); $table = new Table($section); diff --git a/components/console/helpers/tree.rst b/components/console/helpers/tree.rst index 1161d00e942..7e9149d7d33 100644 --- a/components/console/helpers/tree.rst +++ b/components/console/helpers/tree.rst @@ -26,22 +26,17 @@ inside your console command:: namespace App\Command; use Symfony\Component\Console\Attribute\AsCommand; - use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\TreeHelper; use Symfony\Component\Console\Helper\TreeNode; - use Symfony\Component\Console\Input\InputInterface; - use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; - #[AsCommand(name: 'app:some-command', description: '...')] - class SomeCommand extends Command + #[AsCommand(name: 'app:my-command', description: '...')] + class MyCommand { // ... - protected function execute(InputInterface $input, OutputInterface $output): int + protected function execute(SymfonyStyle $io): int { - $io = new SymfonyStyle($input, $output); - $node = TreeNode::fromValues([ 'config/', 'public/', diff --git a/components/console/logger.rst b/components/console/logger.rst index c3d5c447a89..cc182821a0a 100644 --- a/components/console/logger.rst +++ b/components/console/logger.rst @@ -34,7 +34,6 @@ You can rely on the logger to use this dependency inside a command:: use Acme\MyDependency; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; - use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Logger\ConsoleLogger; use Symfony\Component\Console\Output\OutputInterface; @@ -42,9 +41,9 @@ You can rely on the logger to use this dependency inside a command:: name: 'my:command', description: 'Use an external dependency requiring a PSR-3 logger' )] - class MyCommand extends Command + class MyCommand { - protected function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(OutputInterface $output): int { $logger = new ConsoleLogger($output); diff --git a/components/console/single_command_tool.rst b/components/console/single_command_tool.rst index 97cb09bf030..9c6b06537e2 100644 --- a/components/console/single_command_tool.rst +++ b/components/console/single_command_tool.rst @@ -9,19 +9,18 @@ it is possible to remove this need by declaring a single command application:: setName('My Super Command') // Optional ->setVersion('1.0.0') // Optional - ->addArgument('foo', InputArgument::OPTIONAL, 'The directory') - ->addOption('bar', null, InputOption::VALUE_REQUIRED) - ->setCode(function (InputInterface $input, OutputInterface $output): int { + ->setCode(function (OutputInterface $output, #[Argument] string $foo = 'The directory', #[Option] string $bar = ''): int { // output arguments and options + + return 0; }) ->run(); diff --git a/components/process.rst b/components/process.rst index 7552537e82e..9c25c931510 100644 --- a/components/process.rst +++ b/components/process.rst @@ -430,11 +430,14 @@ However, if you run the command via the Symfony ``Process`` class, PHP will use the settings defined in the ``php.ini`` file. You can solve this issue by using the :class:`Symfony\\Component\\Process\\PhpSubprocess` class to run the command:: + use Symfony\Component\Console\Attribute\AsCommand; + use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Process\Process; - class MyCommand extends Command + #[AsCommand(name: 'app:my-command')] + class MyCommand { - protected function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(SymfonyStyle $io): int { // the memory_limit (and any other config option) of this command is // the one defined in php.ini instead of the new values (optionally) @@ -444,6 +447,8 @@ the :class:`Symfony\\Component\\Process\\PhpSubprocess` class to run the command // the memory_limit (and any other config option) of this command takes // into account the values (optionally) passed via the '-d' command option $childProcess = new PhpSubprocess(['bin/console', 'cache:pool:prune']); + + return 0; } } diff --git a/console.rst b/console.rst index 24fab9885da..46c03e39b95 100644 --- a/console.rst +++ b/console.rst @@ -110,23 +110,19 @@ completion (by default, by pressing the Tab key). Creating a Command ------------------ -Commands are defined in classes extending -:class:`Symfony\\Component\\Console\\Command\\Command`. For example, you may -want a command to create a user:: +Commands are defined in classes, for example, you may want a command to create a user:: // src/Command/CreateUserCommand.php namespace App\Command; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; - use Symfony\Component\Console\Input\InputInterface; - use Symfony\Component\Console\Output\OutputInterface; // the name of the command is what users type after "php bin/console" #[AsCommand(name: 'app:create-user')] - class CreateUserCommand extends Command + class CreateUserCommand { - protected function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(): int { // ... put here the code to create the user @@ -147,6 +143,10 @@ want a command to create a user:: } } +Additionally, can extend the :class:`Symfony\\Component\\Console\\Command\\Command` class to +leverage advanced features like lifecycle hooks: :method:`Symfony\\Component\\Console\\Command\\Command::initialize`, +:method:`Symfony\\Component\\Console\\Command\\Command::interact`, and built-in helpers. + Configuring the Command ~~~~~~~~~~~~~~~~~~~~~~~ @@ -156,18 +156,16 @@ You can optionally define a description, help message and the // src/Command/CreateUserCommand.php - // ... - class CreateUserCommand extends Command + #[AsCommand( + name: 'app:create-user', + description: 'Creates a new user.', // the command description shown when running "php bin/console list" + help: 'This command allows you to create a user...', // the command help shown when running the command with the "--help" option + )] + class CreateUserCommand { - // ... - protected function configure(): void + public function __invoke(): int { - $this - // the command description shown when running "php bin/console list" - ->setDescription('Creates a new user.') - // the command help shown when running the command with the "--help" option - ->setHelp('This command allows you to create a user...') - ; + // ... } } diff --git a/console/calling_commands.rst b/console/calling_commands.rst index dd1f0b12ff9..b2cfa36c0c8 100644 --- a/console/calling_commands.rst +++ b/console/calling_commands.rst @@ -18,16 +18,15 @@ the returned code from the command (return value from command ``execute()`` method):: // ... + use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command; use Symfony\Component\Console\Input\ArrayInput; - use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; - class CreateUserCommand extends Command + #[AsCommand(name: 'app:create-user')] + class CreateUserCommand { - // ... - - protected function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(OutputInterface $output): int { $greetInput = new ArrayInput([ // the command name is passed as first argument diff --git a/console/commands_as_services.rst b/console/commands_as_services.rst index 1393879a1df..ed5b99f9cb4 100644 --- a/console/commands_as_services.rst +++ b/console/commands_as_services.rst @@ -16,27 +16,16 @@ For example, suppose you want to log something from within your command:: use Psr\Log\LoggerInterface; use Symfony\Component\Console\Attribute\AsCommand; - use Symfony\Component\Console\Command\Command; - use Symfony\Component\Console\Input\InputInterface; - use Symfony\Component\Console\Output\OutputInterface; - #[AsCommand(name: 'app:sunshine')] - class SunshineCommand extends Command + #[AsCommand(name: 'app:sunshine', description: 'Good morning!')] + class SunshineCommand { public function __construct( private LoggerInterface $logger, ) { - // you *must* call the parent constructor - parent::__construct(); - } - - protected function configure(): void - { - $this - ->setDescription('Good morning!'); } - protected function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(): int { $this->logger->info('Waking up the sun'); // ... @@ -70,7 +59,7 @@ To make your command lazily loaded, either define its name using the PHP // ... #[AsCommand(name: 'app:sunshine')] - class SunshineCommand extends Command + class SunshineCommand { // ... } diff --git a/console/hide_commands.rst b/console/hide_commands.rst index 44a69d09289..4ab9d3a6dad 100644 --- a/console/hide_commands.rst +++ b/console/hide_commands.rst @@ -15,10 +15,9 @@ the ``hidden`` property of the ``AsCommand`` attribute:: namespace App\Command; use Symfony\Component\Console\Attribute\AsCommand; - use Symfony\Component\Console\Command\Command; #[AsCommand(name: 'app:legacy', hidden: true)] - class LegacyCommand extends Command + class LegacyCommand { // ... } diff --git a/console/lockable_trait.rst b/console/lockable_trait.rst index 0f4a4900e17..2a4fd64ffaf 100644 --- a/console/lockable_trait.rst +++ b/console/lockable_trait.rst @@ -13,19 +13,17 @@ that adds two convenient methods to lock and release commands:: // ... use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\LockableTrait; - use Symfony\Component\Console\Input\InputInterface; - use Symfony\Component\Console\Output\OutputInterface; + use Symfony\Component\Console\Style\SymfonyStyle; - class UpdateContentsCommand extends Command + #[AsCommand(name: 'contents:update')] + class UpdateContentsCommand { use LockableTrait; - // ... - - protected function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(SymfonyStyle $io): int { if (!$this->lock()) { - $output->writeln('The command is already running in another process.'); + $io->writeln('The command is already running in another process.'); return Command::SUCCESS; } @@ -52,7 +50,8 @@ a ``$lockFactory`` property with your own lock factory:: use Symfony\Component\Console\Command\LockableTrait; use Symfony\Component\Lock\LockFactory; - class UpdateContentsCommand extends Command + #[AsCommand(name: 'contents:update')] + class UpdateContentsCommand { use LockableTrait; diff --git a/console/style.rst b/console/style.rst index e1e5df38ffe..e05ad5d771d 100644 --- a/console/style.rst +++ b/console/style.rst @@ -7,18 +7,18 @@ questions to the user involves a lot of repetitive code. Consider for example the code used to display the title of the following command:: - // src/Command/GreetCommand.php + // src/Command/MyCommand.php namespace App\Command; + use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; - class GreetCommand extends Command + #[AsCommand(name: 'app:my-command')] + class MyCommand { - // ... - - protected function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(InputInterface $input, OutputInterface $output): int { $output->writeln([ 'Lorem Ipsum Dolor Sit Amet', @@ -42,26 +42,22 @@ which allow to create *semantic* commands and forget about their styling. Basic Usage ----------- -In your command, instantiate the :class:`Symfony\\Component\\Console\\Style\\SymfonyStyle` -class and pass the ``$input`` and ``$output`` variables as its arguments. Then, -you can start using any of its helpers, such as ``title()``, which displays the -title of the command:: +In your `__invoke` method, type the :class:`Symfony\\Component\\Console\\Style\\SymfonyStyle` +argument. Then, you can start using any of its helpers, such as ``title()``, which +displays the title of the command:: - // src/Command/GreetCommand.php + // src/Command/MyCommand.php namespace App\Command; + use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; - use Symfony\Component\Console\Input\InputInterface; - use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; - class GreetCommand extends Command + #[AsCommand(name: 'app:my-command')] + class MyCommand { - // ... - - protected function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(SymfonyStyle $io): int { - $io = new SymfonyStyle($input, $output); $io->title('Lorem Ipsum Dolor Sit Amet'); // ... @@ -448,19 +444,17 @@ long they are. This is done to enable clickable URLs in terminals that support t If you prefer to wrap all contents, including URLs, use this method:: - // src/Command/GreetCommand.php + // src/Command/MyCommand.php namespace App\Command; // ... use Symfony\Component\Console\Style\SymfonyStyle; - class GreetCommand extends Command + #[AsCommand(name: 'app:my-command')] + class MyCommand { - // ... - - protected function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(SymfonyStyle $io): int { - $io = new SymfonyStyle($input, $output); $io->getOutputWrapper()->setAllowCutUrls(true); // ... @@ -487,7 +481,7 @@ Then, instantiate this custom class instead of the default ``SymfonyStyle`` in your commands. Thanks to the ``StyleInterface`` you won't need to change the code of your commands to change their appearance:: - // src/Command/GreetCommand.php + // src/Command/MyCommand.php namespace App\Console; use App\Console\CustomStyle; @@ -495,16 +489,11 @@ of your commands to change their appearance:: use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; - class GreetCommand extends Command + #[AsCommand(name: 'app:my-command')] + class MyCommand { - // ... - - protected function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(InputInterface $input, OutputInterface $output): int { - // Before - $io = new SymfonyStyle($input, $output); - - // After $io = new CustomStyle($input, $output); // ... diff --git a/console/verbosity.rst b/console/verbosity.rst index 9910dca0c3d..cbbb1663895 100644 --- a/console/verbosity.rst +++ b/console/verbosity.rst @@ -49,21 +49,22 @@ It is possible to print a message in a command for only a specific verbosity level. For example:: // ... + use Symfony\Component\Console\Attribute\Argument; + use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; - class CreateUserCommand extends Command + #[AsCommand(name: 'app:create-user')] + class CreateUserCommand { - // ... - - public function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(OutputInterface $output, #[Argument] string $username, #[Argument] string $password): int { $user = new User(...); $output->writeln([ - 'Username: '.$input->getArgument('username'), - 'Password: '.$input->getArgument('password'), + 'Username: '.$username, + 'Password: '.$password, ]); // available methods: ->isSilent(), ->isQuiet(), ->isVerbose(), ->isVeryVerbose(), ->isDebug() diff --git a/logging/monolog_console.rst b/logging/monolog_console.rst index 67bf0f5acae..d6b8e2f6529 100644 --- a/logging/monolog_console.rst +++ b/logging/monolog_console.rst @@ -38,19 +38,19 @@ The example above could then be rewritten as:: namespace App\Command; use Psr\Log\LoggerInterface; + use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; - use Symfony\Component\Console\Input\InputInterface; - use Symfony\Component\Console\Output\OutputInterface; + use Symfony\Component\Console\Style\SymfonyStyle; - class YourCommand extends Command + #[AsCommand(name: 'app:my-command')] + class MyCommand { public function __construct( private LoggerInterface $logger, ) { - parent::__construct(); } - protected function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(SymfonyStyle $io): int { $this->logger->debug('Some info'); $this->logger->notice('Some more info'); diff --git a/routing.rst b/routing.rst index 663e8518504..47e8ab32384 100644 --- a/routing.rst +++ b/routing.rst @@ -2484,23 +2484,23 @@ The solution is to configure the ``default_uri`` option to define the Now you'll get the expected results when generating URLs in your commands:: - // src/Command/SomeCommand.php + // src/Command/MyCommand.php namespace App\Command; - use Symfony\Component\Console\Command\Command; - use Symfony\Component\Console\Input\InputInterface; - use Symfony\Component\Console\Output\OutputInterface; + use Symfony\Component\Console\Attribute\AsCommand; + use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; // ... - class SomeCommand extends Command + #[AsCommand(name: 'app:my-command')] + class MyCommand { - public function __construct(private UrlGeneratorInterface $urlGenerator) - { - parent::__construct(); + public function __construct( + private UrlGeneratorInterface $urlGenerator, + ) { } - protected function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(SymfonyStyle $io): int { // generate a URL with no route arguments $signUpPage = $this->urlGenerator->generate('sign_up'); diff --git a/scheduler.rst b/scheduler.rst index 765f60e156a..66eb77445b9 100644 --- a/scheduler.rst +++ b/scheduler.rst @@ -478,7 +478,8 @@ The attribute takes more parameters to customize the trigger:: // when applying this attribute to a Symfony console command, you can pass // arguments and options to the command using the 'arguments' option: #[AsCronTask('0 0 * * *', arguments: 'some_argument --some-option --another-option=some_value')] - class MyCommand extends Command + #[AsCommand(name: 'app:my-command')] + class MyCommand .. _scheduler-attributes-periodic-task: @@ -527,7 +528,8 @@ The ``#[AsPeriodicTask]`` attribute takes many parameters to customize the trigg // when applying this attribute to a Symfony console command, you can pass // arguments and options to the command using the 'arguments' option: #[AsPeriodicTask(frequency: '1 day', arguments: 'some_argument --some-option --another-option=some_value')] - class MyCommand extends Command + #[AsCommand(name: 'app:my-command')] + class MyCommand Managing Scheduled Messages --------------------------- From 4a417f14e3ef4916250fdf357be7b0658f69a13b Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Fri, 2 May 2025 12:59:31 -0400 Subject: [PATCH 2/5] Antoine's Review --- components/console/helpers/tree.rst | 2 +- console/style.rst | 4 ++-- logging/monolog_console.rst | 8 +++----- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/components/console/helpers/tree.rst b/components/console/helpers/tree.rst index 7e9149d7d33..b3d773a27fd 100644 --- a/components/console/helpers/tree.rst +++ b/components/console/helpers/tree.rst @@ -35,7 +35,7 @@ inside your console command:: { // ... - protected function execute(SymfonyStyle $io): int + public function __invoke(SymfonyStyle $io): int { $node = TreeNode::fromValues([ 'config/', diff --git a/console/style.rst b/console/style.rst index e05ad5d771d..59a4d540c4e 100644 --- a/console/style.rst +++ b/console/style.rst @@ -42,8 +42,8 @@ which allow to create *semantic* commands and forget about their styling. Basic Usage ----------- -In your `__invoke` method, type the :class:`Symfony\\Component\\Console\\Style\\SymfonyStyle` -argument. Then, you can start using any of its helpers, such as ``title()``, which +In your `__invoke` method, add an argument of type :class:`Symfony\\Component\\Console\\Style\\SymfonyStyle`. +Then, you can start using any of its helpers, such as ``title()``, which displays the title of the command:: // src/Command/MyCommand.php diff --git a/logging/monolog_console.rst b/logging/monolog_console.rst index d6b8e2f6529..4d007abe854 100644 --- a/logging/monolog_console.rst +++ b/logging/monolog_console.rst @@ -10,10 +10,9 @@ When a lot of logging has to happen, it's cumbersome to print information depending on the verbosity settings (``-v``, ``-vv``, ``-vvv``) because the calls need to be wrapped in conditions. For example:: - use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; - protected function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(OutputInterface $output): int { if ($output->isDebug()) { $output->writeln('Some info'); @@ -34,13 +33,12 @@ the current log level and the console verbosity. The example above could then be rewritten as:: - // src/Command/YourCommand.php + // src/Command/MyCommand.php namespace App\Command; use Psr\Log\LoggerInterface; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; - use Symfony\Component\Console\Style\SymfonyStyle; #[AsCommand(name: 'app:my-command')] class MyCommand @@ -50,7 +48,7 @@ The example above could then be rewritten as:: ) { } - public function __invoke(SymfonyStyle $io): int + public function __invoke(): int { $this->logger->debug('Some info'); $this->logger->notice('Some more info'); From 236dd82a701ee2dbccf1213fff9b0ef987d667a2 Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Mon, 5 May 2025 13:58:40 -0400 Subject: [PATCH 3/5] Oskar's Review --- components/console/events.rst | 4 ++-- console.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/console/events.rst b/components/console/events.rst index 4fbda573462..699ba444747 100644 --- a/components/console/events.rst +++ b/components/console/events.rst @@ -209,7 +209,7 @@ method:: for these constants to be available. If you use the Console component inside a Symfony application, commands can -handle signals themselves. To do so, subscribe to :class:`Symfony\\Component\\Console\\Event\\ConsoleSignalEvent` event:: +handle signals themselves by subscribing to the :class:`Symfony\\Component\\Console\\Event\\ConsoleSignalEvent` event:: // src/Command/MyCommand.php namespace App\Command; @@ -232,7 +232,7 @@ handle signals themselves. To do so, subscribe to :class:`Symfony\\Component\\Co // ... - // return an integer to set the exit code, or + // set an integer exit code, or // false to continue normal execution $event->setExitCode(0); } diff --git a/console.rst b/console.rst index 46c03e39b95..84ca4c1a931 100644 --- a/console.rst +++ b/console.rst @@ -143,7 +143,7 @@ Commands are defined in classes, for example, you may want a command to create a } } -Additionally, can extend the :class:`Symfony\\Component\\Console\\Command\\Command` class to +Additionally, you can extend the :class:`Symfony\\Component\\Console\\Command\\Command` class to leverage advanced features like lifecycle hooks: :method:`Symfony\\Component\\Console\\Command\\Command::initialize`, :method:`Symfony\\Component\\Console\\Command\\Command::interact`, and built-in helpers. From 1a9a5d79f8aaf241916a3aaa0f0519fc2732aea7 Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Mon, 5 May 2025 16:42:13 -0400 Subject: [PATCH 4/5] Antoine's Review --- components/console/helpers/cursor.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/console/helpers/cursor.rst b/components/console/helpers/cursor.rst index ceca608577d..63045f178ad 100644 --- a/components/console/helpers/cursor.rst +++ b/components/console/helpers/cursor.rst @@ -17,7 +17,7 @@ of the output: use Symfony\Component\Console\Cursor; use Symfony\Component\Console\Output\OutputInterface; - #[AsCommand(name: 'my-command')] + #[AsCommand(name: 'app:my-command')] class MyCommand { // ... From 74432014e530b26038a7faaff19b2510e3f23785 Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Sun, 25 May 2025 05:51:47 +0200 Subject: [PATCH 5/5] Wouter's Review (WIP) --- .../console/helpers/debug_formatter.rst | 9 ++-- .../console/helpers/formatterhelper.rst | 12 ++--- components/console/helpers/processhelper.rst | 2 +- components/console/helpers/questionhelper.rst | 54 +++++++++---------- console.rst | 54 ++++++++----------- console/calling_commands.rst | 3 +- console/style.rst | 2 +- 7 files changed, 59 insertions(+), 77 deletions(-) diff --git a/components/console/helpers/debug_formatter.rst b/components/console/helpers/debug_formatter.rst index 10d3c67a79a..8fa59c319c9 100644 --- a/components/console/helpers/debug_formatter.rst +++ b/components/console/helpers/debug_formatter.rst @@ -10,15 +10,14 @@ this: .. image:: /_images/components/console/debug_formatter.png :alt: Console output, with the first line showing "RUN Running figlet", followed by lines showing the output of the command prefixed with "OUT" and "RES Finished the command" as last line in the output. -Using the debug_formatter +Using the Debug Formatter ------------------------- -The formatter is included in the default helper set and you can get it by -calling :method:`Symfony\\Component\\Console\\Command\\Command::getHelper`:: +The debug formatter helper can be instantiated directly as shown:: - $debugFormatter = $this->getHelper('debug_formatter'); + $debugFormatter = new DebugFormatterHelper(); -The formatter accepts strings and returns a formatted string, which you then +It accepts strings and returns a formatted string, which you then output to the console (or even log the information or do anything else). All methods of this helper have an identifier as the first argument. This is a diff --git a/components/console/helpers/formatterhelper.rst b/components/console/helpers/formatterhelper.rst index d2b19915a3a..4f83e66eecc 100644 --- a/components/console/helpers/formatterhelper.rst +++ b/components/console/helpers/formatterhelper.rst @@ -1,15 +1,11 @@ Formatter Helper ================ -The Formatter helper provides functions to format the output with colors. -You can do more advanced things with this helper than you can in -:doc:`/console/coloring`. +The :class:`Symfony\\Component\\Console\\Helper\\FormatterHelper` helper provides +functions to format the output with colors. You can do more advanced things with +this helper than you can in :doc:`/console/coloring`:: -The :class:`Symfony\\Component\\Console\\Helper\\FormatterHelper` is included -in the default helper set and you can get it by calling -:method:`Symfony\\Component\\Console\\Command\\Command::getHelper`:: - - $formatter = $this->getHelper('formatter'); + $formatter = new FormatterHelper(); The methods return a string, which you'll usually render to the console by passing it to the diff --git a/components/console/helpers/processhelper.rst b/components/console/helpers/processhelper.rst index b46d9f2e95f..df9a8efe45b 100644 --- a/components/console/helpers/processhelper.rst +++ b/components/console/helpers/processhelper.rst @@ -11,7 +11,7 @@ a very verbose verbosity (e.g. ``-vv``):: use Symfony\Component\Process\Process; - $helper = $this->getHelper('process'); + $helper = new ProcessHelper(); $process = new Process(['figlet', 'Symfony']); $helper->run($output, $process); diff --git a/components/console/helpers/questionhelper.rst b/components/console/helpers/questionhelper.rst index fc450d07b55..6d22a2de2af 100644 --- a/components/console/helpers/questionhelper.rst +++ b/components/console/helpers/questionhelper.rst @@ -2,11 +2,9 @@ Question Helper =============== The :class:`Symfony\\Component\\Console\\Helper\\QuestionHelper` provides -functions to ask the user for more information. It is included in the default -helper set and you can get it by calling -:method:`Symfony\\Component\\Console\\Command\\Command::getHelper`:: +functions to ask the user for more information:: - $helper = $this->getHelper('question'); + $helper = new QuestionHelper(); The Question Helper has a single method :method:`Symfony\\Component\\Console\\Helper\\QuestionHelper::ask` that needs an @@ -38,7 +36,7 @@ the following to your command:: { public function __invoke(InputInterface $input, OutputInterface $output): int { - $helper = $this->getHelper('question'); + $helper = new QuestionHelper(); $question = new ConfirmationQuestion('Continue with this action?', false); if (!$helper->ask($input, $output, $question)) { @@ -91,7 +89,7 @@ if you want to know a bundle name, you can add this to your command:: use Symfony\Component\Console\Question\Question; // ... - public function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(InputInterface $input, OutputInterface $output): int { // ... $question = new Question('Please enter the name of the bundle', 'AcmeDemoBundle'); @@ -121,10 +119,10 @@ but ``red`` could be set instead (could be more explicit):: use Symfony\Component\Console\Question\ChoiceQuestion; // ... - public function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(InputInterface $input, OutputInterface $output): int { // ... - $helper = $this->getHelper('question'); + $helper = new QuestionHelper(); $question = new ChoiceQuestion( 'Please select your favorite color (defaults to red)', // choices can also be PHP objects that implement __toString() method @@ -184,10 +182,10 @@ this use :method:`Symfony\\Component\\Console\\Question\\ChoiceQuestion::setMult use Symfony\Component\Console\Question\ChoiceQuestion; // ... - public function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(InputInterface $input, OutputInterface $output): int { // ... - $helper = $this->getHelper('question'); + $helper = new QuestionHelper(); $question = new ChoiceQuestion( 'Please select your favorite colors (defaults to red and blue)', ['red', 'blue', 'yellow'], @@ -218,10 +216,10 @@ will be autocompleted as the user types:: use Symfony\Component\Console\Question\Question; // ... - public function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(InputInterface $input, OutputInterface $output): int { // ... - $helper = $this->getHelper('question'); + $helper = new QuestionHelper(); $bundles = ['AcmeDemoBundle', 'AcmeBlogBundle', 'AcmeStoreBundle']; $question = new Question('Please enter the name of a bundle', 'FooBundle'); @@ -241,9 +239,9 @@ provide a callback function to dynamically generate suggestions:: use Symfony\Component\Console\Question\Question; // ... - public function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(InputInterface $input, OutputInterface $output): int { - $helper = $this->getHelper('question'); + $helper = new QuestionHelper(); // This function is called whenever the input changes and new // suggestions are needed. @@ -282,10 +280,10 @@ You can also specify if you want to not trim the answer by setting it directly w use Symfony\Component\Console\Question\Question; // ... - public function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(InputInterface $input, OutputInterface $output): int { // ... - $helper = $this->getHelper('question'); + $helper = new QuestionHelper(); $question = new Question('What is the name of the child?'); $question->setTrimmable(false); @@ -308,10 +306,10 @@ the response to a question should allow multiline answers by passing ``true`` to use Symfony\Component\Console\Question\Question; // ... - public function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(InputInterface $input, OutputInterface $output): int { // ... - $helper = $this->getHelper('question'); + $helper = new QuestionHelper(); $question = new Question('How do you solve world peace?'); $question->setMultiline(true); @@ -335,10 +333,10 @@ convenient for passwords:: use Symfony\Component\Console\Question\Question; // ... - public function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(InputInterface $input, OutputInterface $output): int { // ... - $helper = $this->getHelper('question'); + $helper = new QuestionHelper(); $question = new Question('What is the database password?'); $question->setHidden(true); @@ -372,10 +370,10 @@ convenient for passwords:: use Symfony\Component\Console\Question\ChoiceQuestion; // ... - public function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(InputInterface $input, OutputInterface $output): int { // ... - $helper = $this->getHelper('question'); + $helper = new QuestionHelper(); QuestionHelper::disableStty(); // ... @@ -396,10 +394,10 @@ method:: use Symfony\Component\Console\Question\Question; // ... - public function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(InputInterface $input, OutputInterface $output): int { // ... - $helper = $this->getHelper('question'); + $helper = new QuestionHelper(); $question = new Question('Please enter the name of the bundle', 'AcmeDemoBundle'); $question->setNormalizer(function (string $value): string { @@ -434,10 +432,10 @@ method:: use Symfony\Component\Console\Question\Question; // ... - public function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(InputInterface $input, OutputInterface $output): int { // ... - $helper = $this->getHelper('question'); + $helper = new QuestionHelper(); $question = new Question('Please enter the name of the bundle', 'AcmeDemoBundle'); $question->setValidator(function (string $answer): string { @@ -494,10 +492,10 @@ You can also use a validator with a hidden question:: use Symfony\Component\Console\Question\Question; // ... - public function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(InputInterface $input, OutputInterface $output): int { // ... - $helper = $this->getHelper('question'); + $helper = new QuestionHelper(); $question = new Question('Please enter your password'); $question->setNormalizer(function (?string $value): string { diff --git a/console.rst b/console.rst index 84ca4c1a931..52f77f2afbb 100644 --- a/console.rst +++ b/console.rst @@ -145,7 +145,7 @@ Commands are defined in classes, for example, you may want a command to create a Additionally, you can extend the :class:`Symfony\\Component\\Console\\Command\\Command` class to leverage advanced features like lifecycle hooks: :method:`Symfony\\Component\\Console\\Command\\Command::initialize`, -:method:`Symfony\\Component\\Console\\Command\\Command::interact`, and built-in helpers. +and :method:`Symfony\\Component\\Console\\Command\\Command::interact`. Configuring the Command ~~~~~~~~~~~~~~~~~~~~~~~ @@ -225,7 +225,6 @@ You can register the command by adding the ``AsCommand`` attribute to it:: namespace App\Command; use Symfony\Component\Console\Attribute\AsCommand; - use Symfony\Component\Console\Command\Command; #[AsCommand( name: 'app:create-user', @@ -233,7 +232,7 @@ You can register the command by adding the ``AsCommand`` attribute to it:: hidden: false, aliases: ['app:add-user'] )] - class CreateUserCommand extends Command + class CreateUserCommand { // ... } @@ -253,16 +252,16 @@ After configuring and registering the command, you can run it in the terminal: $ php bin/console app:create-user As you might expect, this command will do nothing as you didn't write any logic -yet. Add your own logic inside the ``execute()`` method. +yet. Add your own logic inside the ``__invoke()`` method. Console Output -------------- -The ``execute()`` method has access to the output stream to write messages to +The ``__invoke()`` method has access to the output stream to write messages to the console:: // ... - protected function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(OutputInterface $output): int { // outputs multiple lines to the console (adding "\n" at the end of each line) $output->writeln([ @@ -313,9 +312,10 @@ method, which returns an instance of // ... use Symfony\Component\Console\Output\ConsoleOutputInterface; - class MyCommand extends Command + #[AsCommand(name: 'app:my-command')] + class MyCommand { - protected function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(OutputInterface $output): int { if (!$output instanceof ConsoleOutputInterface) { throw new \LogicException('This command accepts only an instance of "ConsoleOutputInterface".'); @@ -374,20 +374,12 @@ Console Input Use input options or arguments to pass information to the command:: - use Symfony\Component\Console\Input\InputArgument; - - // ... - protected function configure(): void - { - $this - // configure an argument - ->addArgument('username', InputArgument::REQUIRED, 'The username of the user.') - // ... - ; - } + use Symfony\Component\Console\Attribute\Argument; - // ... - public function execute(InputInterface $input, OutputInterface $output): int + // The #[Argument] attribute configures $username as a + // required input argument and its value is automatically + // passed to this parameter + public function __invoke(#[Argument('The username of the user.')] string $username, OutputInterface $output): int { $output->writeln([ 'User Creator', @@ -395,8 +387,7 @@ Use input options or arguments to pass information to the command:: '', ]); - // retrieve the argument value using getArgument() - $output->writeln('Username: '.$input->getArgument('username')); + $output->writeln('Username: '.$username); return Command::SUCCESS; } @@ -426,23 +417,22 @@ as a service, you can use normal dependency injection. Imagine you have a // ... use App\Service\UserManager; - use Symfony\Component\Console\Command\Command; + use Symfony\Component\Console\Attribute\Argument; + use Symfony\Component\Console\Attribute\AsCommand; - class CreateUserCommand extends Command + #[AsCommand(name: 'app:create-user')] + class CreateUserCommand { public function __construct( - private UserManager $userManager, - ){ - parent::__construct(); + private UserManager $userManager + ) { } - // ... - - protected function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(#[Argument] string $username, OutputInterface $output): int { // ... - $this->userManager->create($input->getArgument('username')); + $this->userManager->create($username); $output->writeln('User successfully generated!'); diff --git a/console/calling_commands.rst b/console/calling_commands.rst index b2cfa36c0c8..875ead15d2d 100644 --- a/console/calling_commands.rst +++ b/console/calling_commands.rst @@ -14,12 +14,11 @@ arguments and options you want to pass to the command. The command name must be the first argument. Eventually, calling the ``doRun()`` method actually runs the command and returns -the returned code from the command (return value from command ``execute()`` +the returned code from the command (return value from command ``__invoke()`` method):: // ... use Symfony\Component\Console\Attribute\AsCommand; - use Symfony\Component\Console\Command; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\OutputInterface; diff --git a/console/style.rst b/console/style.rst index 59a4d540c4e..a9f71a9d7a8 100644 --- a/console/style.rst +++ b/console/style.rst @@ -42,7 +42,7 @@ which allow to create *semantic* commands and forget about their styling. Basic Usage ----------- -In your `__invoke` method, add an argument of type :class:`Symfony\\Component\\Console\\Style\\SymfonyStyle`. +In your ``__invoke()`` method, add an argument of type :class:`Symfony\\Component\\Console\\Style\\SymfonyStyle`. Then, you can start using any of its helpers, such as ``title()``, which displays the title of the command::