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..699ba444747 100644 --- a/components/console/events.rst +++ b/components/console/events.rst @@ -209,36 +209,32 @@ 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 by subscribing to the :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)) { // ... } // ... - // return an integer to set the exit code, or + // set an integer 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..63045f178ad 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: 'app: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..b3d773a27fd 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 + public function __invoke(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..84ca4c1a931 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, 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. + 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..59a4d540c4e 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, 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/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..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,23 +33,22 @@ 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\Input\InputInterface; - use Symfony\Component\Console\Output\OutputInterface; - 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(): 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 ---------------------------