Skip to content

Commit

Permalink
feature #14 Added a new command to create users (javiereguiluz)
Browse files Browse the repository at this point in the history
This PR was squashed before being merged into the master branch (closes #14).

Discussion
----------

Added a new command to create users

The command it's almost finished. I'll add more help notes and the `interact()` method to show how to use it.

Commits
-------

d33a650 Added a new command to create users
  • Loading branch information
javiereguiluz committed Mar 28, 2015
2 parents 8cf500c + d33a650 commit 493e74b
Show file tree
Hide file tree
Showing 4 changed files with 313 additions and 5 deletions.
18 changes: 18 additions & 0 deletions app/Resources/assets/scss/main.scss
Expand Up @@ -144,6 +144,24 @@ body#homepage {
// Page: 'login'
// --------------------------------------------------
body#login {
#login-users-help p {
font-size: $font-size-base;
line-height: $line-height-base;

&:last-child {
margin-bottom: 0;
}

.label {
margin-right: 5px;
}

.console {
display: block;
margin: 5px 0;
padding: 10px;
}
}
}

//
Expand Down
23 changes: 18 additions & 5 deletions app/Resources/views/security/login.html.twig
Expand Up @@ -59,11 +59,24 @@
</tbody>
</table>

<p>
If they don't work, reload your fixtures by running this command
from the terminal: <br/>
<code>$ php app/console doctrine:fixtures:load</code>
</p>
<div id="login-users-help" class="panel panel-default">
<div class="panel-body">
<p>
<span class="label label-success">NOTE</span>
If these users don't work, reload application fixtures by
running this command from the terminal: <br/>

<code class="console">$ php app/console doctrine:fixtures:load</code>
</p>

<p>
<span class="label label-success">TIP</span>
If you want to create new users, run this other command: <br/>

<code class="console">$ php app/console app:add-user</code>
</p>
</div>
</div>
</div>
</div>
{% endblock %}
Expand Down
265 changes: 265 additions & 0 deletions src/AppBundle/Command/AddUserCommand.php
@@ -0,0 +1,265 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace AppBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
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\Question;
use Doctrine\Common\Persistence\ObjectManager;
use AppBundle\Entity\User;

