Création d'un site avec back-office admin et espace mon compte sur Symfony 2.8 avec FOSUserBundle

Création d'un back-office et d'un espace mon compte sur Symfony 2 avec FOSUserBundle.

Publié le 20/07/2016

Ce tutorial vise à réaliser un site avec un back-office pour l'administration et un espace mon compte pour les utilisateurs. Les utilisateurs pourront s'inscrire au site et recevrons un mail de confirmation pour finaliser leur inscription. Pour ce faire nous allons Utiliser le Bundle FOSUserBundle. Ce dernier va nous fournir deux entités Utilisateur et Groupe avec un ensemble de méthodes permettant le login, enregistrement et reset du mot de passe utilisateur pour ne citer que celles que nous utiliserons.

Description : Création d'un back-office et d'un espace mon compte sur Symfony 2 avec FOSUserBundle. Keywords : développement, web, PHP, Symfony 2, FOS, FOSUserBundle

Partie 1 - Création de l'admin

1 - Installation de Symfony 2.8

sudo php composer.phar create-project symfony/framework-standard-edition /Users/gerard/Documents/htdocs/sf_web_app/ 2.8

Installation de composer.phar : curl -s https://getcomposer.org/installer | php

2 - Création des Bundles

On va créer 4 bundles : MainBundle, BackOfficeBundle, FrontOfficeBundle et CmsBundle.

MainBundle va contenir nos Entités, BackOfficeBundle sera dédié au BackOffice, FrontOfficeBundle sera dédié à l'espace mon compte pour les utilisateurs inscrits au site (front-office) et enfin CmsBundle sera dédié à la gestion de la partie publique du site.

Lors de la création des bundles, on choisira le yml pour le format des fichiers de configuration.


sudo php app/console generate:bundle --namespace=SfWebApp/MainBundle
sudo php app/console generate:bundle --namespace=SfWebApp/BackOfficeBundle
sudo php app/console generate:bundle --namespace=SfWebApp/FrontOfficeBundle
sudo php app/console generate:bundle --namespace=SfWebApp/CmsBundle

Vérifier que les Bundles soient bien enregistrés dans le kernel :

nano AppKernel.php


$bundles = array(
// ...
// app bundle
new SfWebApp\MainBundle\SfWebAppMainBundle(),
new SfWebApp\BackOfficeBundle\SfWebAppBackOfficeBundle(),
new SfWebApp\FrontOfficeBundle\SfWebAppFrontOfficeBundle(),
new SfWebApp\CmsBundle\SfWebAppCmsBundle(),
// ...
);

3 - Installation du vendor FOSUserBundle

On va ajouter à la section "require" de notre fichier /composer.json le bundle FOSUserBundle dans la section "require" :

"friendsofsymfony/user-bundle": "v1.3.6"

On lance un composer update :

sudo php composer.phar update

Ensuite on enregistre les bundles dans le fichier AppKernel en ajoutant dans le tableau $bundles, l'entrée suivante:


// user
new FOS\UserBundle\FOSUserBundle(),

Mettre à jour les droits sur les dossiers :

sudo chown -R "user" sf_sf_web_app sudo chmod -R 755 sf_sf_web_app sudo chmod -R 777 sf_sf_web_app/app/cache sf_sf_web_app/app/logs

4 - Configuration de du fichier de config de l'app :

On va configurer les directives du fichier config.yml de notre app en modifiant la section concernée comme suit :

nano app/config/config.yml


# FOS User Bundle
fos_user:
    db_driver: orm
    firewall_name: main
    user_class: SfWebApp\MainBundle\Entity\User
    service:
        mailer: fos_user.mailer.twig_swift
    registration:
        form:
            type: sf_web_app_fos_user_register
            validation_groups: [Register, Default, Registration]
        confirmation:
            enabled: true
            template: SfWebAppFrontOfficeBundle:Registration:email.html.twig
    profile:
        form:
            type: sf_web_app_fos_user_profile
            #validation_groups: [Profile, Default]
    resetting:
        email:
            template: SfWebAppFrontOfficeBundle:Resetting:email.html.twig
    group:
        group_class: SfWebApp\MainBundle\Entity\Group
    from_email:
        address: mail@domain.tld
        sender_name: name

Toujours dans le même fichier, on va activer le traducteur :


framework:
    translator: ~

Créer un dossier session vide avec un fichier gitkeep à la racine de app afin de pouvoir commiter un dossier de session vide sur le serveur. Ne pas oublier d'exclure le contenu du dossier dans le fichier ".gitignore".

mkdir app/sessions chmod -R 777 app/sessions

5 - Créations des entité User et Group

On va créer deux entités User et Group qui vont étendre les entités du bundle FOSUserBundle.

Créations de l'entité User :

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


<?php

namespace SfWebApp\MainBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use FOS\UserBundle\Entity\User as BaseUser;

/**
 * Class User
 * @package SfWebApp\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\ManyToMany(targetEntity="Group", inversedBy="users")
     * @ORM\JoinTable(name="users_groups",
     *      joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="group_id", referencedColumnName="id")}
     * )
     */
    protected $groups;

    /**
     * @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();
        $this->groups = new ArrayCollection();
    }

    /**
     * @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;
    }

    /**
     * @param $group
     * @return $this
     */
    public function addGoup($group)
    {
        $this->groups[] = $group;
        $group->setUser($this);

        return $this;
    }

    /**
     * @param $groups
     */
    public function setGroups($groups)
    {
        $this->groups->clear();
        foreach ($groups as $group) {
            $this->addGroup($group);
        }
    }

    /**
     * @return ArrayCollection
     */
    public function getGroups()
    {
        return $this->groups;
    }
}

Création de l'entité Group :

nano src/SfWebApp/MainBundle/Entity/Group.php


<?php

namespace SfWebApp\MainBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use FOS\UserBundle\Entity\Group as BaseGroup;

