Symfony 4 : Gestion utilisateurs sans FOSUserBundle v2018 : Chapitre 1

Cet article est écrit en 3 parties : J’avais écrit l’année dernière une série d’articles sur comment gérer les utilisateurs sans FOSUserBundle, il me restait encore à publier « comment modifier son mot de passe et comment faire un « J’ai oublié mon mot de passe ». Vu que les choses ont pas mal bougé en 1 an j’ai décidé de repartir de 0 (mais en reprenant les grandes lignes de l’ancien article). Mon environnement :
  • Linux Mint 19
  • Php 7.2.10
  • MariadB 10.2.18
  • Symfony 4.1.6
On va évidemment commencé par creer le projet :
composer create-project symfony/website-skeleton my-project
Et tout de suite lancer le serveur :
php bin/console server:start
Il suffit maintenant de se rendre sur http://127.0.0.1:8000, et voilà ! Une des nouveautés du bundle maker c’est la possibilité de créer une classe user en ligne de commande ! C’est déjà inclus dans la version que nous avons installé, mais sinon il aurait suffit de faire :
composer require symfony/maker-bundle --dev
Nous allons maintenant passer à la création de la classe :
php bin/console make:user
Vous pourrez, sauf si exception, sélectionner tout le temps le choix par défaut. Nous voilà donc avec une classe toute propre contenant quelques champs (email, roles, mot de passe) :
class User implements UserInterface
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=180, unique=true)
     */
    private $email;

    /**
     * @ORM\Column(type="json")
     */
    private $roles = [];

    /**
     * @var string The hashed password
     * @ORM\Column(type="string")
     */
    private $password;
Mais ce n’est pas tout, le maker a aussi créer le fichier repository ainsi que le fichier /config/packages/security.yaml Pour ajouter un champ, il suffit d’utiliser le maker entity :
hesiode@hesiode-MS-7917:/var/www/userDemo2018$ php bin/console make:entity

 Class name of the entity to create or update (e.g. VictoriousJellybean):
 > User

 Your entity already exists! So let's add some new fields!

 New property name (press <return> to stop adding fields):
 > nomComplet

 Field type (enter ? to see all types) [string]:
 > 

 Field length [255]:
 > 

 Can this field be null in the database (nullable) (yes/no) [no]:
 > 

 updated: src/Entity/User.php

 Add another property? Enter the property name (or press <return> to stop adding fields):
 > 


           
  Success! 
           

 Next: When you're ready, create a migration with make:migration
Avant de créer la base n’oubliez pas de modifier le fichier env et d’y mettre à jour les identifiants pour accès à votre base de données.
DATABASE_URL=mysql://root:db_password@127.0.0.1:3306/userDemo2018
On va lancer l’update du schema :
hesiode@hesiode-MS-7917:/var/www/userDemo2018$ php bin/console doctrine:database:create
Created database `userDemo2018` for connection named default
hesiode@hesiode-MS-7917:/var/www/userDemo2018$ php bin/console make:migration

           
  Success! 
           

 Next: Review the new migration "src/Migrations/Version20181030194442.php"
 Then: Run the migration with php bin/console doctrine:migrations:migrate
 See https://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html
hesiode@hesiode-MS-7917:/var/www/userDemo2018$ php bin/console doctrine:migrations:migrate
                                                              
                    Application Migrations                    
                                                              