/**
* A command console that creates users and stores them in the database.
* To use this command, open a terminal window, enter into your project
* directory and execute the following:
* $ php app/console app:add-user
*
* To output detailed information, increase the command verbosity:
* $ php app/console app:add-user -vv
*
* See http://symfony.com/doc/current/cookbook/console/console_command.html
*
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*/
class AddUserCommand extends ContainerAwareCommand
{
const MAX_ATTEMPTS = 5;

/** @var ObjectManager */
private $em;

protected function configure()
{
$this
// a good practice is to use the 'app:' prefix to group all your custom application commands
->setName('app:add-user')
->setDescription('Creates users and stores them in the database')
->setHelp($this->getCommandHelp())
// commands can optionally define arguments and/or options (mandatory and optional)
// see http://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')
->addOption('is-admin', null, InputOption::VALUE_NONE, 'If set, the user is created as an administrator')
;
}

/**
* This method is executed before initialize() and 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 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)
{
if (null !== $input->getArgument('username') && null !== $input->getArgument('password') && null !== $input->getArgument('email')) {
return;
}

$output->writeln('');
$output->writeln('Add User Command Interactive Wizard');
$output->writeln('-----------------------------------');

$output->writeln(array(
'',
'If you prefer to not use this interactive wizard, provide the',
'arguments required by this command as follows:',
'',
' $ php app/console app:add-user username password email@example.com',
'',
));

$output->writeln(array(
'',
'Now we\'ll ask you for the value of all the missing command arguments.',
'',
));

// See http://symfony.com/doc/current/components/console/helpers/questionhelper.html
$console = $this->getHelper('question');

// Ask for the username if it's not defined
if (null === $username = $input->getArgument('username')) {
$question = new Question(' > <info>Username</>: ');
$question->setValidator(function ($answer) {
if (empty($answer)) {
throw new \RuntimeException('The username cannot be empty');
}

return $answer;
});
$question->setMaxAttempts(self::MAX_ATTEMPTS);

$username = $console->ask($input, $output, $question);
$input->setArgument('username', $username);
} else {
$output->writeln(' > <info>Username</>: '.$username);
}

// Ask for the password if it's not defined
if (null === $password = $input->getArgument('password')) {
$question = new Question(' > <info>Password</> (your type will be hidden): ');
$question->setValidator(array($this, 'passwordValidator'));
$question->setHidden(true);
$question->setMaxAttempts(self::MAX_ATTEMPTS);

$password = $console->ask($input, $output, $question);
$input->setArgument('password', $password);
} else {
$output->writeln(' > <info>Password</>: '.str_repeat('*', strlen($password)));
}

// Ask for the email if it's not defined
if (null === $email = $input->getArgument('email')) {
$question = new Question(' > <info>Email</>: ');
$question->setValidator(array($this, 'emailValidator'));
$question->setMaxAttempts(self::MAX_ATTEMPTS);

$email = $console->ask($input, $output, $question);
$input->setArgument('email', $email);
} else {
$output->writeln(' > <info>Email</>: '.$email);
}
}

/**
* This method is executed after the interact() and before the execute()
* method. It's main purpose is to initialize the variables used in the rest
* of the command methods.
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
$this->em = $this->getContainer()->get('doctrine')->getManager();
}

/**
* This method is executed after interact() and initialize(). It usually
* contains the logic to execute to complete this command task.
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$startTime = microtime(true);

$username = $input->getArgument('username');
$plainPassword = $input->getArgument('password');
$email = $input->getArgument('email');
$isAdmin = $input->getOption('is-admin');

// first check if a user with the same username already exists
$existingUser = $this->em->getRepository('AppBundle:User')->findOneBy(array('username' => $username));

if (null !== $existingUser) {
throw new \RuntimeException(sprintf('There is already a user registered with the "%s" username.', $username));
}

// create the user and encode its password
$user = new User();
$user->setUsername($username);
$user->setEmail($email);
$user->setRoles(array($isAdmin ? 'ROLE_ADMIN' : 'ROLE_USER'));

// See http://symfony.com/doc/current/book/security.html#security-encoding-password
$encoder = $this->getContainer()->get('security.password_encoder');
$encodedPassword = $encoder->encodePassword($user, $plainPassword);
$user->setPassword($encodedPassword);

$this->em->persist($user);
$this->em->flush($user);

$output->writeln('');
$output->writeln(sprintf('[OK] %s was successfully created: %s (%s)', $isAdmin ? 'Administrator user' : 'User', $user->getUsername(), $user->getEmail()));

if ($output->isVerbose()) {
$finishTime = microtime(true);
$elapsedTime = $finishTime - $startTime;

$output->writeln(sprintf('[INFO] New user database id: %d / Elapsed time: %.2f ms', $user->getId(), $elapsedTime*1000));
}
}

/**
* This internal method should be private, but it's declared as public to
* maintain PHP 5.3 compatibility when using it in a callback.
*
* @internal
*/
public function passwordValidator($plainPassword)
{
if (empty($plainPassword)) {
throw new \Exception('The password can not be empty');
}

if (strlen(trim($plainPassword)) < 6) {
throw new \Exception('The password must be at least 6 characters long');
}

return $plainPassword;
}

/**
* This internal method should be private, but it's declared as public to
* maintain PHP 5.3 compatibility when using it in a callback.
*
* @internal
*/
public function emailValidator($email)
{
if (empty($email)) {
throw new \Exception('The email can not be empty');
}

if (false === strpos($email, '@')) {
throw new \Exception('The email should look like a real email');
}

return $email;
}

/**
* 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
* code readability.
*/
private function getCommandHelp()
{
return <<<HELP
The <info>%command.name%</info> command creates new users and saves them in the database:
<info>php %command.full_name%</info> <comment>username password email</comment>
By default the command creates regular users. To create administrator users,
add the <comment>--is-admin</comment> option:
<info>php %command.full_name%</info> username password email <comment>--is-admin</comment>
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
<info>php %command.full_name%</info> <comment>username password</comment>
# command will ask you for the email and password
<info>php %command.full_name%</info> <comment>username</comment>
# command will ask you for all arguments
<info>php %command.full_name%</info>
HELP;
}
}
12 changes: 12 additions & 0 deletions web/css/app.css
Expand Up @@ -7046,6 +7046,18 @@ footer {
body#homepage {
text-align: center; }

body#login #login-users-help p {
font-size: 15px;
line-height: 1.42857; }
body#login #login-users-help p:last-child {
margin-bottom: 0; }
body#login #login-users-help p .label {
margin-right: 5px; }
body#login #login-users-help p .console {
display: block;
margin: 5px 0;
padding: 10px; }

body#blog_index h1 {
margin-bottom: .5em; }
body#blog_index article.post {
Expand Down

0 comments on commit 493e74b

Please sign in to comment.