[Symfony 4] Gestion utilisateurs sans FosUserBundle

@deprecated : nouvelle version => http://blog.dev-web.io/2018/10/30/symfony-4-gestion-utilisateurs-sans-fosuserbundle-v2018-chapitre-1/

Après « Comment utiliser FosUserbundle« , comment se passer de FosUserBundle !

Mais à l’heure où j’écris ces lignes il n’y a de toute façon pas le choix puisque FosUserBundle n’est pas encore compatible avec Symfony 4.

Plutôt que d’attendre qu’il le soit, j’aurai pu y contribuer, mais j’ai préféré me retrousser les manches et mettre les mains dans le cambouis pour développer ma propre solution de gestion des utilisateurs.

Ceci afin de ne pas être dépendant de FOSUserBundle et devoir attendre qu’il soit compatible avec les dernières version de Symfony, mais surtout de bien comprendre les mécanismes de sécurité de Symfony (et aussi parce que ça faisait longtemps que j’avais écrit sur ce blog).

D’abord on prend un papier et un stylo et on répond à la question : Que doit faire un système de gestion des utilisateurs ?

On va commencer par des réponses simples :

  • Inscription d’un utilisateur
  • Connexion de l’utilisateur
  • Affecter des droits (rôles)
  • Récupérer son mot de passe
  • Remplacer son mot de passe

Dans cette 1ere partie nous allons juste voir la création de notre entité User.

Afin de mettre en place tout ça on va commencer par créer un projet Symfony 4 (pour ça il faut absolument php 7.1, sinon ce sera du symfony 3.4)  :

composer create-project symfony/skeleton UserDemo

Puis ajouter tous les composants que l’on aura besoin :

Doctrine et maker pour générer des controllers, entity :

composer require doctrine maker

Gestion de la sécurité :

composer require symfony/security-bundle

Moteur de template Twig :

composer require twig

Toolbar debug :

composer require web-profiler-bundle

Annotations :

composer require annotations

Validation :

composer require validator

Gestion de fixtures :

composer require --dev doctrine/doctrine-fixtures-bundle

Pour créer la base de donnée, on met à jour notre config dans le fichier .env :

DATABASE_URL=mysql://root:@127.0.0.1:3306/userdemo

puis pour la créer dans mysql :

php bin/console doctrine:database:create

On va aussi creer notre 1er controller :

php bin/console make:controller

On va maintenant créer notre Entité User, c’est celle ci qui contiendra toutes les données de l’utilisateur (username, email, mot de passe, …) :

php bin/console make:entity
On a donc un nouveau fichier dans src/Enity/User.php :
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 * @ORM\Table(name="user")
 */
class User implements UserInterface, \Serializable
{
    /**
     * @var int
     *
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(type="string")
     */
    private $fullName;

    /**
     * @var string
     *
     * @ORM\Column(type="string", unique=true)
     */
    private $username;

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

    /**
     * @var string
     *
     * @ORM\Column(type="string")
     */
    private $password;

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

    public function getId(): int
    {
        return $this->id;
    }

    public function setFullName(string $fullName): void
    {
        $this->fullName = $fullName;
    }

    public function getFullName(): string
    {
        return $this->fullName;
    }

    public function getUsername(): string
    {
        return $this->username;
    }

    public function setUsername(string $username): void
    {
        $this->username = $username;
    }

    public function getEmail(): string
    {
        return $this->email;
    }

    public function setEmail(string $email): void
    {
        $this->email = $email;
    }

    public function getPassword(): string
    {
        return $this->password;
    }

    public function setPassword(string $password): void
    {
        $this->password = $password;
    }

    /**
     * Retourne les rôles de l'user
     */
    public function getRoles(): array
    {
        $roles = $this->roles;

        // Afin d'être sûr qu'un user a toujours au moins 1 rôle
        if (empty($roles)) {
            $roles[] = 'ROLE_USER';
        }

        return array_unique($roles);
    }

    public function setRoles(array $roles): void
    {
        $this->roles = $roles;
    }

    /**
     * Retour le salt qui a servi à coder le mot de passe
     *
     * {@inheritdoc}
     */
    public function getSalt(): ?string
    {
        // See "Do you need to use a Salt?" at https://symfony.com/doc/current/cookbook/security/entity_provider.html
        // we're using bcrypt in security.yml to encode the password, so
        // the salt value is built-in and you don't have to generate one

        return null;
    }