WARNING! You are about to execute a database migration that could result in schema changes and data loss. Are you sure you wish to continue? (y/n)y
Migrating up to 20181030194442 from 0

  ++ migrating 20181030194442

     -> CREATE TABLE user (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, nom_complet VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_8D93D649E7927C74 (email), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB

  ++ migrated (0.04s)

  ------------------------

  ++ finished in 0.04s
  ++ 1 migrations executed
  ++ 1 sql queries
hesiode@hesiode-MS-7917:/var/www/userDemo2018$ 
On va déjà créer quelques utilisateurs fake, grâce aux fixtures, pour ça il faut ajouter un composant puis la librairie faker :
composer require orm-fixtures --dev

composer require fzaninotto/faker --dev
Puis toujours avec ce puissant maker, on va créer la classe que l’on appellera UserFixtures :
hesiode@hesiode-MS-7917:/var/www/userDemo2018$ php bin/console make:fixtures

 The class name of the fixtures to create (e.g. AppFixtures):
 > UserFixtures

 created: src/DataFixtures/UserFixtures.php

           
  Success! 
           

 Next: Open your new fixtures class and start customizing it.
 Load your fixtures by running: php bin/console doctrine:fixtures:load
 Docs: https://symfony.com/doc/master/bundles/DoctrineFixturesBundle/index.html
hesiode@hesiode-MS-7917:/var/www/userDemo2018$ 
Ce qui donnera :
<?php

namespace App\DataFixtures;

use App\Entity\User;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
use Faker;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;

class UserFixtures extends Fixture
{
    private $passwordEncoder;

    public function __construct(UserPasswordEncoderInterface $passwordEncoder)
    {
        $this->passwordEncoder = $passwordEncoder;
    }

    public function load(ObjectManager $manager)
    {
        // On configure dans quelles langues nous voulons nos données
        $faker = Faker\Factory::create('fr_FR');

        // on créé 10 users
        for ($i = 0; $i < 10; $i++) {
            $user = new User();
            $user->setNomComplet($faker->name);
            $user->setEmail(sprintf('userdemo%d@example.com', $i));
            $user->setPassword($this->passwordEncoder->encodePassword(
                $user,
                'userdemo'
            ));
            $manager->persist($user);
        }

        $manager->flush();

    }
}
Il suffit maintenant de créer les fixtures :
hesiode@hesiode-MS-7917:/var/www/userDemo2018$ php bin/console doctrine:fixtures:load
Careful, database will be purged. Do you want to continue y/N ?y
  > purging database
  > loading App\DataFixtures\UserFixtures
hesiode@hesiode-MS-7917:/var/www/userDemo2018$ 
Ce qui nous donne dans la base 10 magnifiques users : Nous allons maintenant créer le controller, et là aussi on va utiliser le maker :
php bin/console make:controller
Nous l’appellerons simplement SecurityController :
hesiode@hesiode-MS-7917:/var/www/userDemo2018$ php bin/console make:controller

 Choose a name for your controller class (e.g. TinyElephantController):
 > SecurityController

 created: src/Controller/SecurityController.php
 created: templates/security/index.html.twig

           
  Success! 
           

 Next: Open your new controller class and add some pages!
hesiode@hesiode-MS-7917:/var/www/userDemo2018$ 
Ce maker créé le controller mais aussi la vue (le fichier twig), on peut donc se rendre sur http://127.0.0.1:8001/security Nous aurons pas besoin de cette route (/security) on pourra la supprimer, on va créer à la place /login qui va se charger d’afficher un formulaire de connexion :
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class SecurityController extends AbstractController
{
    /**
     * @Route("/login", name="user_login")
     */
    public function login()
    {
        return $this->render('security/login.html.twig', []);
    }
}
Et la le fichier twig security/login.html.twig :
{% extends 'base.html.twig' %}

{% block title %}Hello !{% endblock %}

{% block body %}
<style>
    .example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; }
    .example-wrapper code { background: #F5F5F5; padding: 2px 6px; }
</style>

<div class="example-wrapper">

    Login User incoming !

</div>
{% endblock %}
Voilà pour cette partie ! Dans la prochaine partie nous rentrerons dans le dur avec la création du formulaire et la connexion de l’utilisateur. Vous pourrez retrouver le code de l’ensemble du projet sur github : https://github.com/gponty/userDemo2018  

21 commentaires

  1. Bonjour
    quand j’essaye de faire la migration doctrine me sort des erreurs:
    SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ‘JSON NOT NULL, passwo
    rd VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_8D93D649E7927C7’ at line 1

    si quelqu’un peut m’aider
    merci

      1. Merci de ta prompte réponse.
        J’ai commencé à créer cette gestion d’utilisateurs, qui me paraît plus simple à gérer que FosUser.
        Tes explications sont claires (bien qu’il manque par exemple cet item sur comment dréer un user…)
        Mon itention est d’arriver à interfacer ça avec EasyAdmin ! Il va falloir que je comprenne comment dire à EasyAdmin comment crypter le mot de passe (avec bcrypt, que j’ai choisi) !

        J’ai commencé à créer cette Command (en utilisant la doc de Symfony) , mais il faut d’abord que je crée un service UserManager. Là ça devient plus dur !

        1. Eheh c’est ce qui est passionnant ! Bon courage !

          Par contre tu n’as pas forcement besoin d’un usermanager.
          Tu peux créer ton user directement dans ta commande, à toi de voir !

  2. Ca peut donner quelque chose comme ça :
    (si tu comptes reeutiliser la création d’user, il vaudrait effectivement mieux passer par un service)
    Tu peux aussi passer l’email, mdp, .. en paramètre de ta commande

    class CreateUserCommand extends Command
    {
    protected static $defaultName = 'create:user';

    private $em;

    private $passwordEncoder;

    public function __construct(
    EntityManagerInterface $em,
    UserPasswordEncoderInterface $passwordEncoder
    )
    {

    $this->em = $em;
    $this->passwordEncoder = $passwordEncoder;

    parent::__construct();

    }

    protected function configure()
    {
    $this->setDescription('Creation d\'un user');
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
    $io = new SymfonyStyle($input, $output);

    $user1 = new User();
    $user1->setEmail('g.ponty@dev-web.io');
    $encoded = $this->passwordEncoder->encodePassword($user1, 'passwd');
    $user1->setPassword($encoded);
    $this->em->persist($user1);

    $this->em->flush();

    $io->success('User created.');
    }
    }

  3. Bonjour Guillaume,

    Je voulais d’abord te remercier pour le tuto. Super pratique !

    Seulement, mon cas est différent et je ne sais pas sous quel angle je dois prendre le problème. Explications :

    J’ai une page twig « home » où il est affiché le formulaire de connexion, le formulaire d’inscription et les dernières actualités(le dernier point reste à faire). La vue passe par un contrôleur nommé « HomeController » où il y a une méthode index() où j’y avais commencé à placer le petit bout de code pour l’inscription d’un nouvel utilisateur :

    public function index(Request $request, UserPasswordEncoderInterface $encoder)
    {
    // Formulaire de connexion

    // Création d’un nouvel utilisateur
    $user = new User();

    // Création du formulaire d’inscription
    $form = $this->createForm(UserType::class, $user);

    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid())
    {
    // Encodage du mot de passe
    $hash = $encoder->encodePassword($user, $user->getPassword());

    $user->setPassword($hash);
    $user->setCreatedAt(new DateTime);

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

    $this->addFlash(« success », « Votre inscription a bien été prise en compte. »);
    return $this->redirectToRoute(« newsFeed »);
    }

    return $this->render(‘home/home.html.twig’, [
    « formRegister » => $form->createView()
    ]);
    }

    Aurais-tu une idée sur la façon dont je dois appeler la méthode login() dans la méthode index() sans pour autant rentrer en conflit avec le formulaire d’inscription ? Je suis un peu perdu depuis quelques jours avec ça. Est-ce qu’il faut absolument qu’il y ait une nouvelle route pour le login ou puis-je afficher tout de même le login via la page twig « home » ?

    Désolé si je ne parais pas clair mais moi-même je suis dans le flou là. 😀

    1. Bonjour Arnaud,
      Oui tu peux sans problème, il suffit que tu créés 2 formulaires distincts. ($formLogin et $formInscription par exemple)

      Guillaume

      1. Cela veut dire que je dois créer un fichier LoginType et non faire un formulaire à la mano directement dans le fichier home.html.twig ?! Comment vais-je pouvoir faire comprendre à Symfony que ce LoginType est à prendre en compte lors de la connexion ?

        Désolé, je débute encore sous Symfony. J’ai une autre problématique à résoudre, c’est la connexion automatique après s’être inscrit sur le site. De ce côté là, j’ai l’impression que c’est le Guard qui doit faire le boulot avec la possibilité de faire des entrées différentes pour se logger… mais je suis encore loin de tout comprendre.

        Merci beaucoup Guillaume en tout cas pour cette première réponse.

        1. J’ai résolu le problème en séparant les 2 formulaires dans des pages twig différentes. 🙂

          J’avais réussi à tout faire fonctionner mais le problème est que le paramètre « _username » du formulaire de connexion est récupéré dès la requête et donc lorsque je voulais m’inscrire, via le formulaire, sur le site, Symfony me renvoyait une erreur de type « The key « _username » must be a string, « NULL » given. »
          Il me demandait absolument que je lui rentre une valeur même si je n’utilisais pas le formulaire de connexion. Cet attribut prend ses aises et occupe tout l’espace. 😀

          Voilà mon petit retour d’expérience. J’ai l’impression que l’architecture Symfony n’apprécie pas trop le fait qu’il y ait plusieurs formulaires sur une même page…

  4. Bonjour et merci pour ce tuto.

    J’ai deux question qui viennent suite à ça.
    Je travaille sur un projet qui utilise actuellement fosuser et j’aimerai l’enlever pour suivre ton tuto et avoir une meilleure main sur ces fonctions. Cependant je ne trouve pas comment supprimer fosuser pour passer à ça. Ce n’est pas moi qui l’ai intégrer et je ne sais pas quoi enlever.
    Et mon autre questions était si c’était possible de se servir de tous ça avec une base mongodb et donc pas d’entity mais des document ?

    Merci d’avance.

  5. Bonjour,

    Ce tuto est vraiment nickel chrome. J’ai par contre un souci bloquant et majeur après la validation du formulaire de login.
    Erreur 500…
    La log indique des pb de mémoire :
    PHP Fatal error: Allowed memory size…exhausted
    puis une erreur 500
    SERVER POST (500) /login »::1″
    pour juste après indiquer que l’authentification est ok :
    SECURI Guard authentication successful!
    mais sauf que la page devient vide avec une erreur 500 dans Chrome et dans la log du serveur symfony

    Pas la moindre piste pour l’instant, j’ai tout relu en vérifiant chaque ligne par rapport à ce tuto mais sans succès depuis 4 jours…
    Qqun aurait une idée quelconque ???

    Config locale :
    Mac OS 10.14.6 / Serveur Apache 2.4.34 / PHP 7.1.23 / Symfony 4.3.4

  6. Juste un grand Merci, j’ai du contourner certaines choses pour l’adapter à mon projet, mais bien plus clair que tout ce que j’ai pu trouver entre StackOverflow et YT. Keep up!

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.