/**
 * Group
 *
 * @ORM\Table(name="fos_group")
 * @ORM\Entity
 */
class Group extends BaseGroup
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @var
     *
     * @ORM\ManyToMany(targetEntity="User", mappedBy="groups")
     */
    protected $users;

    /**
     * Constructor
     */
    public function __construct($name, $roles)
    {
        parent::__construct($name, $roles);
        $this->users = new ArrayCollection();
    }

    /**
     * @param $user
     * @return $this
     */
    public function addUser($user)
    {
        $this->users[] = $user;
        $user->setGroup($this);

        return $this;
    }

    /**
     * @param $users
     */
    public function setUsers($users)
    {
        $this->users->clear();
        foreach ($users as $user) {
            $this->addUser($user);
        }
    }

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

6 - Configuration de security.yml

On configure le fichier security.yml de façon à ce que le login soit pris en charge par FOSUserBundle. On va donc modifier les directives de notre fichier security.yml comme suit :


security:
    encoders:
        FOS\UserBundle\Model\UserInterface: sha512

    role_hierarchy:
        ROLE_USER:        [ROLE_USER]
        ROLE_ADMIN:       [ROLE_ADMIN]
        ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]

    providers:
        fos_userbundle:
            id: fos_user.user_provider.username_email

    firewalls:
        dev:
            pattern:  ^/(_(profiler|wdt)|css|images|js)/
            security: false

        # login area for backoffice users
        backoffice:
            context: primary_auth
            pattern:            ^/admin
            form_login:
                provider:       fos_userbundle
                login_path:     sf_web_app_back_office_security_login
                use_forward:    true
                use_referer:    true
                check_path:     sf_web_app_back_office_security_check
                failure_path:   sf_web_app_back_office_bundle_admin_index
                default_target_path: sf_web_app_back_office_bundle_admin_index
            logout:
                path: sf_web_app_back_office_security_logout
                target: sf_web_app_cms_bundle_homepage
            anonymous:    true

        # defaut login area for standard users
        main:
            context: primary_auth
            pattern:            ^/
            form_login:
                provider:       fos_userbundle
                login_path:     fos_user_security_login
                use_forward:    true
                use_referer:    true
                check_path:     fos_user_security_check
                #failure_path:   null
                default_target_path: fos_user_profile_show
            logout:
                path: fos_user_security_logout
                target: sf_web_app_cms_bundle_homepage
            anonymous:    true

    access_control:
        # back-office

        - { path: ^/admin/connexion, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin, roles: ROLE_ADMIN }
        - { path: ^/private-space, roles: ROLE_USER }
        # front-office
        - { path: ^/, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/connexion, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/inscription, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/mot-de-passe-oublie, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/profile, role: IS_AUTHENTICATED_FULLY }
        - { path: ^/deconnexion$, role: IS_AUTHENTICATED_FULLY }

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

Lancer les commandes suivantes :

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

8 - Nettoyage du bundle CmsBundle

Mise en place d'une route, action de controller et vue pour la home de notre site.

Dans le fichier routing.yml du bundle, ajouter la route suivante :

nano src/SfWebApp/CmsBundle/Ressources/config/routing.yml


sf_web_app_cms_homepage:
    path:     /
    defaults: { _controller: SfWebAppCmsBundle:Default:index }

Dans le DefaultController :

nano src/SfWebApp/CmsBundle/Controller/DefaultController.php


<?php

namespace SfWebApp\CmsBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class DefaultController extends Controller
{
    public function indexAction()
    {
        return $this->render('SfWebAppCmsBundle:Default:index.html.twig');
    }
}

Dans la vue index :

nano src/SfWebApp/CmsBundle/Resources/views/Default/index.html.twig

Hello !

A partir de ce moment, on va pouvoir charger la home de notre site dans l'environnement de dev : http://sf-web-app.local/app_dev.php

9 - Ajouts des routes

On ajoute les routes pour nos bundles dans le fichier de routing de l'app :

nano app/config/routing.yml


# FOS USER BUNDLE ROUTES
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: /change-password

# REGISTRATION
fos_user_registration_register:
    pattern: /inscription
    defaults: { _controller: FOSUserBundle:Registration:register }
    requirements:
        _method: GET|POST

fos_user_registration_check_email:
    pattern: /inscription/e-mail
    defaults: { _controller: FOSUserBundle:Registration:checkEmail }
    requirements:
        _method: GET

fos_user_registration_confirm:
    pattern: /inscription/confirmation/{token}
    defaults: { _controller: FOSUserBundle:Registration:confirm }
    requirements:
        _method: GET

fos_user_registration_confirmed:
    pattern: /inscription/confirmation
    defaults: { _controller: FOSUserBundle:Registration:confirmed }
    requirements:
        _method: GET

# RESETTING PASSWORD
fos_user_resetting_request:
    pattern: /mot-de-passe-oublie/reinitialisation
    defaults: { _controller: FOSUserBundle:Resetting:request }
    requirements:
        _method: GET

fos_user_resetting_send_email:
    pattern: /mot-de-passe-oublie/e-mail
    defaults: { _controller: FOSUserBundle:Resetting:sendEmail }
    requirements:
        _method: GET|POST

fos_user_resetting_check_email:
    pattern: /mot-de-passe-oublie/verification
    defaults: { _controller: FOSUserBundle:Resetting:checkEmail }
    requirements:
        _method: GET

fos_user_resetting_reset:
    pattern: /mot-de-passe-oublie/{token}
    defaults: { _controller: FOSUserBundle:Resetting:reset }
    requirements:
        _method: GET|POST

# APP BUNDLES
sf_web_app_cms:
    resource: « @SfWebAppCmsBundle/Resources/config/routing.yml"
    prefix:   /

sf_web_app_back_office:
    resource: « @SfWebAppBackOfficeBundle/Resources/config/routing.yml"
    prefix:   /admin