    /**
     * Removes sensitive data from the user.
     *
     * {@inheritdoc}
     */
    public function eraseCredentials(): void
    {
        // Nous n'avons pas besoin de cette methode car nous n'utilions pas de plainPassword
        // Mais elle est obligatoire car comprise dans l'interface UserInterface
        // $this->plainPassword = null;
    }

    /**
     * {@inheritdoc}
     */
    public function serialize(): string
    {
        return serialize([$this->id, $this->username, $this->password]);
    }

    /**
     * {@inheritdoc}
     */
    public function unserialize($serialized): void
    {
        [$this->id, $this->username, $this->password] = unserialize($serialized, ['allowed_classes' => false]);
    }
}

On met la base à jour :

php bin/console doctrine:schema:update --force

Nous allons tout de suite créer un utilisateur en utilisant les fixtures (c’est-à-dire des exemples de données), il faut d’abord ajouter un encoder dans le fichier security.yaml :

security:
    encoders:
        App\Entity\User: bcrypt
Créer le fichier src/DataFixtures/AppFixtures.php
namespace App\DataFixtures;

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

class AppFixtures extends Fixture
{
    private $passwordEncoder;

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

    public function load(ObjectManager $manager)
    {
        foreach ($this->getUserData() as [$fullname, $username, $password, $email, $roles]) {
            $user = new User();
            $user->setFullName($fullname);
            $user->setUsername($username);
            $user->setPassword($this->passwordEncoder->encodePassword($user, $password));
            $user->setEmail($email);
            $user->setRoles($roles);

            $manager->persist($user);
            $this->addReference($username, $user);
        }

        $manager->flush();
    }

    private function getUserData(): array
    {
        return [
            // $userData = [$fullname, $username, $password, $email, $roles];
            ['Jane Doe', 'jane_admin', 'kitten', 'jane_admin@symfony.com', ['ROLE_ADMIN']],
            ['Tom Doe', 'tom_admin', 'kitten', 'tom_admin@symfony.com', ['ROLE_ADMIN']],
            ['John Doe', 'john_user', 'kitten', 'john_user@symfony.com', ['ROLE_USER']],
        ];
    }


}

Il n’y a plus qu’à charger les fixtures :

php bin/console doctrine:fixtures:load

Et si tout va bien vous devez avoir dans votre base 3 utilisateurs :

Dans le prochain chapitre nous verrons comment se connecter.

En attendant vous pouvez retrouver l’ensemble des sources ici : https://github.com/gponty/userDemo

