diff --git a/config/help/MakeCommand.txt b/config/help/MakeCommand.txt index a88b9d430..f555cd4f0 100644 --- a/config/help/MakeCommand.txt +++ b/config/help/MakeCommand.txt @@ -3,3 +3,7 @@ The %command.name% command generates a new command: php %command.full_name% app:do-something If the argument is missing, the command will ask for the command name interactively. + +Symfony can also generate the command with the old structure (configure and execute method) instead of an invokable command. + +php %command.full_name% --invokable \ No newline at end of file diff --git a/src/Maker/MakeCommand.php b/src/Maker/MakeCommand.php index 52d331c53..34cafb177 100644 --- a/src/Maker/MakeCommand.php +++ b/src/Maker/MakeCommand.php @@ -18,14 +18,19 @@ use Symfony\Bundle\MakerBundle\Str; use Symfony\Bundle\MakerBundle\Util\PhpCompatUtil; use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; +use Symfony\Component\Console\Attribute\Argument; use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Attribute\Option; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\LazyCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\HttpKernel\Kernel; + /** * @author Javier Eguiluz @@ -58,10 +63,22 @@ public function configureCommand(Command $command, InputConfiguration $inputConf { $command ->addArgument('name', InputArgument::OPTIONAL, \sprintf('Choose a command name (e.g. app:%s)', Str::asCommand(Str::getRandomTerm()))) + ->addOption('invokable', null, InputOption::VALUE_NEGATABLE, 'Generate an invokable command (using PHP attributes) starting from Symfony 7.3?') ->setHelp($this->getHelpFileContents('MakeCommand.txt')) ; } + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void + { + if ($input->getOption('invokable') === null) { + $description = $command->getDefinition()->getOption('invokable')->getDescription(); + $question = new ConfirmationQuestion($description, Kernel::VERSION_ID >= 70300); + $invokable = $io->askQuestion($question); + + $input->setOption('invokable', $invokable); + } + } + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void { $commandName = trim($input->getArgument('name')); @@ -74,25 +91,44 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen \sprintf('The "%s" command name is not valid because it would be implemented by "%s" class, which is not valid as a PHP class name (it must start with a letter or underscore, followed by any number of letters, numbers, or underscores).', $commandName, Str::asClassName($commandName, 'Command')) ); - $useStatements = new UseStatementGenerator([ - Command::class, - InputArgument::class, - InputInterface::class, - InputOption::class, - OutputInterface::class, - SymfonyStyle::class, - AsCommand::class, - ]); + if ($input->getOption('invokable')) { + $useStatements = new UseStatementGenerator([ + Command::class, + SymfonyStyle::class, + AsCommand::class, + Argument::class, + Option::class, + ]); - $generator->generateClass( - $commandClassNameDetails->getFullName(), - 'command/Command.tpl.php', - [ - 'use_statements' => $useStatements, - 'command_name' => $commandName, - 'set_description' => !class_exists(LazyCommand::class), - ] - ); + $generator->generateClass( + $commandClassNameDetails->getFullName(), + 'command/InvokableCommand.tpl.php', + [ + 'use_statements' => $useStatements, + 'command_name' => $commandName, + ] + ); + } else { + $useStatements = new UseStatementGenerator([ + Command::class, + InputArgument::class, + InputInterface::class, + InputOption::class, + OutputInterface::class, + SymfonyStyle::class, + AsCommand::class, + ]); + + $generator->generateClass( + $commandClassNameDetails->getFullName(), + 'command/Command.tpl.php', + [ + 'use_statements' => $useStatements, + 'command_name' => $commandName, + 'set_description' => !class_exists(LazyCommand::class), + ] + ); + } $generator->writeChanges(); diff --git a/templates/command/InvokableCommand.tpl.php b/templates/command/InvokableCommand.tpl.php new file mode 100644 index 000000000..3a775bfe2 --- /dev/null +++ b/templates/command/InvokableCommand.tpl.php @@ -0,0 +1,25 @@ + + +namespace ; + + + +#[AsCommand( + name: '', + description: 'Add a short description for your command', +)] +class +{ + public function __invoke( + SymfonyStyle $io, + #[Argument('Argument description')] string $arg, + #[Option('Option description')] bool $enable = false", + ): int { + $io->note(sprintf('The value of $arg is: %s', $arg)); + $io->note(sprintf('The value of $enable is: %s', $enable ? 'true' : 'false')); + + $io->success('You have a new command! Now make it your own! Pass --help to see your options.'); + + return Command::SUCCESS; + } +} diff --git a/tests/Maker/MakeCommandTest.php b/tests/Maker/MakeCommandTest.php index 1ff279c19..65e5cde35 100644 --- a/tests/Maker/MakeCommandTest.php +++ b/tests/Maker/MakeCommandTest.php @@ -25,11 +25,26 @@ protected function getMakerClass(): string public function getTestDetails(): \Generator { - yield 'it_makes_a_command_no_attributes' => [$this->createMakerTest() + yield 'it_makes_an_invokable_command_no_attributes' => [$this->createMakerTest() ->run(function (MakerTestRunner $runner) { $runner->runMaker([ // command name 'app:foo', + // create invokable command by default + '', + ]); + + $this->runCommandTest($runner, 'it_makes_a_command.php'); + }), + ]; + + yield 'it_makes_an_old_structured_command_no_attributes' => [$this->createMakerTest() + ->run(function (MakerTestRunner $runner) { + $runner->runMaker([ + // command name + 'app:foo', + // create a command with the old structure by default + 'yes', ]); $this->runCommandTest($runner, 'it_makes_a_command.php'); @@ -41,6 +56,8 @@ public function getTestDetails(): \Generator $runner->runMaker([ // command name 'app:foo', + // create invokable command by default + '', ]); $this->runCommandTest($runner, 'it_makes_a_command.php'); @@ -63,6 +80,8 @@ public function getTestDetails(): \Generator $runner->runMaker([ // command name 'app:foo', + // create invokable command by default + '', ]); $this->runCommandTest($runner, 'it_makes_a_command_in_custom_namespace.php');