Passed
Push — fix_coverage_in_scrutinizer ( cd0379...a04ba4 )
by Herberto
13:22
created

AddUserCommand::getCommandHelp()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 26
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 26
ccs 2
cts 2
cp 1
rs 8.8571
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 0
crap 1
1
<?php
2
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace App\Command;
13
14
use App\Entity\User;
15
use App\Repository\UserRepository;
16
use App\Utils\Validator;
17
use Doctrine\ORM\EntityManagerInterface;
18
use Symfony\Component\Console\Command\Command;
19
use Symfony\Component\Console\Exception\RuntimeException;
20
use Symfony\Component\Console\Input\InputArgument;
21
use Symfony\Component\Console\Input\InputInterface;
22
use Symfony\Component\Console\Input\InputOption;
23
use Symfony\Component\Console\Output\OutputInterface;
24
use Symfony\Component\Console\Style\SymfonyStyle;
25
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
26
use Symfony\Component\Stopwatch\Stopwatch;
27
28
/**
29
 * A console command that creates users and stores them in the database.
30
 *
31
 * To use this command, open a terminal window, enter into your project
32
 * directory and execute the following:
33
 *
34
 *     $ php bin/console app:add-user
35
 *
36
 * To output detailed information, increase the command verbosity:
37
 *
38
 *     $ php bin/console app:add-user -vv
39
 *
40
 * See https://symfony.com/doc/current/cookbook/console/console_command.html
41
 * For more advanced uses, commands can be defined as services too. See
42
 * https://symfony.com/doc/current/console/commands_as_services.html
43
 *
44
 * @author Javier Eguiluz <[email protected]>
45
 * @author Yonel Ceruto <[email protected]>
46
 */
