Créer un projet Symfony 7

Initialiser le projet

Installation des prérequis

Tout d’abord, il faut une version de PHP installée sur le poste, ou bien passer par Docker.

Il faut également avoir installé Composer sur le poste.

Ensuite, il faut Installer scoop pour pouvoir installer Symfony CLI. Il s’agit d’un utilitaire en ligne de commande faisant intermédiaire pour utiliser Composer.

# Ouvrir PowerShell puis saisir les commandes :

# Installer Scoop
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression

# Installer Symfony CLI
scoop install symfony-cli

# Confirmer l'installation
symfony version

# Vérifier les prérequis
symfony check:requirements

Initialiser un projet Symfony

  • Se rendre dans le répertoire qui contient les sites. Exemple : cd C:\Users\Fabien\Sites\
  • Créer un projet Symfony, commandes possibles :
# Composants pour un microservice, une application "console" ou bien une API
symfony new <nom_répertoire_projet>

# Ajout de tous les composants nécessaires à une application web (avec interface)
symfony new --webapp <nom_répertoire_projet>

# Préciser la version de Symfony à Utiliser
symfony new --webapp --version=7.2 <nom_répertoire_projet>

En ayant créer un vhost Apache qui pointe vers le répertoire /public du projet, il devrait dès maintenant possible d’accéder au site depuis un navigateur. Une page par défaut de présentation de Symfony devrait s’afficher, avec la barre de débogage en bas de page. Si la debugbar ne s’affiche pas, il faut ajouter un fichier .htaccess dans le répertoire /public avec le contenu minimum suivant :

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>

La console Symfony

Depuis le terminal, en étant dans le répertoire du projet on peut exécuter une commande permettant de lister toutes les commandes Symfony disponibles :

# A exécuter depuis le répertoire du projet pour lister toutes les commandes Symfony disponibles :
symfony console list

# Permet de lister les commandes d'une section en particulier, ici, la secion "cache" :
symfony console list cache

Créer une route, premier contrôleur

On peut créer un contrôleur via l’IDE, ou bien grâce à la commande : symfony console make:controller. Cette commande créée également un template Twig dans un sous-répertoire au nom du contrôleur au sein du dossier templates.

On peut par exemple créer un contrôleur nommé DefaultController avec la route / nommée app_default.

Template par défaut, CSS (via Bootstrap) et Javascript

Le template base.html.twig constitue le socle commun à toutes les futures pages du site qui pourront donc l’étendre.

Le CSS par défaut se situe dans le fichier assets/styles/app.css. Il est chargé par le fichier assets/app.js.

Découverte de Symfony AssetMapper et des ImportMaps

Des fichiers CSS et Javascript peuvent être gérés grâce au composant AssetMapper de Symfony. Il repose sur les ImportMaps disponibles nativement en HTML. Ils permettent d’intégrer facilement des bibliothèques Javascript sans devoir passer par des outils lourds et complexes tels que Webpack. Une simple commande pour intégrer la bibliothèque suffit. AssetMapper est automatiquement présent si Symfony a été installé avec l’option --webapp.

Commandes Symfony disponibles :

# Lister les dépendances non à jour et la dernière version disponible
php bin/console importmap:outdated

# Mettre à jour toutes les dépendances
php bin/console importmap:update
  
# Idem, mais uniquement pour une dépendance en particulier (ici, Bootstrap)
php bin/console importmap:outdated bootstrap lodash
php bin/console importmap:update bootstrap lodash

Installation de Bootstrap et FontAwesome

Comme évoqué ci-dessus, une simple commande suffit.

symfony console importmap:require bootstrap

php bin/console importmap:require @fortawesome/fontawesome-free/css/all.css

Il faut ensuite compléter le fichier assets/app.js en ajoutant :

  • import './vendor/bootstrap/dist/css/bootstrap.min.css';
  • import './vendor/@fortawesome/fontawesome-free/css/all.css';

Utiliser une base de données grâce à Doctrine

Présentation de Doctrine

Doctrine est l’outil intégré à Symfony qui permet de se connecter à une BDD et d’échanger avec via PHP. Doctrine peut s’utiliser dans un projet PHP ne reposant par sur Symfony. Il permet par ailleurs d’utiliser aussi bien une BDD MySQL, que PostgreSQL, MariaDB, NoSQL…

Doctrine met notamment à disposition DBAL et ORM :

  • DBAL : signifiant DataBase Abstraction Layer, il s’agit d’une couche d’accès aux données. Cela s’apparente à PDO dont DBAL facilite l’utilisation.
  • ORM : signifiant Object Relational Mapper, permet de faire le lien entre la BDD et les classes PHP. Permet une sorte de correspondance entre les tables SQL et les classes PHP.

Paramétrer l’accès à la BDD

Le fichier .env à la racine du projet permet de configurer le DSN, Database Source Name, dans la variable d’environnement DATABASE_URL.

Exemple pour MySQL :

DATABASE_URL="mysql://root:H1d7djue0a6l@127.0.0.1:3306/ma-bdd?serverVersion=8.0.31&charset=utf8mb4"