sf_web_app_front_office:
    resource: « @SfWebAppFrontOfficeBundle/Resources/config/routing.yml"
    prefix:   /private-space

On va pouvoir ajouter ensuite les routes des Bundles.

9.1 - Ajouts des routes du BackOfficeBundle

Création du fichier de routes rassemblant les includes :

nano app/src/SfWebApp/BackOfficeBundle/Resources/config/routing.yml


# SECURITY
sf_web_app_back_office_security:
    resource: "@SfWebAppBackOfficeBundle/Resources/config/routing/security.yml"

# BACKOFFICE
sf_web_app_back_office_bo:
    resource: "@SfWebAppBackOfficeBundle/Resources/config/routing/backoffice.yml"

Création du fichier de routes pour l'authentification :

nano app/src/SfWebApp/BackOfficeBundle/Resources/config/routing/security.yml


sf_web_app_back_office_security_login:
    path:     /login
    defaults: { _controller: SfWebAppBackOfficeBundle:Security:login }

sf_web_app_back_office_security_check:
    path:     /login-check
    defaults: { _controller: SfWebAppBackOfficeBundle:Security:check }
    requirements:
        _method: POST

sf_web_app_back_office_security_logout:
    path:     /logout
    defaults: { _controller: SfWebAppBackOfficeBundle:Security:logout }

Création du fichier de routes pour le back-office :

nano app/src/SfWebApp/BackOfficeBundle/Resources/config/routing/backoffice.yml


sf_web_app_backofficebundle_home:
    pattern: /
    defaults: { _controller: "SfWebAppBackOfficeBundle:Default:index" }
    requirements:
        _method: GET

9.2 - Ajouts des routes du FrontOfficeBundle

Création du fichier de routes pour le front-office :

nano app/src/SfWebApp/FrontOfficeBundle/Resources/config/routing.yml


sf_web_app_front_office_homepage:
    path:     /
    defaults: { _controller: SfWebAppFrontOfficeBundle:Default:index }

# SECURITY
sf_web_app_front_office_security:
    resource: « @SfWebAppFrontOfficeBundle/Resources/config/routing/security.yml"

9.3 - Ajouts des routes du Front

Création du fichier de routes pour le front :

nano app/src/SfWebApp/CmsBundle/Resources/config/routing.yml


sf_web_app_cms_homepage:
    path:     /
    defaults: { _controller: SfWebAppCmsBundle:Default:index }

9.3.1 - Surcharge routes Profile


mkdir src/SfWebApp/FrontOfficeBundle/Resources/config/routing
nano src/SfWebApp/FrontOfficeBundle/Resources/config/routing/security.yml


# PROFILE
fos_user_profile_show:
    pattern: /profile
    defaults: { _controller: FOSUserBundle:Profile:show }
    requirements:
        _method: GET

fos_user_profile_edit:
    pattern: /profile/editer
    defaults: { _controller: FoulquierInfoFrontOfficeBundle:Profile:edit }
    requirements:
      _method: GET|POST

10 - Création d'un utilisateur admin

Création d'un utilisateur admin avec le mot de passe admin.

php app/console fos:user:create admin admin@example.com admin --super-admin

11 - Création du controller Security et de la vue login

Dans le BackOfficeBundle, on va créer le controller chargé de l'authentification :

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


<?php
namespace SfWebApp\BackOfficeBundle\Controller;

use FOS\UserBundle\Controller\SecurityController as BaseSecurityController;