47
class AddUserCommand extends Command
48
{
49
    // to make your command lazily loaded, configure the $defaultName static property,
50
    // so it will be instantiated only when the command is actually called.
51
    protected static $defaultName = 'app:add-user';
52
53
    /**
54
     * @var SymfonyStyle
55
     */
56
    private $io;
57
58
    private $entityManager;
59
    private $passwordEncoder;
60
    private $validator;
61
    private $users;
62
63 4 View Code Duplication
    public function __construct(EntityManagerInterface $em, UserPasswordEncoderInterface $encoder, Validator $validator, UserRepository $users)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
64
    {
65 4
        parent::__construct();
66
67 4
        $this->entityManager = $em;
68 4
        $this->passwordEncoder = $encoder;
69 4
        $this->validator = $validator;
70 4
        $this->users = $users;
71 4
    }
72
73
    /**
74
     * {@inheritdoc}
75
     */
76 4
    protected function configure()
77
    {
78
        $this
79 4
            ->setDescription('Creates users and stores them in the database')
80 4
            ->setHelp($this->getCommandHelp())
81
            // commands can optionally define arguments and/or options (mandatory and optional)
82
            // see https://symfony.com/doc/current/components/console/console_arguments.html
83 4
            ->addArgument('username', InputArgument::OPTIONAL, 'The username of the new user')
84 4
            ->addArgument('password', InputArgument::OPTIONAL, 'The plain password of the new user')
85 4
            ->addArgument('email', InputArgument::OPTIONAL, 'The email of the new user')
86 4
            ->addArgument('full-name', InputArgument::OPTIONAL, 'The full name of the new user')
87 4
            ->addOption('admin', null, InputOption::VALUE_NONE, 'If set, the user is created as an administrator')
88
        ;
89 4
    }
90
91
    /**
92
     * This optional method is the first one executed for a command after configure()
93
     * and is useful to initialize properties based on the input arguments and options.
94
     */
95 4
    protected function initialize(InputInterface $input, OutputInterface $output)
96
    {
97
        // SymfonyStyle is an optional feature that Symfony provides so you can
98
        // apply a consistent look to the commands of your application.
99
        // See https://symfony.com/doc/current/console/style.html
100 4
        $this->io = new SymfonyStyle($input, $output);
101 4
    }
102
103
    /**
104
     * This method is executed after initialize() and before execute(). Its purpose
105
     * is to check if some of the options/arguments are missing and interactively
106
     * ask the user for those values.
107
     *
108
     * This method is completely optional. If you are developing an internal console
109
     * command, you probably should not implement this method because it requires
110
     * quite a lot of work. However, if the command is meant to be used by external
111
     * users, this method is a nice way to fall back and prevent errors.
112
     */
113 4
    protected function interact(InputInterface $input, OutputInterface $output)
114
    {
115 4
        if (null !== $input->getArgument('username') && null !== $input->getArgument('password') && null !== $input->getArgument('email') && null !== $input->getArgument('full-name')) {
116 2
            return;
117
        }
118
119 2
        $this->io->title('Add User Command Interactive Wizard');
120 2
        $this->io->text([
121 2
            'If you prefer to not use this interactive wizard, provide the',
122
            'arguments required by this command as follows:',
123
            '',
124
            ' $ php bin/console app:add-user username password [email protected]',
125
            '',
126
            'Now we\'ll ask you for the value of all the missing command arguments.',
127
        ]);
128
129
        // Ask for the username if it's not defined
130 2
        $username = $input->getArgument('username');
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $username is correct as $input->getArgument('username') (which targets Symfony\Component\Consol...nterface::getArgument()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
131 2 View Code Duplication
        if (null !== $username) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
132
            $this->io->text(' > <info>Username</info>: '.$username);
133
        } else {
134 2
            $username = $this->io->ask('Username', null, [$this->validator, 'validateUsername']);
135 2
            $input->setArgument('username', $username);
136
        }
137
138
        // Ask for the password if it's not defined
139 2
        $password = $input->getArgument('password');
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $password is correct as $input->getArgument('password') (which targets Symfony\Component\Consol...nterface::getArgument()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
140 2
        if (null !== $password) {
141
            $this->io->text(' > <info>Password</info>: '.str_repeat('*', mb_strlen($password)));
142
        } else {
143 2
            $password = $this->io->askHidden('Password (your type will be hidden)', [$this->validator, 'validatePassword']);
144 2
            $input->setArgument('password', $password);
145
        }
146
147
        // Ask for the email if it's not defined
148 2
        $email = $input->getArgument('email');
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $email is correct as $input->getArgument('email') (which targets Symfony\Component\Consol...nterface::getArgument()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
149 2 View Code Duplication
        if (null !== $email) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
150
            $this->io->text(' > <info>Email</info>: '.$email);
151
        } else {
152 2
            $email = $this->io->ask('Email', null, [$this->validator, 'validateEmail']);
153 2
            $input->setArgument('email', $email);
154
        }
155
156
        // Ask for the full name if it's not defined
157 2
        $fullName = $input->getArgument('full-name');
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $fullName is correct as $input->getArgument('full-name') (which targets Symfony\Component\Consol...nterface::getArgument()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
158 2 View Code Duplication
        if (null !== $fullName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
159
            $this->io->text(' > <info>Full Name</info>: '.$fullName);
160
        } else {
161 2
            $fullName = $this->io->ask('Full Name', null, [$this->validator, 'validateFullName']);
162 2
            $input->setArgument('full-name', $fullName);
163
        }
164 2
    }
165
166
    /**
167
     * This method is executed after interact() and initialize(). It usually
168
     * contains the logic to execute to complete this command task.
169
     */
170 4
    protected function execute(InputInterface $input, OutputInterface $output)
171
    {
172 4
        $stopwatch = new Stopwatch();
173 4
        $stopwatch->start('add-user-command');
174
175 4
        $username = $input->getArgument('username');
176 4
        $plainPassword = $input->getArgument('password');
177 4
        $email = $input->getArgument('email');
178 4
        $fullName = $input->getArgument('full-name');
179 4
        $isAdmin = $input->getOption('admin');
180
181
        // make sure to validate the user data is correct
182 4
        $this->validateUserData($username, $plainPassword, $email, $fullName);
183
184
        // create the user and encode its password
185 4
        $user = new User();
186 4
        $user->setFullName($fullName);
187 4
        $user->setUsername($username);
188 4
        $user->setEmail($email);
189 4
        $user->setRoles([$isAdmin ? 'ROLE_ADMIN' : 'ROLE_USER']);
190
191
        // See https://symfony.com/doc/current/book/security.html#security-encoding-password
192 4
        $encodedPassword = $this->passwordEncoder->encodePassword($user, $plainPassword);
193 4
        $user->setPassword($encodedPassword);
194
195 4
        $this->entityManager->persist($user);
196 4
        $this->entityManager->flush();
197
198 4
        $this->io->success(sprintf('%s was successfully created: %s (%s)', $isAdmin ? 'Administrator user' : 'User', $user->getUsername(), $user->getEmail()));
199
200 4
        $event = $stopwatch->stop('add-user-command');
201 4
        if ($output->isVerbose()) {
202
            $this->io->comment(sprintf('New user database id: %d / Elapsed time: %.2f ms / Consumed memory: %.2f MB', $user->getId(), $event->getDuration(), $event->getMemory() / pow(1024, 2)));
203
        }
204 4
    }
205
206 4
    private function validateUserData($username, $plainPassword, $email, $fullName)
207
    {
208
        // first check if a user with the same username already exists.
209 4
        $existingUser = $this->users->findOneBy(['username' => $username]);
210
211 4
        if (null !== $existingUser) {
212
            throw new RuntimeException(sprintf('There is already a user registered with the "%s" username.', $username));
213
        }
214
215
        // validate password and email if is not this input means interactive.
216 4
        $this->validator->validatePassword($plainPassword);
217 4
        $this->validator->validateEmail($email);
218 4
        $this->validator->validateFullName($fullName);
219
220
        // check if a user with the same email already exists.
221 4
        $existingEmail = $this->users->findOneBy(['email' => $email]);
222
223 4
        if (null !== $existingEmail) {
224
            throw new RuntimeException(sprintf('There is already a user registered with the "%s" email.', $email));
225
        }
226 4
    }
227
228
    /**
229
     * The command help is usually included in the configure() method, but when
230
     * it's too long, it's better to define a separate method to maintain the
231
     * code readability.
232
     */
233 4
    private function getCommandHelp()
234
    {
235
        return <<<'HELP'
236 4
The <info>%command.name%</info> command creates new users and saves them in the database:
237
238
  <info>php %command.full_name%</info> <comment>username password email</comment>
239
240
By default the command creates regular users. To create administrator users,
241
add the <comment>--admin</comment> option:
242
243
  <info>php %command.full_name%</info> username password email <comment>--admin</comment>
244
245
If you omit any of the three required arguments, the command will ask you to
246
provide the missing values:
247
248
  # command will ask you for the email
249
  <info>php %command.full_name%</info> <comment>username password</comment>
250
251
  # command will ask you for the email and password
252
  <info>php %command.full_name%</info> <comment>username</comment>
253
254
  # command will ask you for all arguments
255
  <info>php %command.full_name%</info>
256
257
HELP;
258
    }
259
}
260