Créer une entité et définir ses propriétés

  • On utilise la commande : symfony console make:entity Movie.
  • Chaque entité dispose d’une propriété id, car c’est obligatoire avec Doctrine.
  • Chaque propriété est créée avec son type, sont accesseur et son mutateur (méthodes get et set). Exception pour la propriété id qui n’a pas de méthode set.
  • Les propriétés non nullables sont bien créées NOT NULL dans la BDD mais sont nullable dans la classe PHP. Cela date de PHP 7.4, une propriété typée doit avoir une valeur par défaut. Doctrine met donc null par défaut, mais on pourra revenir sur notre propriété dans la classe PHP pour corriger le type et la valeur par défaut. Il faudra également modifier les typages dans les méthodes get et set.
  • Les propriétés de type ENUM sont bien créées ainsi dans la BDD, mais sont de type string dans la classe PHP. Il faut revenir dessus pour modifier le type après avoir créé la classe de l’ENUM correspondant. Il faudra donc ensuite revenir sur notre propriété dans la classe PHP pour corriger le type et la valeur par défaut. Il faudra également modifier les typages dans les méthodes get et set.

Créer et exécuter les migrations

Lorsque nos entités sont à jour, on peut demander à Doctrine de créer les migrations, puis de les exécuter.

# Générer les migrations
symfony console make:migration

# Exécuter les migrations
symfony console doctrine:migrations:migrate

Les migrations sont dans le répertoire /migrations. Chaque migration contient une méthode up pour les changements à apporter et une méthode down permettant d’annuler ces changement. Le nom du fichier est constitué du timestamp afin que les migrations soient exécutées dans l’ordre.

Authentification et autorisation des utilisateurs

Symfony dispose de tout le nécessaire afin d’authentifier un utilisateur et, s’il tente d’accéder à une page sécurisé sans l’être, renvoyer une erreur HTTP 401 Unauthorized. Le nécessaire est également disponible pour vérifier les autorisations d’un utilisateur connecté et, s’il tente d’accéder à une page non permise, renvoyer une erreur HTTP 403 Forbidden.

Créer l’entité utilisateur

La classe de l’entité utilisateur doit implémenter l’interface Symfony\Component\Security\Core\User\UserInterface. Elle met notamment à disposition les méthodes :

  • getUserIdentifier : Retourne l’identifiant de l’utilisateur. Il peut s’agir de son email ou d’un login quelconque.
  • getRoles : Retourne un tableau de chaines de caractères qui sont les droits de l’utilisateur.
  • eraseCredentials : Permet de ne pas avoir à conserver en mémoire le mot de passe ’utilisateur avant hachage.

L’interface Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface peut également être implémentée. Elle met à disposition la méthode getPassword qui retourne le mot de passe haché de l’utilisateur. Elle n’est pas obligatoire, car l’utilisateur peut s’authentifier sans mot de passe, notamment dans des systèmes récents qui utilisent les passkey.

Pour créer une entité utilisant ces interfaces, le composant MakerBundle de Symfony dispose de la commande symfony console make:user qui créera une entité implémentant automatiquement l’interface UserInterface. Le chois sera donné d’implémenter ou non l’interface PasswordAuthenticatedUserInterface.

Créer un formulaire d’ajout / inscription d’utilisateurs

La commande symfony console make:registration-form permet de générer un formulaire d’enregistrement d’utilisateurs avec tout ce qu’il faut notamment pour le chiffrement du mot de passe dans le contrôleur associé (créé en même temps), l’envoi d’un email de vérification de l’adresse mail.

Après validation de la commande, le template register.html.twig peut être personnalisé. De même que le comportement des champs du formulaire dans l’objet RegistrationFormType. On pourra par exemple retirer la case à cocher « J’accepte les termes ». Le résultat est visible via la route /register.

Dans le contrôleur RegistrationController, on verra comment est chiffré le mot de passe saisi en clair dans le formulaire.

La méthode de chiffrement des mots de passe est définie dans le fichier config/packages/security.yaml. Par défaut, le mode auto indique que Symfony effectue lui-même le meilleur choix.

Authentifier un utilisateur et connaitre ses droits

Symfony utilise un UserProvider provider afin de chercher si un utilisateur correspond à l’identifiant soumit pour authentification et si oui, il retourne l’objet de l’utilisateur. Un second objet nommé Authenticator s’occupe ensuite de vérifier que le mot de passe de l’objet récupéré correspond avec celui soumit dans le formulaire d’authentification. Dans Symfony, pour vérifier si un utilisateur dispose ou non d’un droit, on utilise le système de Voters.

La notion de Firewall

Dans Symfony, ces deux étapes sont effectuées par un objet appelé Firewall. Il peut y avoir plusieurs firewalls, tous sont définis dans le fichier config/packages/security.yaml. Chaque firewall s’applique aux URL correspondant à l’expression régulière dans variable pattern ou à toutes les URL si cette variable est vide.

Dans le cas où plusieurs firewalls sont présents, c’est le 1er dont le pattern correspond qui s’applique.

Important à savoir, si un firewall dispose de la variable security avec la valeur false, alors aucune règle de sécurité ne s’applique.

Créer le formulaire d’authentification

