Implémentation de FOSUserBundle dans Symfony 3.2

On souhaite implémenter le bundle FOSUserBundle dans une application Symfony 3.2 afin de fournir un espace d'administration pour les administrateurs et un espace privé pour les utilisateurs.

Publié le 30/04/2017

1 - Création de l'application Symfony

1.1 - Symfony Installer

On va utiliser le Symfony Installer pour créer le projet.

Installer le Symfony Installer comme décrit dans le README.md du dépot Github : https://github.com/symfony/symfony-installer

Installation sur Linux ou Mac OS :


sudo curl -LsS http://symfony.com/installer -o /usr/local/bin/symfony
sudo chmod a+x /usr/local/bin/symfony

1.2 - Création de l'application

symfony new FosSf3 

Remarque : Sur Symfony 3.4, il faudra remplacer la ligne suivante dans le fichier composer.json :


"autoload": {
        "psr-4": {
            "": "src/"
        },
        //...    },

Puis exécuter la commande :

php composer.phar dump-autoload 

1.3 - Création des Bundles

On va organiser notre application en quatre bundles :

  • MainBundle : contiendra les entités et les services commun au FrontOfficeBundle et au BackOfficeBundle
  • BackOfficeBundle : concerne le back-office administrateur
  • FrontOfficeBundle : concerne le front-office utilisateur
  • CmsBundle : concerne l'accès public au site

php bin/console generate:bundle

Remplir le formulaire de création de bundle :

  • partage avec d'autres applications : no
  • espace de nom : FosSf3/NomBundle [Ex : FosSf3/MainBundle]
  • dossier cible : src/
  • configuration format : annotation

On supprime le Bundle App crée par défaut à l'installation de Symfony :

  • Suppression de l'entrée dans le fichier le fichier app/AppKernel.php
  • Suppression des routes du bundle dans le fichier app/config/routing.yml
  • Suppression des services du bundle dans le fichier app/config/services.yml
  • Suppression du Bundle src/AppBundle

1.4 - Installation de FOSUserBundle


curl -s https://getcomposer.org/installer | php
php composer.phar require friendsofsymfony/user-bundle

Activation du Bundle :

nano app/AppKernel.php


<?php
// app/AppKernel.php

public function registerBundles()
{
    $bundles = array(
        // ...
        new FOS\UserBundle\FOSUserBundle(),
        // ...
    );
}

1.5 - Ajout de la configuration de FOSUserBundle

nano app/config/config.yml


# FOS User Bundle
fos_user:
    db_driver: orm
    firewall_name: main
    user_class: Sf3App\MainBundle\Entity\User
    service:
        mailer: fos_user.mailer.twig_swift
    registration:
        form:
            type: FOS\UserBundle\Form\Type\RegistrationFormType
            name: fos_user_registration_form
            validation_groups: [Registration, Default]
        confirmation:
            enabled: true
            template: '@FOSUser/Registration/email.txt.twig'
    profile:
        form:
            type: FOS\UserBundle\Form\Type\ProfileFormType
            #validation_groups: [Profile, Default]
    resetting:
        email:
            template: '@FOSUser/Resetting/email.txt.twig'
    group:
        group_class: FosSf3\MainBundle\Entity\Group
    from_email:
        address: mail@domain.tld
        sender_name: user

Activer les traduction en décommentant la ligne "configuration translator" dans config.yml :


// ..
framework:
    // ..
    translator:      { fallbacks: ["%locale%"] }

1.6 - Création de l'entité User

nano src/FosSf3/MainBundle/Entity/User.php


<?php

namespace FosSf3\MainBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use FOS\UserBundle\Model\User as BaseUser;

/**
 * Class User
 * @package FosSf3\MainBundle\Entity
 * @ORM\Table(name="fos_user")
 * @ORM\Entity
 */
class User extends BaseUser
{
    const ROLE_SUPER_ADMIN = 'ROLE_SUPER_ADMIN';
    const ROLE_ADMIN = 'ROLE_ADMIN';
    const ROLE_USER = 'ROLE_USER';

    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @var
     *
     * @ORM\Column(name="gender", type="string", length=255, nullable=true)
     */
    protected $gender;

    /**
     * @var
     *
     * @ORM\Column(name="firstname", type="string", length=255, nullable=true)
     */
    protected $firstname;

    /**
     * @var
     *
     * @ORM\Column(name="lastname", type="string", length=255, nullable=true)
     */
    protected $lastname;

    /**
     * @var
     *
     * @ORM\Column(name="address", type="text", nullable=true, nullable=true)
     */
    protected $address;

    /**
     * @var
     *
     * @ORM\Column(name="zip_code", type="string", length=255, nullable=true)
     */
    protected $zipCode;

    /**
     * @var
     *
     * @ORM\Column(name="city", type="string", length=255, nullable=true)
     */
    protected $city;

    /**
     * @var
     *
     * @ORM\Column(name="country", type="string", length=255, nullable=true)
     */
    protected $country;

    /**
     * @var
     *
     * @ORM\Column(name="phone", type="string", length=255, nullable=true)
     */
    protected $phone;

    /**
     * Constructor
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * @param mixed $gender
     */
    public function setGender($gender)
    {
        $this->gender = $gender;
    }

    /**
     * @return mixed
     */
    public function getGender()
    {
        return $this->gender;
    }

    /**
     * @param mixed $firstname
     */
    public function setFirstname($firstname)
    {
        $this->firstname = $firstname;
    }

    /**
     * @return mixed
     */
    public function getFirstname()
    {
        return $this->firstname;
    }

    /**
     * @param mixed $lastname
     */
    public function setLastname($lastname)
    {
        $this->lastname = $lastname;
    }

    /**
     * @return mixed
     */
    public function getLastname()
    {
        return $this->lastname;
    }



    /**
     * @param mixed $address
     */
    public function setAddress($address)
    {
        $this->address = $address;
    }

    /**
     * @return mixed
     */
    public function getAddress()
    {
        return $this->address;
    }

    /**
     * @param mixed $city
     */
    public function setCity($city)
    {
        $this->city = $city;
    }

    /**
     * @return mixed
     */
    public function getCity()
    {
        return $this->city;
    }

    /**
     * @param mixed $country
     */
    public function setCountry($country)
    {
        $this->country = $country;
    }

    /**
     * @return mixed
     */
    public function getCountry()
    {
        return $this->country;
    }

    /**
     * @param int $id
     */
    public function setId($id)
    {
        $this->id = $id;
    }

    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @param mixed $zipCode
     */
    public function setZipCode($zipCode)
    {
        $this->zipCode = $zipCode;
    }

    /**
     * @return mixed
     */
    public function getZipCode()
    {
        return $this->zipCode;
    }

    /**
     * @param mixed $phone
     */
    public function setPhone($phone)
    {
        $this->phone = $phone;
    }

    /**
     * @return mixed
     */
    public function getPhone()
    {
        return $this->phone;
    }

}

Création et mise à jour du schéma de la base de données :


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

1.7 - Ajout des routes de FOSUser

nano app/config/routing.yml 

Ajouter en haut du fichier l'appel de routes de FOSUser :


# app/config/routing.yml
fos_user_security:
    resource: "@FOSUserBundle/Resources/config/routing/security.xml"

fos_user_profile:
    resource: "@FOSUserBundle/Resources/config/routing/profile.xml"
    prefix: /profile

fos_user_register:
    resource: "@FOSUserBundle/Resources/config/routing/registration.xml"
    prefix: /register

fos_user_resetting:
    resource: "@FOSUserBundle/Resources/config/routing/resetting.xml"
    prefix: /resetting

fos_user_change_password:
    resource: "@FOSUserBundle/Resources/config/routing/change_password.xml"
    prefix: /profile

1.8 - Configuration du fichier security.yml

nano app/config/security.yml 

# app/config/security.yml
security:
    encoders:
        FOS\UserBundle\Model\UserInterface: bcrypt

    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: ROLE_ADMIN

    providers:
        fos_userbundle:
            id: fos_user.user_provider.username

    firewalls:
        main:
            pattern: ^/
            form_login:
                provider: fos_userbundle
                csrf_token_generator: security.csrf.token_manager

            logout:       true
            anonymous:    true

    access_control:
        - { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/, role: ROLE_ADMIN }

1.9 - Héritage de FOSUserBundle pour le MainBundle

nano src/FosSf3/MainBundle/FosSf3MainBundle.php 

<?php

namespace FosSf3\MainBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class FosSf3MainBundle extends Bundle
{
    public function getParent()
    {
        return 'FOSUserBundle';
    }
}


2 - Implémentation du login back-office

2.1 - Création du fichier de traduction

nano app/Resources/FOSUserBundle/translations/FOSUserBundle.fr.yml 

# Profile
profile:
    fields:
        gender: Civilité
        firstname: Prénom
        lastname: Nom
        address: Adresse
        zip_code: Code postal
        phone: Tél.
        email: E-mail
        password_first: Mot de passe
        password_second: Mot de passe (validation)

# Register
register:
    fields:
        firstname: Nom

# Resetting account
resetting:
    email:
        subject: |
            Réinitialisation de votre mot de passe
        message:  |
            <p>Pour réinitialiser votre mot de passe, merci de cliquer sur le lien suivant :</p>
            <p><a href="%confirmationUrl%">%confirmationUrl%</a></p>

# Registration
registration:
    confirmed: Votre compte est maintenant activé.
    email:
        subject: |
            Confirmation de l'inscription
        message:  |
            <p>Pour valider votre compte utilisateur, merci de vous rendre sur :</p>
            <p><a href="%confirmationUrl%">%confirmationUrl%</a></p>

2.2 - Création du template de vue pour le back-office

nano app/Resources/views/back-office.html.twig 

{# add navigation current nav mark #}
{% set route = app.request.get('_route') %}

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>{% block title %}Back-Office{% endblock %}</title>
    <meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' name='viewport'>

</head>
<body>
<div class="container">

    {% block header %}{% endblock %}

    {% block flash %}

        {% for flashMessage in app.session.flashbag.get('notice') %}
            <div>
                {{ flashMessage }}
            </div>
        {% endfor %}

        {% for flashMessage in app.session.flashbag.get('error') %}
            <div>
                {{ flashMessage }}
            </div>
        {% endfor %}

    {% endblock %}

    <!-- container content - BEGIN -->
    <div class="container container-content">
        <div id="layout-view" class="view view-villa-all">
            {% block content %}{% endblock %}
        </div>
    </div>
    <!-- container content - END -->

    {% block footer %}{% endblock footer %}

</div>
</body>
</html>

2.3 - Création de la vue login

nano app/Resources/FOSUserBundle/views/Security/login.html.twig 

{% extends 'back-office.html.twig' %}

{% block content %}

    <div id="layout-view" class="view view-login">
        {% if error %}
            <div class="bg-danger">{{ error|trans({}, 'FOSUserBundle') }}</div>
        {% endif %}
        <div id="login-form" class="col-md-4 col-md-offset-4">

            <h1 class="text-center">Admin</h1>

            <form action="{{ path("fos_user_security_check") }}" method="post" class="center-block">

                <input type="hidden" name="_csrf_token" value="{{ csrf_token }}" />

                <div class="form-group">
                    <input class="form-control" type="text" id="username" name="_username" value="{{ last_username }}" required="required" placeholder="{{ 'email_address'|trans }}"/>
                </div>
                <div class="form-group">
                    <input class="form-control" type="password" id="password" name="_password" required="required" placeholder="{{ 'password'|trans }}"/>
                </div>
                <div class="form-group">
                    <input type="checkbox" id="remember_me" name="_remember_me" value="on" />
                    <label for="remember_me">{{ 'security.login.remember_me'|trans({}, 'FOSUserBundle') }}</label>
                </div>

                <input class="btn btn-primary btn-block" type="submit" id="_submit" name="_submit" value="{{ 'security.login.submit'|trans({}, 'FOSUserBundle') }}" />

            </form>

        </div>
    </div>
{% endblock %}

2.4 - Création du SecurityController

nano src/FosSf3/BackOfficeBundle/Controller/SecurityController.php 

<php

namespace FosSf3\BackOfficeBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class SecurityController extends Controller
{
    /**
     * Renders the login template with the given parameters. Overwrite this function in
     * an extended controller to provide additional data for the login template.
     *
     * @param array $data
     *
     * @return \Symfony\Component\HttpFoundation\Response
     */
    protected function renderLogin(array $data)
    {
        return $this->container->get('templating')->renderResponse('FosSf3BackOfficeBundle:Security:login.html.twig', $data);
    }
}

3 - Création d'un utilisateur en base de données

Création d'un utilisateur avec les droits super-admin :

  • login : admin
  • password : admin
  • email : mail@domain.tld
php bin/console fos:user:create admin mail@domain.tld admin --super-admin 

4 - Test du login

On se connecte l'url du back-office : http://fos-sf3.local/admin

On test le login, on constate avec le profiler qu'on est connecté en tant qu'admin.