35 commentaires

  1. Bonjour.
    Merci pour ce tutorial.

    Je rencontre un message d’erreur.
    J’ai essayé de suivre le tutoriel ca ne fonctionne pas, idem en clonant ton repository GitHub.

    Aurez-tu une idée de la source de ce bug ?

    Cordialement

    Voici le message d’erreur

    2018-01-02T17:50:41+01:00 [error] Error thrown while running command « doctrine:schema:update –force ». Message: « An exception occurred while executing ‘CREATE TABLE user (id INT AUTO_INCREMENT NOT NULL, full_name VARCHAR(255) NOT NULL, username VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, password VARCHAR(64) NOT NULL, roles JSON NOT NULL, UNIQUE INDEX UNIQ_8D93D649F85E0677 (username), UNIQUE INDEX UNIQ_8D93D649E7927C74 (email), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB’:

    SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘JSON NOT NULL, UNIQUE INDEX UNIQ_8D93D649F85E0677 (username), UNIQUE INDEX UNIQ_’ at line 1 »

    In AbstractMySQLDriver.php line 94:

    An exception occurred while executing ‘CREATE TABLE user (id INT AUTO_INCRE
    MENT NOT NULL, full_name VARCHAR(255) NOT NULL, username VARCHAR(255) NOT N
    ULL, email VARCHAR(255) NOT NULL, password VARCHAR(64) NOT NULL, roles JSON
    NOT NULL, UNIQUE INDEX UNIQ_8D93D649F85E0677 (username), UNIQUE INDEX UNIQ
    _8D93D649E7927C74 (email), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLL
    ATE utf8_unicode_ci ENGINE = InnoDB’:

    SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error i
    n your SQL syntax; check the manual that corresponds to your MySQL server v
    ersion for the right syntax to use near ‘JSON NOT NULL, UNIQUE INDEX UNIQ_8
    D93D649F85E0677 (username), UNIQUE INDEX UNIQ_’ at line 1

    In PDOConnection.php line 106:

    SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error i
    n your SQL syntax; check the manual that corresponds to your MySQL server v
    ersion for the right syntax to use near ‘JSON NOT NULL, UNIQUE INDEX UNIQ_8
    D93D649F85E0677 (username), UNIQUE INDEX UNIQ_’ at line 1

    In PDOConnection.php line 104:

    SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error i
    n your SQL syntax; check the manual that corresponds to your MySQL server v
    ersion for the right syntax to use near ‘JSON NOT NULL, UNIQUE INDEX UNIQ_8
    D93D649F85E0677 (username), UNIQUE INDEX UNIQ_’ at line 1

    doctrine:schema:update [–complete] [–dump-sql] [-f|–force] [–em [EM]] [-h|–help] [-q|–quiet] [-v|vv|vvv|–verbose] [-V|–version] [–ansi] [–no-ansi] [-n|–no-interaction] [-e|–env ENV] [–no-debug] [–]

        1. Ah le problème vient de là, pour utiliser le type json il faut au moins un MySql 5.7.8
          Je te conseille de migrer, ce qui te permettra de profiter de pas mal de nouvelles fonctionnalités.

          1. Bonjour, j’ai le même soucis sous linux (ça marche sous windows). Guillaume avez-vous trouvé une solution sans changer de version de MySQL ?

          2. Bonjour,
            Oui il peut y avoir plusieurs solutions mais il faut modifier le code (comme par exemple mettre du string plutôt que du json)

  2. Bonjour,

    J’essaie de reproduire votre tuto mais au moment de la connexion, j’obtiens toujours un « invalid credentials », d’ou cette erreur pourrait se produire ?
    Je suis sur symfony 3 , j’ai donc remplacer $role[] : json par $role : json_array donc même principe , mais impossible de me connecter.

    Cordialement,

    1. Bonjour,

      Bizarre comme ça je ne vois pas, est-ce que tu as une erreur particulière dans les logs ?
      Est-ce que la table est bien remplie ?

      1. La table est bien remplie et je n’ai pas d’erreur particuliere, je n’ai pas fait de fixture par contre je rentre mon utilisateur avec les formulaires, et je n’ai pas d’username j’ai simplement renvoyer le getUsername() sur mon email.

        Je me demande sinon si ca peut venir du framework.yml qui n’est pas présent sous symfony3 ou du $helper->GetLastUsername() ?

  3. Bonjour, mon ide netbeans me reconnait des erreurs sur

    [$this->id, $this->username, $this->password] = unserialize($serialized, [‘allowed_classes’ => false]);

    Et

    public function getSalt(): ?string

    Personnellement je ne connais pas non plus ces syntaxes

  4. Bonjour,
    J’ai eu le même problème sur ma machine.
    C’est la version de base de données que j’ai qui ne supporte pas le type JSON.
    J’ai MiraDB comme système de base de données.
    J’ai fait la mise à jour vers la version 10.2.14 et depuis aucun problème.

  5. j’ai un problème lors de l’éxécution de la commande php bin/console doctrine:fixtures:load
    l’erreur est la suivante:
    Parse error : syntaxe error , unexpected ‘[‘

  6. Excellent article.
    Pour ma part j’ai eu un problème en chargerant mes fixtures: il ne les trouvait simplement pas:
    $ php bin/console doctrine:fixtures:load
    In LoadDataFixturesDoctrineCommand.php line 95:

    Could not find any fixture services to load.

    Après avoir « googelisé » le message, je suis tombé sur la solution de déclarer les fixtures en tant que services dans config/services.yaml :

    # and have a tag that allows actions to type-hint services
    App\DataFixtures\:
    resource: ‘../src/DataFixtures’
    tags: [‘doctrine.fixture.orm’]

    .. et çca marche nickel !
    source: https://stackoverflow.com/questions/47613979/symfony-3-4-0-could-not-find-any-fixture-services-to-load

  7. Bonjour,
    j’essaye de suivre le tuto, quand j’essaye de mettre à jour le schema ça coince au niveau du champs ‘roles’, je remplacé le type ‘json’ par ‘text’ mais je continue à avoir une erreur, je suis sous mariaDb 10.0.34 :

    In AbstractMySQLDriver.php line 125:

    An exception occurred while executing ‘CREATE TABLE user (id INT AUTO_INCRE
    MENT NOT NULL, full_name VARCHAR(255) NOT NULL, username VARCHAR(255) NOT N
    ULL, email VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL, roles LON
    GTEXT NOT NULL, UNIQUE INDEX UNIQ_8D93D649F85E0677 (username), UNIQUE INDEX
    UNIQ_8D93D649E7927C74 (email), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8
    mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB’:

    SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was t
    oo long; max key length is 767 bytes

    Merci pour votre aide !

  8. Bonjour.
    Merci pour ce tutorial.

    Je rencontre un message d’erreur.
    J’ai essayé de suivre le tutoriel ca ne fonctionne pas, idem en clonant ton repository GitHub.

    Aurez-tu une idée de la source de ce bug ?

    Cordialement

    Voici le message d’erreur
    2018-07-26T08:20:58+00:00 [error] Error thrown while running command «  »doctrine:fixtures:load » ». Message: « There are no commands defined in the « doctrine:fixtures » namespace.

    Did you mean one of these?
    doctrine
    doctrine:cache
    doctrine:database
    doctrine:generate
    doctrine:mapping
    doctrine:migrations
    doctrine:query
    doctrine:schema »

    There are no commands defined in the « doctrine:fixtures » namespace.

    Did you mean one of these?
    doctrine
    doctrine:cache
    doctrine:database
    doctrine:generate
    doctrine:mapping
    doctrine:migrations
    doctrine:query
    doctrine:schema

  9. Salut , tout d’abord merci pour ce tuto, j’ai essayé de suivre pas a pas les étapes du tuto, mais la dernière commande m’affiche ceci:

    C:\wamp64\www\dev-web.io\UserDemo>php bin/console doctrine:fixtures:load
    2018-10-18T16:49:03+00:00 [error] Error thrown while running command «  »doctrine:fixtures:load » ». Message: « There are no commands defined in the « doctrine:fixtures » namespace.

    Did you mean one of these?
    doctrine
    doctrine:cache
    doctrine:database
    doctrine:generate
    doctrine:mapping
    doctrine:migrations
    doctrine:query
    doctrine:schema »

    There are no commands defined in the « doctrine:fixtures » n
    amespace.

    Did you mean one of these?
    doctrine
    doctrine:cache
    doctrine:database
    doctrine:generate
    doctrine:mapping
    doctrine:migrations
    doctrine:query
    doctrine:schema

    ……………………………………………………………..
    et je ne comprend pas trop, SVP besoin d’aide, c’est ma première fois d’utiliser un framework

  10. Bonsoir, tout d’abord merci pour ce tuto, j’ai essayé de suivre de pas a pas toutes les étapes du tuto mais a la dernière commande il y a une erreur qui s’affiche voici :

    C:\wamp64\www\dev-web.io\UserDemo>php bin/console doctrine:fixtures:load
    2018-10-18T17:14:19+00:00 [error] Error thrown while running command «  »doctrine:fixtures:load » ». Message: « There are no commands defined in the « doctrine:fixtures » namespace.

    Did you mean one of these?
    doctrine
    doctrine:cache
    doctrine:database
    doctrine:generate
    doctrine:mapping
    doctrine:migrations
    doctrine:query
    doctrine:schema »

    There are no commands defined in the « doctrine:fixtures » n
    amespace.

    Did you mean one of these?
    doctrine
    doctrine:cache
    doctrine:database
    doctrine:generate
    doctrine:mapping
    doctrine:migrations
    doctrine:query
    doctrine:schema

    ************************
    SVP besoin d’aide, c’est ma première fois d’utiliser un framework et débutante dans le domaine

    1. Bonjour,
      Je pense que tu as oublié de lancer ces 2 commandes :
      composer req –dev make doctrine/doctrine-fixtures-bundle
      composer req –dev fzaninotto/faker

      1. Bonjour ces 2 commandes on l’exécute dans quelle répertoire on exécute ces commandes. voici quand j’exécute les commandes:

        C:\wamp64\www\dev-web.io\UserDemo>composer req -dev make doctrine/doctrine-fixtures-bundle

        [RuntimeException]
        Invalid working directory specified, ev does not exist.

        C:\wamp64\www\dev-web.io\UserDemo>cd doctrine
        Le chemin d’accès spécifié est introuvable.

        C:\wamp64\www\dev-web.io\UserDemo>composer req -dev fzaninotto/faker

        [RuntimeException]
        Invalid working directory specified, ev does not exist.

        C:\wamp64\www\dev-web.io\UserDemo>

  11. Bonjour Guillaume,

    J’ai beaucoup appris sur ce Blog. Merci.
    Avez-vous un lien où je peux trouver comment gérer l’affectation des droit(rôle) aux utilisateurs d’une appli symfony4. Je veux simplement interdire UPDATE et DELET à certains utilisateur, mais mysql ignore les users de mon appli.

  12. Bonjour,
    Merci pour le tuto svp quelqu’un sait m’aider comment envoyer un email de confirmation a un utilisateur accompagné d’un mot de passe standard.

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.