diff --git a/src/Command/AddUserCommand.php b/src/Command/AddUserCommand.php index a060f81bf..c9f4d53e6 100644 --- a/src/Command/AddUserCommand.php +++ b/src/Command/AddUserCommand.php @@ -15,12 +15,12 @@ use App\Repository\UserRepository; use App\Utils\Validator; use Doctrine\ORM\EntityManagerInterface; +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\Exception\RuntimeException; -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\Style\SymfonyStyle; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; @@ -49,7 +49,8 @@ */ #[AsCommand( name: 'app:add-user', - description: 'Creates users and stores them in the database' + description: 'Creates users and stores them in the database', + help: self::HELP, )] final class AddUserCommand extends Command { @@ -64,23 +65,9 @@ public function __construct( parent::__construct(); } - protected function configure(): void - { - $this - ->setHelp($this->getCommandHelp()) - // commands can optionally define arguments and/or options (mandatory and optional) - // see https://symfony.com/doc/current/components/console/console_arguments.html - ->addArgument('username', InputArgument::OPTIONAL, 'The username of the new user') - ->addArgument('password', InputArgument::OPTIONAL, 'The plain password of the new user') - ->addArgument('email', InputArgument::OPTIONAL, 'The email of the new user') - ->addArgument('full-name', InputArgument::OPTIONAL, 'The full name of the new user') - ->addOption('admin', null, InputOption::VALUE_NONE, 'If set, the user is created as an administrator') - ; - } - /** - * This optional method is the first one executed for a command after configure() - * and is useful to initialize properties based on the input arguments and options. + * This optional method is the first one executed for a command and is useful + * to initialize properties based on the input arguments and options. */ protected function initialize(InputInterface $input, OutputInterface $output): void { @@ -91,9 +78,9 @@ protected function initialize(InputInterface $input, OutputInterface $output): v } /** - * This method is executed after initialize() and before execute(). Its purpose - * is to check if some of the options/arguments are missing and interactively - * ask the user for those values. + * This method is executed after initialize() and before __invoke(). Its purpose + * is to check if some options/arguments are missing and interactively ask the user + * for those values. * * This method is completely optional. If you are developing an internal console * command, you probably should not implement this method because it requires @@ -161,26 +148,21 @@ protected function interact(InputInterface $input, OutputInterface $output): voi /** * This method is executed after interact() and initialize(). It usually * contains the logic to execute to complete this command task. + * + * Commands can optionally define arguments and/or options (mandatory and optional) + * + * @see https://symfony.com/doc/current/console/input.html */ - protected function execute(InputInterface $input, OutputInterface $output): int - { + public function __invoke( + #[Argument('The username of the new user')] string $username, + #[Argument('The plain password of the new user', 'password')] string $plainPassword, + #[Argument('The email of the new user')] string $email, + #[Argument('The full name of the new user')] string $fullName, + #[Option('If set, the user is created as an administrator', 'admin')] bool $isAdmin = false, + ): int { $stopwatch = new Stopwatch(); $stopwatch->start('add-user-command'); - /** @var string $username */ - $username = $input->getArgument('username'); - - /** @var string $plainPassword */ - $plainPassword = $input->getArgument('password'); - - /** @var string $email */ - $email = $input->getArgument('email'); - - /** @var string $fullName */ - $fullName = $input->getArgument('full-name'); - - $isAdmin = $input->getOption('admin'); - // make sure to validate the user data is correct $this->validateUserData($username, $plainPassword, $email, $fullName); @@ -202,7 +184,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $event = $stopwatch->stop('add-user-command'); - if ($output->isVerbose()) { + if ($this->io->isVerbose()) { $this->io->comment(\sprintf('New user database id: %d / Elapsed time: %.2f ms / Consumed memory: %.2f MB', $user->getId(), $event->getDuration(), $event->getMemory() / (1024 ** 2))); } @@ -232,33 +214,30 @@ private function validateUserData(string $username, string $plainPassword, strin } /** - * The command help is usually included in the configure() method, but when - * it's too long, it's better to define a separate method to maintain the + * The command help is usually included in the #[AsCommand] attribute, but when + * it's too long, it's better to define a separate constant to maintain the * code readability. */ - private function getCommandHelp(): string - { - return <<<'HELP' - The %command.name% command creates new users and saves them in the database: + public const HELP = <<<'HELP' + The %command.name% command creates new users and saves them in the database: - php %command.full_name% username password email + php %command.full_name% username password email - By default the command creates regular users. To create administrator users, - add the --admin option: + By default the command creates regular users. To create administrator users, + add the --admin option: - php %command.full_name% username password email --admin + php %command.full_name% username password email --admin - If you omit any of the three required arguments, the command will ask you to - provide the missing values: + If you omit any of the three required arguments, the command will ask you to + provide the missing values: - # command will ask you for the email - php %command.full_name% username password + # command will ask you for the email + php %command.full_name% username password - # command will ask you for the email and password - php %command.full_name% username + # command will ask you for the email and password + php %command.full_name% username - # command will ask you for all arguments - php %command.full_name% - HELP; - } + # command will ask you for all arguments + php %command.full_name% + HELP; } diff --git a/src/Command/DeleteUserCommand.php b/src/Command/DeleteUserCommand.php index 2c91523d5..39d9d7c72 100644 --- a/src/Command/DeleteUserCommand.php +++ b/src/Command/DeleteUserCommand.php @@ -16,10 +16,10 @@ use App\Utils\Validator; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; +use Symfony\Component\Console\Attribute\Argument; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\RuntimeException; -use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; @@ -41,7 +41,17 @@ */ #[AsCommand( name: 'app:delete-user', - description: 'Deletes users from the database' + description: 'Deletes users from the database', + help: <<<'HELP' + The %command.name% command deletes users from the database: + + php %command.full_name% username + + If you omit the argument, the command will ask you to + provide the missing value: + + php %command.full_name% + HELP, )] final class DeleteUserCommand extends Command { @@ -56,23 +66,6 @@ public function __construct( parent::__construct(); } - protected function configure(): void - { - $this - ->addArgument('username', InputArgument::REQUIRED, 'The username of an existing user') - ->setHelp(<<<'HELP' - The %command.name% command deletes users from the database: - - php %command.full_name% username - - If you omit the argument, the command will ask you to - provide the missing value: - - php %command.full_name% - HELP - ); - } - protected function initialize(InputInterface $input, OutputInterface $output): void { // SymfonyStyle is an optional feature that Symfony provides so you can @@ -105,10 +98,8 @@ protected function interact(InputInterface $input, OutputInterface $output): voi $input->setArgument('username', $username); } - protected function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(#[Argument('The username of an existing user')] string $username): int { - /** @var string|null $username */ - $username = $input->getArgument('username'); $username = $this->validator->validateUsername($username); /** @var User|null $user */ diff --git a/src/Command/ListUsersCommand.php b/src/Command/ListUsersCommand.php index 41e8c259f..cfecb44fa 100644 --- a/src/Command/ListUsersCommand.php +++ b/src/Command/ListUsersCommand.php @@ -14,9 +14,9 @@ use App\Entity\User; use App\Repository\UserRepository; use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Attribute\Option; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; @@ -42,9 +42,24 @@ #[AsCommand( name: 'app:list-users', description: 'Lists all the existing users', - aliases: ['app:users'] + aliases: ['app:users'], + help: <<<'HELP' + The %command.name% command lists all the users registered in the application: + + php %command.full_name% + + By default the command only displays the 50 most recent users. Set the number of + results to display with the --max-results option: + + php %command.full_name% --max-results=2000 + + In addition to displaying the user list, you can also send this information to + the email address specified in the --send-to option: + + php %command.full_name% --send-to=fabien@symfony.com + HELP, )] -final class ListUsersCommand extends Command +final class ListUsersCommand { public function __construct( private readonly MailerInterface $mailer, @@ -52,44 +67,22 @@ public function __construct( private readonly string $emailSender, private readonly UserRepository $users, ) { - parent::__construct(); - } - - protected function configure(): void - { - $this - ->setHelp(<<<'HELP' - The %command.name% command lists all the users registered in the application: - - php %command.full_name% - - By default the command only displays the 50 most recent users. Set the number of - results to display with the --max-results option: - - php %command.full_name% --max-results=2000 - - In addition to displaying the user list, you can also send this information to - the email address specified in the --send-to option: - - php %command.full_name% --send-to=fabien@symfony.com - HELP - ) - // commands can optionally define arguments and/or options (mandatory and optional) - // see https://symfony.com/doc/current/components/console/console_arguments.html - ->addOption('max-results', null, InputOption::VALUE_OPTIONAL, 'Limits the number of users listed', 50) - ->addOption('send-to', null, InputOption::VALUE_OPTIONAL, 'If set, the result is sent to the given email address') - ; } /** * This method is executed after initialize(). It usually contains the logic * to execute to complete this command task. + * + * Commands can optionally define arguments and/or options (mandatory and optional) + * + * @see https://symfony.com/doc/current/console/input.html */ - protected function execute(InputInterface $input, OutputInterface $output): int - { - /** @var int|null $maxResults */ - $maxResults = $input->getOption('max-results'); - + public function __invoke( + InputInterface $input, + OutputInterface $output, + #[Option('If set, the result is sent to the given email address', 'send-to')] ?string $email = null, + #[Option('Limits the number of users listed')] int $maxResults = 50, + ): int { // Use ->findBy() instead of ->findAll() to allow result sorting and limiting $allUsers = $this->users->findBy([], ['id' => 'DESC'], $maxResults); @@ -122,9 +115,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $usersAsATable = $bufferedOutput->fetch(); $output->write($usersAsATable); - /** @var string|null $email */ - $email = $input->getOption('send-to'); - if (null !== $email) { $this->sendReport($usersAsATable, $email); } diff --git a/src/Controller/.gitignore b/src/Controller/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/Entity/.gitignore b/src/Entity/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/Repository/.gitignore b/src/Repository/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/Command/AbstractCommandTestCase.php b/tests/Command/AbstractCommandTestCase.php index b2f279336..43aa28388 100644 --- a/tests/Command/AbstractCommandTestCase.php +++ b/tests/Command/AbstractCommandTestCase.php @@ -13,7 +13,6 @@ use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; -use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Tester\CommandTester; abstract class AbstractCommandTestCase extends KernelTestCase @@ -29,11 +28,10 @@ abstract class AbstractCommandTestCase extends KernelTestCase protected function executeCommand(array $arguments, array $inputs = []): CommandTester { $kernel = self::bootKernel(); + $application = new Application($kernel); - // this uses a special testing container that allows you to fetch private services - /** @var Command $command */ - $command = static::getContainer()->get($this->getCommandFqcn()); - $command->setApplication(new Application($kernel)); + $command = $application->find($this->getCommandName()); + $command->setApplication($application); $commandTester = new CommandTester($command); $commandTester->setInputs($inputs); @@ -42,5 +40,5 @@ protected function executeCommand(array $arguments, array $inputs = []): Command return $commandTester; } - abstract protected function getCommandFqcn(): string; + abstract protected function getCommandName(): string; } diff --git a/tests/Command/AddUserCommandTest.php b/tests/Command/AddUserCommandTest.php index d8f20e2a9..979bffbf4 100644 --- a/tests/Command/AddUserCommandTest.php +++ b/tests/Command/AddUserCommandTest.php @@ -11,7 +11,6 @@ namespace App\Tests\Command; -use App\Command\AddUserCommand; use App\Repository\UserRepository; use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; @@ -103,8 +102,8 @@ private function assertUserCreated(bool $isAdmin): void $this->assertSame($isAdmin ? ['ROLE_ADMIN'] : ['ROLE_USER'], $user->getRoles()); } - protected function getCommandFqcn(): string + protected function getCommandName(): string { - return AddUserCommand::class; + return 'app:add-user'; } } diff --git a/tests/Command/ListUsersCommandTest.php b/tests/Command/ListUsersCommandTest.php index 4726901b8..e41e4bdfc 100644 --- a/tests/Command/ListUsersCommandTest.php +++ b/tests/Command/ListUsersCommandTest.php @@ -11,7 +11,6 @@ namespace App\Tests\Command; -use App\Command\ListUsersCommand; use PHPUnit\Framework\Attributes\DataProvider; final class ListUsersCommandTest extends AbstractCommandTestCase @@ -50,8 +49,8 @@ public function testItSendsAnEmailIfOptionProvided(): void $this->assertEmailCount(1); } - protected function getCommandFqcn(): string + protected function getCommandName(): string { - return ListUsersCommand::class; + return 'app:list-users'; } }