Pour créer rapidement un formulaire d’authentification avec gestion du token CSRF et vérification du mot de passe, symfony met à disposition la commande symfony console make:security:form-login. Après exécution, il n’y a qu’à personnaliser la route dans le contrôleur SecurityController si on le souhaite, et personnaliser la mise en page du formulaire dans le template login.html.twig.

Lorsque l’authentification a réussi, on voit normalement l’identifiant de l’utilisateur apparaitre dans la debugbar de Symfony.

Aussi bien après authentification qu’après déconnexion, la page vers laquelle l’utilisateur doit être redirigé peut être personnalisée. Cela s’effectue dans le fichier config/packages/security.yaml. Exemple :

security:
    # ...
    firewalls:
        # ...
        main:
           # ...
            form_login:
                # ...
                default_target_path: after_login_route_name
            logout:
                # ...
                target: app_default_index

Les autorisations / vérifier un droit utilisateur

Lorsqu’un utilisateur est connecté, on utilise les Voters afin de savoir s’il est ou non autorisé à effectuer une action, accéder à une page etc. La méthode isGranted permet de contrôler une autorisation spécifiée via une clé. Éventuellement, on peut préciser sur quel objet le contrôle doit être effectué (un utilisateur pourrait par exemple avoir le droit de modifier certaines fiches clients mais pas d’autres, selon des règles définies par l’application).

Vérification d’un droit depuis un contrôleur :

#
# Soit depuis un attribut sur la route.
# Soit en vérifiant le droit dans la méthode.
# Soit en renvoyant une AccessDeniedException toujours dans la méthode.
#

#[IsGranted('ROLE_KEY')]
public function funcName(): Response
{
    if ($this->isGranted('ROLE_KEY')) {
        
    }
  
    $this->denyAccessUnlessGranted('ROLE_KEY');
}

L’intérêt dans le contrôleur, c’est que l’attribut IsGranted peut être déclarer sur la classe elle-même, afin de s’appliquer à toutes les routes de la classe. De la même manière qu’on peut déclarer éventuellement un préfixe à toutes les routes d’un contrôleur.

Vérification d’un droit depuis un classe autre qu’un contrôleur :

class FirstClassName
{
    public function __consctruct(
        Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface $checker
    )
    {
    }
  
    public function funcName()
    {
        $this->checker->isGranted('ROLE_KEY');
    }
}

# Autre solution

class FirstClassName
{
    public function __consctruct(
        Symfony\Bundle\SecurityBundle\Security $security
    )
    {
    }
  
    public function funcName()
    {
        $this->security->isGranted('ROLE_KEY');
    }
}

Dans un template :

{% if is_granted('ROLE_KEY') %}

{% endif %}

Il est également possible de limiter très rapidement l’accès à un ensemble de routes via la section access_control du fichier config/packages/security.yaml. Les restrictions peuvent y être définies selon le préfixe de la route (ou une REGEX), une ou plusieurs adresses IP (ou masque), le host (ou une REGEX)… De nombreux exemples sont disponibles dans la documentation officielle : https://symfony.com/doc/current/security/access_control.html.

Créer de nouveaux rôles

Il existe des rôles et des « comportements » fournis en standard, comme savoir si un utilisateur est connecté :

  • ROLE_ADMIN
  • ROLE_USER
  • IS_AUTHENTICATED
  • PUBLIC_ACCESS

Des rôles supplémentaires peuvent être créés, ROLE_SUPER_ADMIN par exemple. Ils doivent simplement d’utiliser le préfixe ROLE_ et être en majuscules. Tous les rôles et leur hiérarchie peuvent être déclarés dans le fichier config/packages/security.yaml. Par exemple :

security:
    # …
    role_hierarchy:
        ROLE_USER: ~
        ROLE_GESTIONNAIRE: ROLE_USER
        ROLE_AJOUT_FACTURE: ROLE_USER
        ROLE_ENCAISSER_DECAISSER: ROLE_USER
        ROLE_COMPTABLE: [ROLE_USER, ROLE_AJOUT_FACTURE, ROLE_ENCAISSER_DECAISSER]
        ROLE_GESTION_UTILISATEUR: ROLE_USER
        ROLE_ADMIN: [ROLE_COMPTABLE, ROLE_GESTION_UTILISATEUR]

Les rôles d’un utilisateur sont dans la propriété roles de l’entité User et peuvent être définis et récupérés par les méthodes setRoles et getRoles. Leur comportement est à coder selon ce qu’on souhaite que l’application fasse.

Déploiement en production

En cas d’erreur 500, tester cette solution :

  1. composer remove symfony/apache-pack
  2. composer require symfony/apache-pack
  3. Renvoyer en production le fichier /public/.htaccess qui vient d’être régénéré.

En cas d’erreur 404 sur les CSS / JS :

  1. Sur l’environnement de développement, exécuter php bin/console asset-map:compile --env=prod
  2. Déployer en production le contenu de /public/assets

Sources :

– OpenClassrooms : https://openclassrooms.com/fr/courses/8264046-construisez-un-site-web-a-laide-du-framework-symfony-7
– Symfony : https://symfony.com/doc/current/index.html