class SecurityController extends BaseSecurityController
{
    /**
     * 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('SfWebAppBackOfficeBundle:Security:login.html.twig', $data);
    }
} 

Création du template de base 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 Blog Test{% endblock %}</title>
    <meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' name='viewport'>
    {% block stylesheets %}

        {% stylesheets filter='cssrewrite'
        '@SfWebAppBackOfficeBundle/Resources/public/css/libs/bootstrap.min.css'
        '@SfWebAppBackOfficeBundle/Resources/public/css/libs/bootstrap-theme.min.css'
        '@SfWebAppBackOfficeBundle/Resources/public/css/libs/jquery-ui.min.css'
        '@SfWebAppBackOfficeBundle/Resources/public/css/libs/jquery-ui.structure.min.css'
        '@SfWebAppBackOfficeBundle/Resources/public/css/libs/backoffice.css' %}
        <link rel="stylesheet" href="{{ asset_url }}" type="text/css" />
        {% endstylesheets %}

    {% endblock %}
</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>

    {% block javascripts %}

        {% javascripts
        '@SfWebAppBackOfficeBundle/Resources/public/js/libs/jquery-1.10.1.min.js'
        '@SfWebAppBackOfficeBundle/Resources/public/js/libs/jquery-ui.min.js'
        '@SfWebAppBackOfficeBundle/Resources/public/js/libs/bootstrap.min.js' %}
        <script src="{{ asset_url }}"></script>
        {% endjavascripts %}
        
        <script>
            // get locale
            locale = "{{ app.request.locale }}";
            // get route
            route = "{{ route }}";
        </script>
    {% endblock %}

</body>
</html>

On prendra soin d'ajouter TwitterBootstrap et jQuery aux templates (FrontOffice, BackOffice et Cms). Pour cela on va créer dans chacun des dossiers css et js dans src/SfWebApp/BackOfficeBundle/Resources/public/, un dossier libs auquel on va ajouter les fichiers JS et CSS de TwitterBootstrap et jQuery.

http://getbootstrap.com

http://jquery.com

Création du template pour le login :

nano src/SfWebApp/BackOfficeBundle/Resources/views/layout-login.htm.twig


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

{% block header %}
{% endblock %}

{% block content %}
{% endblock %}

{% block footer %}
{% endblock %}

Création de la vue login :

nano src/SfWebApp/BackOfficeBundle/Resources/views/Security/login.htm.twig


{% extends 'SfWebAppBackOfficeBundle::layout-login.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">Blog Test</h1>

            <form action="{{ path("sf_web_app_back_office_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 %}

On génère les assets :

rm -rf app/cache/* php app/console assets:install --symlink php app/console assetic:dump

12 - Ajout des traductions

On va ajouter les traductions pour le login :


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

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:
    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>

13 - Accès à l'admin

On peut dès maintenant accès à notre admin par l'url suivante > http://sf-web-app.local/app_dev.php/backoffice/

Partie 2 - Création de l'espace utilisateur

1 - Création du controller DefaultController

nano src/SfWebApp/FrontOfficeBundle/Controller/DefaultController.php


<?php

namespace SfWebApp\FrontOfficeBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class DefaultController extends Controller
{
    public function indexAction()
    {
        return $this->render('SfWebAppFrontOfficeBundle:Default:index.html.twig');
    }
}

2 - Création de la vue index

nano src/SfWebApp/FrontOfficeBundle/Resources/Default/index.html.twig


{% extends 'SfWebAppFrontOfficeBundle::layout.html.twig' %}

{% block content %}

    <div class="row">
        <div class="col-md-12">
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h2 class="panel-title">{{ 'dashboard'|trans }}</h2>
                </div>
                <div class="panel-body">
                </div>
            </div>
        </div>
    </div>
{% endblock %}

Partie 3 - Création du login et inscription espace utilisateur

 

1 - SfWebAppFrontOfficeBundle hérite de FOSUserBundle

nano src/SfWebApp/FrontOfficeBundle/SfWebAppFrontOfficeBundle.php


<?php

namespace SfWebApp\FrontOfficeBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

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

2 - Création des formulaires d'inscription et de modifications des informations utilisateurs

2.1 - Formulaire information utilisateur

nano src/SfWebApp/FrontOfficeBundle/Form/Type/ProfileType.php


<?php

namespace SfWebApp\FrontOfficeBundle\Form\Type;

use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use FOS\UserBundle\Form\Type\ProfileFormType;

class ProfileType extends ProfileFormType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('gender', 'choice', array(
                'label' => 'profile.fields.gender',
                'translation_domain' => 'forms',
                'choices' => array(
                    'm' => 'M.',
                    'f' => 'Mme'
                ),
                'required' => false
            ))
            ->add('firstname', 'text', array(
                'label' => 'profile.fields.firstname',
                'translation_domain' => 'forms',
                'required' => false
            ))
            ->add('lastname', 'text', array(
                'label' => 'profile.fields.lastname',
                'translation_domain' => 'forms',
                'required' => false
            ))
            ->add('address', 'textarea', array(
                'label' => 'profile.fields.address',
                'translation_domain' => 'forms',
                'required' => false
            ))
            ->add('zip_code', 'text', array(
                'label' => 'profile.fields.zip_code',
                'translation_domain' => 'forms',
                'required' => false
            ))
            ->add('city', 'text', array(
                'label' => 'profile.fields.city',
                'translation_domain' => 'forms',
                'required' => false
            ))
            ->add('country', 'text', array(
                'label' => 'profile.fields.country',
                'translation_domain' => 'forms',
                'required' => false
            ))
            ->add('phone', 'text', array(
                'label' => 'profile.fields.phone',
                'translation_domain' => 'forms',
                'required' => false
            ))
            ->add('email', 'email', array(
                'label' => 'profile.fields.email',
                'translation_domain' => 'forms',
                'required' => false
            ))
            ->add('plainPassword', 'repeated', array(
                'first_options'  => array(
                    'label' => 'profile.fields.password_first',
                    'required' => false
                ),
                'second_options' => array(
                    'label' => 'profile.fields.password_second'
                ),
                'required' => false,
                'translation_domain' => 'forms',
                'required' => false
            ))
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'validation_groups' => array('Default', 'Account'),
            'data_class' => 'SfWebApp\MainBundle\Entity\User'
        ));
    }

    public function getName()
    {
        return ‘sf_web_app_fos_user_profile';
    }
}

3.2 - Formulaire inscription utilisateur

nano src/SfWebApp/FrontOfficeBundle/Form/Type/RegisterType.php


<?php

namespace SfWebApp\FrontOfficeBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use FOS\UserBundle\Form\Type\RegistrationFormType;

class RegisterType extends RegistrationFormType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        parent::buildForm($builder, $options);

        $builder
            ->add('gender', 'text', array(
                'label' => 'profile.fields.gender',
                'translation_domain' => 'forms'
            ))
            ->add('firstname', 'text', array(
                'label' => 'profile.fields.firstname',
                'translation_domain' => 'forms'
            ))
            ->add('lastname', 'text', array(
                'label' => 'profile.fields.lastname',
                'translation_domain' => 'forms'
            ))
            ->add('address', 'text', array(
                'label' => 'profile.fields.address',
                'translation_domain' => 'forms'
            ))
            ->add('zip_code', 'text', array(
                'label' => 'profile.fields.zip_code',
                'translation_domain' => 'forms'
            ))
            ->add('city', 'text', array(
                'label' => 'profile.fields.city',
                'translation_domain' => 'forms'
            ))
            ->add('country', 'text', array(
                'label' => 'profile.fields.country',
                'translation_domain' => 'forms'
            ))
            ->add('phone', 'text', array(
                'label' => 'profile.fields.phone',
                'translation_domain' => 'forms'
            ))
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => ’SfWebApp\MainBundle\Entity\User'
        ));
    }

    public function getName()
    {
        return ‘sf_web_app_fos_user_register';
    }
}

3.3 - Enregistrement des formulaires en tant que services

nano src/SfWebApp/FrontOfficeBundle/Resources/config/services.yml


services:
    user.form.profile.type:
        class: SfWebApp\FrontOfficeBundle\Form\Type\ProfileType
        parent: fos_user.profile.form.type
        tags:
            - { name: form.type, alias: sf_web_app_fos_user_profile }

    user.form.register.type:
        class: SfWebApp\FrontOfficeBundle\Form\Type\RegisterType
        parent: fos_user.registration.form.type
        tags:
            - { name: form.type, alias: sf_web_app_fos_user_register }

3.4 - Création des vues profile

- Création de la vue profile show :

nano src/SfWebApp/FrontOfficeBundle/Resources/views/Profile/show.html.twig


{% extends 'SfWebAppFrontOfficeBundle::layout.html.twig' %}

{% block content %}
    <div class="row">
        <div class="col-md-12">
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h2 class="panel-title">{{ 'profile'|trans }}</h2>
                </div>
                <div class="panel-body">
                    <p>Nom d'utilisateur : {{ app.user.username }}</p>
                    <p>Prénom : {{ app.user.firstname }}</p>
                    <p>Nom : {{ app.user.lastname }}</p>
                    <p>CP : {{ app.user.zipCode }}</p>
                    <p>Ville : {{ app.user.city }}</p>
                    <p>Pays : {{ app.user.country }}</p>
                    <p>Tel. : {{ app.user.phone }}</p>
                    <p>Adresse e-mail : {{ app.user.email }}</p>
                    <a href="{{ path('fos_user_profile_edit') }}" class="btn btn-primary">{{ 'edit' }}</a>
                </div>
            </div>
        </div>
    </div>
{% endblock %}

- Création de la vue profile edit :

nano src/SfWebApp/FrontOfficeBundle/Resources/views/Profile/edit.html.twig


{% extends 'SfWebAppFrontOfficeBundle::layout.html.twig' %}

{% block content %}
    <div class="row">
        <div class="col-md-12">
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h2 class="panel-title">{{ 'user_infos'|trans }}</h2>
                </div>
                <div class="panel-body">
                    {% form_theme form 'bootstrap_3_layout.html.twig' %}
                    {{ form_start(form) }}
                    {{ form_row(form.gender) }}
                    {{ form_row(form.firstname) }}
                    {{ form_row(form.lastname) }}
                    {{ form_row(form.email) }}
                    {{ form_row(form.phone) }}
                    {{ form_row(form.address) }}
                    {{ form_row(form.zip_code) }}
                    {{ form_row(form.city) }}
                    {{ form_row(form.country) }}
                    {{ form_row(form.plainPassword.first) }}
                    {{ form_row(form.plainPassword.second) }}
                    <button type="submit" value="Mettre à jour" class="btn btn-success"/>{{ 'save'|trans }}</button>
                    {{ form_end(form) }}
                </div>
            </div>
        </div>
    </div>
{% endblock %}

3.5 - Création des vues registration

- Création de la vue register :

nano src/SfWebApp/FrontOfficeBundle/Resources/views/Registration/register.html.twig


{% extends 'SfWebAppFrontOfficeBundle::layout.html.twig' %}

{% block title %}Register{% endblock %}

{% block content %}
    <div class="row">
        <div class="col-md-12">
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h2 class="panel-title">{{ 'title_user_register'|trans }}</h2>
                </div>
                <div class="panel-body">
                    {% form_theme form 'bootstrap_3_layout.html.twig' %}
                    {{ form_start(form) }}
                    {{ form_row(form.gender) }}
                    {{ form_row(form.username) }}
                    {{ form_row(form.firstname) }}
                    {{ form_row(form.lastname) }}
                    {{ form_row(form.email) }}
                    {{ form_row(form.phone) }}
                    {{ form_row(form.address) }}
                    {{ form_row(form.zip_code) }}
                    {{ form_row(form.city) }}
                    {{ form_row(form.country) }}
                    {{ form_row(form.plainPassword.first) }}
                    {{ form_row(form.plainPassword.second) }}
                    <button type="submit" value="{{ 'registration.submit'|trans({}, 'FOSUserBundle') }}" class="btn btn-success"/>{{ 'register'|trans }}</button>
                    {{ form_end(form) }}
                </div>
            </div>
        </div>
    </div>
{% endblock %}

- Création de la vue email :

nano src/SfWebApp/FrontOfficeBundle/Resources/views/Registration/email.html.twig


{% block subject %}
    {% autoescape false %}
    {{ 'registration.email.subject'|trans({'%username%': user.username, '%confirmationUrl%': confirmationUrl}, 'FOSUserBundle') }}
    {% endautoescape %}
{% endblock %}

{% block body_text %}
    {% autoescape false %}
    {{ 'registration.email.message'|trans({'%username%': user.username, '%confirmationUrl%': confirmationUrl}, 'FOSUserBundle') }}
    {% endautoescape %}
{% endblock %}

{% block body_html %}
    {% include 'SfWebAppFrontOfficeBundle:Registration:inc/email.html.twig' %}
{% endblock %}

- Création de la vue confirmed :

nano src/SfWebApp/FrontOfficeBundle/Resources/views/Registration/confirmed.html.twig


{% extends 'SfWebAppFrontOfficeBundle::layout.html.twig' %}

{% block content %}
    <div class="row">
        <div class="col-md-12">
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h2 class="panel-title">{{ 'title_user_register'|trans }}</h2>
                </div>
                <div class="panel-body">
                    <p>{{ 'registration.confirmed'|trans({'%username%': user.username}, 'FOSUserBundle') }}</p>
                    {% if app.session is not empty %}
                        {% set targetUrl = app.session.get('_security.' ~ app.security.token.providerKey ~ '.target_path') %}
                        {% if targetUrl is not empty %}<p><a href="{{ targetUrl }}">{{ 'registration.back'|trans({}, 'FOSUserBundle') }}</a></p>{% endif %}
                    {% endif %}
                </div>
            </div>
        </div>
    </div>

{% endblock %}

- Création de la vue checkEmail :

nano src/SfWebApp/FrontOfficeBundle/Resources/views/Registration/checkEmail.html.twig


{% extends 'SfWebAppFrontOfficeBundle::layout.html.twig' %}

{% block content %}
    <div class="row">
        <div class="col-md-12">
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h2 class="panel-title">{{ 'check_email'|trans }}</h2>
                </div>
                <div class="panel-body">
                    <p>{{ 'registration.check_email'|trans({'%email%': user.email}, 'FOSUserBundle') }}</p>
                </div>
            </div>
        </div>
    </div>
{% endblock %}

- Création de l'include email :

nano src/SfWebApp/FrontOfficeBundle/Resources/views/Registration/inc/email.html.twig


<html>
<head>
    <style type="text/CSS">
        /* for Yahoo Beta, AOL */
        body, #body_style {background: #ffffff; min-height: 200px; color: #fff; font-family: Arial, Helvetica, sans-serif; font-size: 12px;}

