Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 13 additions & 36 deletions src/Command/AddUserCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Attribute\Interact;
use Symfony\Component\Console\Attribute\Option;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Stopwatch\Stopwatch;
Expand Down Expand Up @@ -55,8 +54,6 @@
)]
final class AddUserCommand extends Command
{
private SymfonyStyle $io;

public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly UserPasswordHasherInterface $passwordHasher,
Expand All @@ -66,45 +63,24 @@
parent::__construct();
}

/**
* 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
{
// SymfonyStyle is an optional feature that Symfony provides so you can
// apply a consistent look to the commands of your application.
// See https://symfony.com/doc/current/console/style.html
$this->io = new SymfonyStyle($input, $output);
}

/**
* 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
* quite a lot of work. However, if the command is meant to be used by external
* users, this method is a nice way to fall back and prevent errors.
*/
protected function interact(InputInterface $input, OutputInterface $output): void
#[Interact]
public function prompt(SymfonyStyle $io): void
{
/** @var string|null $username */
$username = $input->getArgument('username');
$username = $io->getArgument('username');

Check failure on line 70 in src/Command/AddUserCommand.php

View workflow job for this annotation

GitHub Actions / PHPStan

Call to an undefined method Symfony\Component\Console\Style\SymfonyStyle::getArgument().
/** @var string|null $password */
$password = $input->getArgument('password');

Check failure on line 72 in src/Command/AddUserCommand.php

View workflow job for this annotation

GitHub Actions / PHPStan

Undefined variable: $input
/** @var string|null $email */
$email = $input->getArgument('email');

Check failure on line 74 in src/Command/AddUserCommand.php

View workflow job for this annotation

GitHub Actions / PHPStan

Undefined variable: $input
/** @var string|null $fullName */
$fullName = $input->getArgument('full-name');

Check failure on line 76 in src/Command/AddUserCommand.php

View workflow job for this annotation

GitHub Actions / PHPStan

Undefined variable: $input

if (null !== $username && null !== $password && null !== $email && null !== $fullName) {
return;
}

$this->io->title('Add User Command Interactive Wizard');
$this->io->text([
$io->title('Add User Command Interactive Wizard');
$io->text([
'If you prefer to not use this interactive wizard, provide the',
'arguments required by this command as follows:',
'',
Expand All @@ -115,34 +91,34 @@

// Ask for the username if it's not defined
if (null !== $username) {
$this->io->text(' > <info>Username</info>: '.$username);
$io->text(' > <info>Username</info>: '.$username);
} else {
$username = $this->io->ask('Username', null, $this->validator->validateUsername(...));

Check failure on line 96 in src/Command/AddUserCommand.php

View workflow job for this annotation

GitHub Actions / PHPStan

Access to an undefined property App\Command\AddUserCommand::$io.
$input->setArgument('username', $username);

Check failure on line 97 in src/Command/AddUserCommand.php

View workflow job for this annotation

GitHub Actions / PHPStan

Undefined variable: $input
}

// Ask for the password if it's not defined
if (null !== $password) {
$this->io->text(' > <info>Password</info>: '.u('*')->repeat(u($password)->length()));
$io->text(' > <info>Password</info>: '.u('*')->repeat(u($password)->length()));
} else {
$password = $this->io->askHidden('Password (your type will be hidden)', $this->validator->validatePassword(...));

Check failure on line 104 in src/Command/AddUserCommand.php

View workflow job for this annotation

GitHub Actions / PHPStan

Access to an undefined property App\Command\AddUserCommand::$io.
$input->setArgument('password', $password);

Check failure on line 105 in src/Command/AddUserCommand.php

View workflow job for this annotation

GitHub Actions / PHPStan

Undefined variable: $input
}

// Ask for the email if it's not defined
if (null !== $email) {
$this->io->text(' > <info>Email</info>: '.$email);
$io->text(' > <info>Email</info>: '.$email);
} else {
$email = $this->io->ask('Email', null, $this->validator->validateEmail(...));
$email = $io->ask('Email', null, $this->validator->validateEmail(...));
$input->setArgument('email', $email);

Check failure on line 113 in src/Command/AddUserCommand.php

View workflow job for this annotation

GitHub Actions / PHPStan

Undefined variable: $input
}

// Ask for the full name if it's not defined
if (null !== $fullName) {
$this->io->text(' > <info>Full Name</info>: '.$fullName);
$io->text(' > <info>Full Name</info>: '.$fullName);
} else {
$fullName = $this->io->ask('Full Name', null, $this->validator->validateFullName(...));
$fullName = $io->ask('Full Name', null, $this->validator->validateFullName(...));
$input->setArgument('full-name', $fullName);

Check failure on line 121 in src/Command/AddUserCommand.php

View workflow job for this annotation

GitHub Actions / PHPStan

Undefined variable: $input
}
}

Expand All @@ -155,6 +131,7 @@
* @see https://symfony.com/doc/current/console/input.html
*/
public function __invoke(
SymfonyStyle $io,
#[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,
Expand Down
47 changes: 8 additions & 39 deletions src/Command/DeleteUserCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Attribute\Ask;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
Expand Down Expand Up @@ -55,8 +54,6 @@
)]
final class DeleteUserCommand extends Command
{
private SymfonyStyle $io;

public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly Validator $validator,
Expand All @@ -66,40 +63,12 @@ public function __construct(
parent::__construct();
}

protected function initialize(InputInterface $input, OutputInterface $output): void
{
// SymfonyStyle is an optional feature that Symfony provides so you can
// apply a consistent look to the commands of your application.
// See https://symfony.com/doc/current/console/style.html
$this->io = new SymfonyStyle($input, $output);
}

protected function interact(InputInterface $input, OutputInterface $output): void
{
/** @var string|null $username */
$username = $input->getArgument('username');

if (null !== $username) {
return;
}

$this->io->title('Delete User Command Interactive Wizard');
$this->io->text([
'If you prefer to not use this interactive wizard, provide the',
'arguments required by this command as follows:',
'',
' $ php bin/console app:delete-user username',
'',
'Now we\'ll ask you for the value of all the missing command arguments.',
'',
]);

$username = $this->io->ask('Username', null, $this->validator->validateUsername(...));
$input->setArgument('username', $username);
}

public function __invoke(#[Argument('The username of an existing user')] string $username): int
{
public function __invoke(
SymfonyStyle $io,
#[Argument('The username of an existing user')]
#[Ask('Username', maxAttempts: 3)]
string $username,
): int {
$username = $this->validator->validateUsername($username);

/** @var User|null $user */
Expand All @@ -120,7 +89,7 @@ public function __invoke(#[Argument('The username of an existing user')] string
$userUsername = $user->getUsername();
$userEmail = $user->getEmail();

$this->io->success(\sprintf('User "%s" (ID: %d, email: %s) was successfully deleted.', $userUsername, $userId, $userEmail));
$io->success(\sprintf('User "%s" (ID: %d, email: %s) was successfully deleted.', $userUsername, $userId, $userEmail));

// Logging is helpful and important to keep a trace of what happened in the software runtime flow.
// See https://symfony.com/doc/current/logging.html
Expand Down
11 changes: 10 additions & 1 deletion src/Security/PostVoter.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,21 @@ protected function voteOnAttribute(string $attribute, $post, TokenInterface $tok

// the user must be logged in; if not, deny permission
if (!$user instanceof User) {
// votes can include explanations about the decisions. These can be:
// * internal: not shown to the end user, but useful for logging or debugging (you can include technical details)
// * public: (as in this case) meant to be shown to the end user (make sure to not include sensitive information)
$vote?->addReason(\sprintf('There is no user logged in, so it\'s not possible to %s the blog post.', $attribute));

return false;
}

// the logic of this voter is pretty simple: if the logged-in user is the
// author of the given blog post, grant permission; otherwise, deny it.
// (the supports() method guarantees that $post is a Post object)
return $user === $post->getAuthor();
if ($user === $post->getAuthor()) {
return true;
}

$vote?->addReason(\sprintf('You can\'t %s this blog post because you are not its author.', $attribute));
}
}
Loading