Bonus: création de commandes pour gérer les membres depuis le terminal (5/5)

publié le 03/05/2018

symfony 4 commande

J'ai toujours trouvé pratique d'avoir la possibilité de gérer les membres depuis le terminal, soit pour tester en phase de développement, ou même pour créer en 2 secondes un accès admin à un client. L'objectif de cet article est de créer 2 commandes: l'une pour créer 1 membre, la 2nde pour lui assigner un rôle.


Bonus: création de commandes pour gérer les membres depuis le terminal

Evidemment, ces commandes sont intimement liées à l'entité User que nous avons développé dans les précédents épisodes. Consultez mon dépôt Github si vous voulez les détails de cette classe.

Comme souvent, la documentation Symfony sur le sujet est très pédagogique. Jetez-y un oeil sans retenue, soit que vous ayez un doute sur les méthodes employées dans mes classes, ou que vous souhaitiez améliorer mon code (Dieu sait qu'il est perfectible).


1ère commande: création d'un membre

<?php

namespace App\Command;

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 Symfony\Component\Console\Command\Command;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\User;
use Symfony\Component\Validator\Validator\ValidatorInterface;

class CreateUserCommand extends Command
{
    private $em;

    private $passwordEncoder;

    private $validator;

    public function __construct(UserPasswordEncoderInterface $passwordEncoder, EntityManagerInterface $em, ValidatorInterface $validator)
    {
        $this->em = $em;
        $this->passwordEncoder = $passwordEncoder;
        $this->validator = $validator;

        parent::__construct();
    }

    protected function configure()
    {
        $this
            ->setName('app:create-user')
            ->setDescription('Create a user.')
            ->setDefinition(array(
                new InputArgument('username', InputArgument::REQUIRED, 'The username'),
                new InputArgument('email', InputArgument::REQUIRED, 'The email'),
                new InputArgument('password', InputArgument::REQUIRED, 'The password'),
            ))
            ;
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $user = new User();

        $username = $input->getArgument('username');

        $email = $input->getArgument('email');

        $password = $input->getArgument('password');
        $password = $this->passwordEncoder->encodePassword($user, $password);

        $user->setUsername($username);
        $user->setEmail($email);
        $user->setPassword($password);

        $errors = $this->validator->validate($user);

        if (count($errors) > 0) {
            $errorsString = (string) $errors;
            throw new \Exception($errorsString);
        }


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

        $output->writeln(sprintf('Created user %s', $username));
    }

    /**
     * {@inheritdoc}
     */
    protected function interact(InputInterface $input, OutputInterface $output)
    {
        $questions = array();

        if (!$input->getArgument('username')) {
            $question = new Question('Please choose a username:');
            $questions['username'] = $question;
        }

        if (!$input->getArgument('email')) {
            $question = new Question('Please choose an email:');
            $questions['email'] = $question;
        }

        if (!$input->getArgument('password')) {
            $question = new Question('Please choose a password:');
            $question->setHidden(true);
            $question->setHiddenFallback(false);
            $questions['password'] = $question;
        }

        foreach ($questions as $name => $question) {
            $answer = $this->getHelper('question')->ask($input, $output, $question);
            $input->setArgument($name, $answer);
        }
    }
}

Pour éviter les erreurs, vous remarquerez que notre commande dépend du composant validator de Symfony. Testons notre bébé! Rien de plus simple. Depuis le terminal, tapez:

bin/console app:create-user

et laissez-vous guider.


2ème commande: assignation d'un rôle

<?php

namespace App\Command;

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 Symfony\Component\Console\Command\Command;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\User;
use Symfony\Component\Validator\Validator\ValidatorInterface;

class SetRoleUserCommand extends Command
{
    private $em;

    private $validator;

    public function __construct(EntityManagerInterface $em, ValidatorInterface $validator)
    {
        $this->em = $em;
        $this->validator = $validator;

        parent::__construct();
    }

    protected function configure()
    {
        $this
            ->setName('app:set-role-user')
            ->setDescription('Set the user\'s role.')
            ->setDefinition(array(
                new InputArgument('username', InputArgument::REQUIRED, 'The username'),
                new InputArgument('role', InputArgument::REQUIRED, 'New role'),
            ))
            ;
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $username = $input->getArgument('username');

        $user = $this->em->getRepository(User::class)->findOneByUsername($username);

        $role = $input->getArgument('role');

        $roles = $user->setRoles([$role]);

        $errors = $this->validator->validate($user);

        if (count($errors) > 0) {
            $errorsString = (string) $errors;
            throw new \Exception($errorsString);
        }

        $this->em->flush();

        $output->writeln(sprintf('New role defined for %s', $username));
    }

    /**
     * {@inheritdoc}
     */
    protected function interact(InputInterface $input, OutputInterface $output)
    {
        $questions = array();

        if (!$input->getArgument('username')) {
            $question = new Question('The username of the existing user:');
            $questions['username'] = $question;
        }

        if (!$input->getArgument('role')) {
            $question = new Question('New role');
            $questions['role'] = $question;
        }

        foreach ($questions as $name => $question) {
            $answer = $this->getHelper('question')->ask($input, $output, $question);
            $input->setArgument($name, $answer);
        }
    }
}

Depuis la console:

bin/console app:set-role-user

pour tester. A noter que les rôles doivent obligatoirement commencer par ROLE_. Si ce n'est pas le cas, la console va littéralement vous crier dessus ;-)


Conclusion

On pourrait tout à fait imaginer une commande supplémentaire pour supprimer un rôle existant. On pourrait aussi améliorer les erreurs générées dans la console lorsque le composant validator repère une erreur: cela pourrait être plus "user friendly". Cependant, au vu de l'objectif fixé et en considérant que ce sont des commandes qui ne sont pas destinées à être utlisées par le client, on peut estimer que cela suffit. Malgré tout, si vous prenez le temps de les améliorer, je serais ravi si vous partagiez vos trouvailles avec moi.

N'hésitez pas à m'envoyer un message si vous avez des questions ou des remarques.