        /* for Hotmail */
        .ExternalClass {width: 100%;}

        /* for Yahoo Classic and New */
        .yshortcuts {color: #F00;}

        /* some css reset for Gmail, Hotmail */
        p {margin: 0; padding: 0; margin-bottom: 0;}
        a, a:link, a:visited {color: #2A5DB0;}
    </style>
</head>

{# for Gmail, Lotus Notes 6.5 and 7, AOL #}
<body style="background: #ffffff; min-height: 500px; color: #000; font-family: Arial, Helvetica, sans-serif; font-size: 13px" alink="#FF0000" link="#FF0000" bgcolor="#f5f5f5" text="#000000">
{# for AOL #}
<span id="body_style" style="display: block">
        <table bgcolor="#ffffff" width="600" align="center">
            <tr>
                <td>
                    <table width="600" border="0" align="center" cellpadding="0" cellspacing="0" bgcolor="#ffffff">
                        <tr>
                            <td></td>
                        </tr>
                        <tr style="height: 20px;"></tr>
                    </table>

                    <!-- CONTENT - BEGIN -->
                    <table width="600" border="0" align="center" cellpadding="0" cellspacing="0" bgcolor="#ffffff">
                        <tr>
                            <td style="color: #000000; font-family: Tahoma, Arial, sans-serif; font-size: 10pt; width: 600px; padding: 0px;">
                                <div style="margin-left: 10px; margin-right: 10px;">
                                    <p>Confirmation inscription</p>
                                    <p style="text-align: left;">{{ 'registration.email.message'|trans({'%username%': user.username, '%confirmationUrl%': confirmationUrl}, 'FOSUserBundle')|raw }}</p>
                                </div>

                            </td>
                        </tr>
                        <tr style="height: 40px;"></tr>
                    </table>
                    <!-- CONTENT - END -->

                    
                </td>
            </tr>
        </table>
    </span>
</body>
</html>

3.6 - Création des vues resetting

- Création de la vue reset :

nano src/SfWebApp/FrontOfficeBundle/Resources/views/Resetting/reset.html.twig


{% extends 'SfWebAppFrontOfficeBundle::layout.html.twig' %}

{% block content %}
    <div class="row">
        <div class="col-md-12">
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h2 class="panel-title">{{ 'reset_password'|trans }}</h2>
                </div>
                <div class="panel-body">
                    {% form_theme form 'bootstrap_3_layout.html.twig' %}
                    {{ form_start(form) }}
                    {{ form_row(form.new.first) }}
                    {{ form_row(form.new.second) }}
                    <button type="submit" class="btn btn-primary" value="{{ 'resetting.reset.submit'|trans({}, 'FOSUserBundle') }}" />{{ 'reset_password'|trans }}</button>
                    {{ form_end(form) }}
                </div>
            </div>
        </div>
    </div>
{% endblock %}

- Création de la vue request :

nano src/SfWebApp/FrontOfficeBundle/Resources/views/Resetting/request.html.twig


{% extends 'SfWebAppFrontOfficeBundle::layout.html.twig' %}

{% block content %}
    <div class="row" >
        <div class="col-md-12">
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h2 class="panel-title">{{ 'reset_password'|trans }}</h2>
                </div>
                <div class="panel-body">
                    {% if invalid_username is defined %}
                        <p>{{ 'resetting.request.invalid_username'|trans({'%username%': invalid_username}, 'FOSUserBundle') }}</p>
                    {% endif %}
                    <form action="{{ path('fos_user_resetting_send_email') }}" method="POST" class="fos_user_resetting_request">
                        <div class="form-group">
                            <label for="username">{{ 'resetting.request.username'|trans({}, 'FOSUserBundle') }}</label>
                            <input type="text" id="username" class="form-control" name="username" required="required" />
                        </div>
                        <button type="submit" class="btn btn-primary" value="{{ 'resetting.request.submit'|trans({}, 'FOSUserBundle') }}" />{{ 'reset_password'|trans }}</button>
                    </form>
                </div>
            </div>
        </div>
    </div>
{% endblock %}

- Création de la vue passwordAlreadyRequested :

nano src/SfWebApp/FrontOfficeBundle/Resources/views/Resetting/passwordAlreadyRequested.html.twig


{% extends 'SfWebAppFrontOfficeBundle::layout.html.twig' %}

{% block content %}
    <div class="row"></div>
    <p>{{ 'resetting.password_already_requested'|trans({}, 'FOSUserBundle') }}</p>
{% endblock %}

- Création de la vue email :

nano src/SfWebApp/FrontOfficeBundle/Resources/views/Resetting/email.html.twig


{% block subject %}
    {% autoescape false %}
    {{ 'resetting.email.subject'|trans({'%username%': user.username, '%confirmationUrl%': confirmationUrl}, 'FOSUserBundle') }}
    {% endautoescape %}
{% endblock %}

{% block body_text %}
    {% autoescape false %}
    {{ 'resetting.email.message'|trans({'%username%': user.username, '%confirmationUrl%': confirmationUrl}, 'FOSUserBundle') }}
    {% endautoescape %}
{% endblock %}

{% block body_html %}
    {% include 'SfWebAppFrontOfficeBundle:Resetting:inc/email.html.twig' %}
{% endblock %}

- Création de la vue checkEmail :

nano src/SfWebApp/FrontOfficeBundle/Resources/views/Resetting/checkEmail.html.twig


{% extends 'SfWebAppFrontOfficeBundle::layout.html.twig' %}

{% block content %}
    <div class="row">
        <div class="col-md-12">
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h2 class="panel-title">{{ 'reset_password'|trans }}</h2>
                </div>
                <div class="panel-body">
                    <p>{{ 'resetting.check_email'|trans({'%email%': email}, 'FOSUserBundle') }}</p>
                </div>
            </div>
        </div>
    </div>
{% endblock %}

- Création de l'include email :

nano src/SfWebApp/FrontOfficeBundle/Resources/views/Resetting/inc/email.html.twig


<html>
<head>
    <style type="text/CSS">
        /* for Yahoo Beta, AOL */
        body, #body_style {background: #ffffff; min-height: 200px; color: #fff; font-family: Arial, Helvetica, sans-serif; font-size: 12px;}

        /* for Hotmail */
        .ExternalClass {width: 100%;}

        /* for Yahoo Classic and New */
        .yshortcuts {color: #F00;}

        /* some css reset for Gmail, Hotmail */
        p {margin: 0; padding: 0; margin-bottom: 0;}
        a, a:link, a:visited {color: #2A5DB0;}
    </style>
</head>

{# for Gmail, Lotus Notes 6.5 and 7, AOL #}
<body style="background: #ffffff; min-height: 500px; color: #000; font-family: Arial, Helvetica, sans-serif; font-size: 13px" alink="#FF0000" link="#FF0000" bgcolor="#f5f5f5" text="#000000">
{# for AOL #}
<span id="body_style" style="display: block">
        <table bgcolor="#ffffff" width="600" align="center">
            <!-- DI - BEGIN -->
            <tr>
                <td>
                    <table width="600" border="0" align="center" cellpadding="0" cellspacing="0" bgcolor="#ffffff">
                        <tr>
                            <td></td>
                        </tr>
                        <tr style="height: 20px;"></tr>
                    </table>
                    <!-- CONTENT - BEGIN -->
                    <table width="600" border="0" align="center" cellpadding="0" cellspacing="0" bgcolor="#ffffff">
                        <tr>
                            <td style="color: #000000; font-family: Tahoma, Arial, sans-serif; font-size: 10pt; width: 600px; padding: 0px;">
                                <div align="left">
                                    <p>{{ 'resetting.email.message'|trans({'%username%': user.username, '%confirmationUrl%': confirmationUrl}, 'FOSUserBundle')|raw }}</p>
                                </div>
                            </td>
                        </tr>
                        <tr style="height: 40px;"></tr>
                    </table>
                    <!-- CONTENT - END -->
                </td>
            </tr>
            <!-- DI - END -->
        </table>
    </span>
</body>
</html>

3.7 - Création la vue login et des templates du bundle

- Création de la vue login :

nano src/SfWebApp/FrontOfficeBundle/Resources/views/Security/login.html.twig


{% extends 'SfWebAppFrontOfficeBundle::layout-login.html.twig' %}

{% block content %}
    <div id="layout-view" class="view view-login">
        {% if error %}
            <div class="bg-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
        {% endif %}
        <div id="login-form" class="col-md-4 col-md-offset-4">
            <div class="columns clearfix">
                <div class="column-01">
                    
                </div>
                <div class="column-02">
                    <h1></h1>
                    <p>Espace client</p>
                </div>
            </div>
            <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 type="text" class="form-control" id="username" name="_username" value="{{ last_username }}" required="required"/>
                </div>
                <div class="form-group">
                    <input type="password" class="form-control" id="password" name="_password" required="required"/>
                </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>
                <div class="form-group">
                    <a href="{{ path('fos_user_resetting_request') }}">
                        <strong>{{ 'reset_password'|trans }}</strong>
                    </a>
                </div>
                <input class="btn btn-primary btn-block" type="submit" id="_submit" name="_submit" value="{{ 'security.login.submit'|trans({}, 'FOSUserBundle') }}" />
            </form>
        </div>
    </div>

{% endblock %}

- Création du layout login :

nano src/SfWebApp/FrontOfficeBundle/Resources/views/layout-login.html.twig


{% extends '::front-office.html.twig' %}

{% block header %}
{% endblock %}

{% block content %}
{% endblock %}

{% block footer %}
{% endblock %}

- Création du layout de FrontOfficeBunble :

nano src/SfWebApp/FrontOfficeBundle/Resources/views/layout.html.twig


{% extends '::front-office.html.twig' %}

{% block header %}

    <nav class="navbar navbar-default main-nav">
        <div class="container-fluid">
            <!-- Brand and toggle get grouped for better mobile display -->
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>

            </div>

            <!-- Collect the nav links, forms, and other content for toggling -->

            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                {% if is_granted('IS_AUTHENTICATED_FULLY') %}
                    <ul class="nav navbar-nav">

                    </ul>
                    <ul class="nav navbar-nav navbar-right">
                        <li class="dropdown">
                            <a class="dropdown-toggle" href="" data-toggle="dropdown">{{ app.user.firstname }} {{ app.user.lastname }} <b class="caret"></b></a>
                            <ul class="dropdown-menu">
                                <li>
                                    <a href="{{ path('fos_user_profile_show') }}">{{ 'my_account'|trans }}</a>
                                </li>
                                <li>
                                    <a href="{{ path('fos_user_security_logout') }}">{{ 'logout'|trans }}</a>
                                </li>

                            </ul>
                        </li>
                    </ul>
                {% else %}
                    <ul class="nav navbar-nav">
                        <li>
                            <a href="">{{ 'sf_web_app'|trans }}</a>
                        </li>
                    </ul>
                {% endif %}
            </div><!-- /.navbar-collapse -->

        </div><!-- /.container-fluid -->
    </nav>

{% endblock %}

{% block content %}{% endblock %}

{% block footer %}{% endblock %}

- Création du template front-office :

nano app/Resources/views/front-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 %}Front-Office D&i{% endblock %}</title>
    <meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' name='viewport'>
    {% block stylesheets %}
        {% stylesheets filter='cssrewrite'
        '@SfWebAppFrontOfficeBundle/Resources/public/css/libs/bootstrap.min.css'
        '@SfWebAppFrontOfficeBundle/Resources/public/css/libs/bootstrap-theme.min.css'
        '@SfWebAppFrontOfficeBundle/Resources/public/css/libs/jquery-ui.min.css'
        '@SfWebAppFrontOfficeBundle/Resources/public/css/libs/jquery-ui.structure.min.css'
        '@SfWebAppFrontOfficeBundle/Resources/public/css/libs/jquery.dataTables.min.css'
        '@SfWebAppFrontOfficeBundle/Resources/public/css/front-office.css' %}
        <link rel="stylesheet" href="{{ asset_url }}" type="text/css" />
        {% endstylesheets %}
    {% endblock %}
</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 %}
    {% block content %}{% endblock %}
    {% block footer %}{% endblock footer %}
</div>
{% block javascripts %}
    {% javascripts
    '@SfWebAppFrontOfficeBundle/Resources/public/js/libs/jquery-1.10.1.min.js'
    '@SfWebAppFrontOfficeBundle/Resources/public/js/libs/jquery-ui.min.js'
    '@SfWebAppFrontOfficeBundle/Resources/public/js/libs/bootstrap.min.js'
    '@SfWebAppFrontOfficeBundle/Resources/public/js/libs/jquery.dataTables.min.js'
    '@SfWebAppFrontOfficeBundle/Resources/public/js/datatables-fo.js' %}
    <script src="{{ asset_url }}"></script>
    {% endjavascripts %}
    <script>
        // get locale
        locale = "{{ app.request.locale }}";
        // get route
        route = "{{ route }}";
    </script>
{% endblock %}

</body>
</html>

3.8 - Ajout des traductions

Formulaires FOSUserBundle :

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


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

register:
    fields:
        firstname: Nom

FOSUserBundle :

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


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:
    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>

Validators :

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


valid_phone_number: Cette valeur n'est pas un numéro de téléphone valide.
"'{{ value }}' is not a valid email.": "'{{ value }}' n'est pas une adresse e-mail valide."
not_empty_field: Le champs ne peut être vide.

4 - Configuration de l'envoi d'email

nano app/config/parameters.yml

Modifier les champs suivantes :


mailer_transport:  smtp
    mailer_host:       ssl0.ovh.net
    mailer_user:       "mail@ovh.com"
    mailer_password:   "pwd"

5 - Surcharge du ProflieController de FOSUserBundle

Suite à un problème de validation sur editProfile(), j’ai surchargé le controller de FOSUserBundle :

MAJ : Le problème de validation à été corrigé avec la version 1.3.6 de FOSUserBundle, la surcharge des contrôleurs de FOSUserBundle n'est pas nécessaire.

nano src/SfWebApp/FrontOfficeBundle/Controller/ProfileController.php


<?php

namespace SfWebApp\FrontOfficeBundle\Controller;

use FOS\UserBundle\Controller\ProfileController as BaseController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use FOS\UserBundle\Model\UserInterface;

class ProfileController extends BaseController
{

    protected $container;

    /**
     * @return ContainerInterface
     */
    public function getContainer()
    {
        return $this->container;
    }

    /**
     * Show the user
     */
    public function showAction()
    {
        $user = $this->container->get('security.context')->getToken()->getUser();
        if (!is_object($user) || !$user instanceof UserInterface) {
            throw new AccessDeniedException('This user does not have access to this section.');
        }

        return $this->container->get('templating')->renderResponse('FOSUserBundle:Profile:show.html.'.$this->container->getParameter('fos_user.template.engine'), array('user' => $user));
    }

    /**
     * Edit the user
     */
    public function editAction()
    {
        $user = $this->container->get('security.context')->getToken()->getUser();
        if (!is_object($user) || !$user instanceof UserInterface) {
            throw new AccessDeniedException('This user does not have access to this section.');
        }

        $request = $this->container->get('request');

        $form = $this->container->get('fos_user.profile.form');

        $data = $request->request->get('fos_user_profile_form');
        $userId = $user->getId();

        if ($request->getMethod() == 'POST') {

            $em = $this->getContainer()->get('doctrine.orm.entity_manager');
            $user = $em->getRepository(‘SfWebAppMainBundle:User')->find($userId);

            $user->setGender($data['gender']);
            $user->setFirstname($data['firstname']);
            $user->setLastname($data['lastname']);
            $user->setEmail($data['email']);
            $user->setEmailCanonical($data['email']);
            $user->setPhone($data['phone']);
            $user->setAddress($data['address']);
            $user->setZipCode($data['zip_code']);
            $user->setCity($data['city']);
            $user->setCountry($data['country']);
            $user->setPlainPassword($data['plainPassword']['first']);

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

            $this->setFlash('fos_user_success', 'profile.flash.updated');

            return new RedirectResponse($this->getRedirectionUrl($user));
        }

        return $this->container->get('templating')->renderResponse(
            'FOSUserBundle:Profile:edit.html.'.$this->container->getParameter('fos_user.template.engine'),
            array('form' => $form->createView())
        );
    }

    /**
     * Generate the redirection url when editing is completed.
     *
     * @param \FOS\UserBundle\Model\UserInterface $user
     *
     * @return string
     */
    protected function getRedirectionUrl(UserInterface $user)
    {
        return $this->container->get('router')->generate('fos_user_profile_show');
    }

    /**
     * @param string $action
     * @param string $value
     */
    protected function setFlash($action, $value)
    {
        $this->container->get('session')->getFlashBag()->set($action, $value);
    }
}