Vous êtes sur la page 1sur 542

Accueil

Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Titre, auteur...
Introduction
Avant-propos
Public visé
Prérequis
Objectifs du livre
Le développement avec les frameworks
Symfony
Quiz
Mise en place d’un projet Symfony
L’outillage nécessaire

 1. Introduction
 2. Symfony : Un projet PHP
 a. Préconisation d’installation
 b. Installation sous Linux
 c. Installation sous Windows
 3. L’installeur Symfony
 4. Composer
 a. Installer Composer
 5. Les environnements de développement pour Symfony
 a. Un IDE pour Symfony !
 b. PHPStorm
 c. Eclipse IDE for PHP Developers
 d. Visual Studio Code
 e. Conclusion

Création d’un projet Symfony


Structure de l’application
Quiz
Architecture du framework
Routage et contrôleur
L’injection de dépendances
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

L’outillage nécessaire
1. Introduction
Avant de se lancer dans la création d’un projet Symfony, il est nécessaire de
s’équiper ! Un certain nombre d’outils sont nécessaires pour mettre en place le projet
et pour développer, leur mise en place correcte est détaillée ci-après. Certains de ces
outils sont indispensables pour tout développeur Symfony, d’autres sont optionnels ou
possèdent des alternatives. Il est essentiel de bien comprendre leur rôle et de les
installer à bon escient avant d’aller plus loin dans la construction de son projet.

2. Symfony : Un projet PHP


Le premier ensemble d’outils à installer permet de poser les fondations. En effet, un
projet Symfony est avant tout un projet PHP avec tout ce que cela implique : serveur
web disposant de PHP, base de données…
Il est donc nécessaire de prévoir l’installation d’un tel environnement, même si,
comme cela sera évoqué plus loin, l’installeur Symfony peut se substituer au serveur
web. L’installation d’un vrai serveur web disposant de PHP sera à un moment ou un
autre indispensable, notamment pour la mise en place de tests logiciels, ou encore
pour certaines fonctionnalités de sécurité.
Pour disposer d’une installation fonctionnelle de ce type d’environnement, deux
solutions s’offrent à vous :
 Installation séparée d’un serveur web, de PHP et d’une base de données
relationnelle.
 Installation d’un package prêt à l’emploi fournissant ces produits.
La première approche nécessite bien entendu une connaissance poussée du système
d’exploitation choisi, du réseau, et du fonctionnement de ces produits. La seconde,
elle, est beaucoup plus abordable techniquement, et surtout plus rapide, notamment en
environnement Windows.
Pour travailler avec Symfony 4, une version 7.1 ou supérieure de PHP est requise.
a. Préconisation d’installation
Dans cet ouvrage, nous allons principalement utiliser Apache HTTP Server en tant
que serveur web et MySQL en tant que serveur de base de données, car ce sont les
produits les plus communément associés au langage PHP. Nous verrons cependant au
cours des différents chapitres que des alternatives sont possibles.
Pour pouvoir disposer d’une installation rapidement fonctionnelle, nous vous
conseillons les choix suivants : installer les produits individuellement si vous
travaillez sous Linux (ils font partie des paquets d’installation fournis avec votre
distribution), ou installer un paquet prêt à l’emploi si vous travaillez sous Windows.
b. Installation sous Linux
Un serveur web Apache, PHP et une base de données MySQL font partie des produits
nativement fournis dans les dépôts d’installation de la plupart des distributions Linux,
leur installation est donc simple et rapide.
Installation sur Ubuntu
Commencez par mettre à jour la liste des paquets ainsi que la distribution en utilisant
les commandes suivantes :
sudo apt update
sudo apt upgrade

Ensuite, installez, activez et démarrez Apache HTTP Server avec :


sudo apt install apache2
sudo systemctl enable apache2
sudo systemctl start apache2

L’ouverture d’un navigateur pointant sur l’adresse locale http://localhost


devrait afficher la page d’accueil par défaut du serveur validant ainsi son
installation. L’étape suivante consiste à installer et sécuriser MySQL ; deux
commandes sont nécessaires :
sudo apt install mysql-server
sudo mysql_secure_installation

Pour cette commande de sécurisation de MySQL, il est nécessaire d’être attentif aux
questions posées pour sécuriser correctement votre serveur.
L’ultime étape d’installation consiste à fournir le support de PHP dans le serveur web
Apache, la commande suivante permet de tous les installer :
sudo apt install php libapache2-mod-php php7.4-mysql php7.4-common
php7.4-mysql php7.4-xml php7.4-xmlrpc php7.4-curl php7.4-gd
php7.4-imagick php7.4-cli php7.4-dev php7.4-imap php7.4-mbstring
php7.4-opcache php7.4-soap php7.4-zip php7.4-intl -y

Un redémarrage d’Apache est nécessaire :


sudo systemctl restart apache2

Installation sur CentOS


Comme sur Ubuntu, la procédure démarre par la mise à jour des paquets :
sudo yum update
L’installation, l’activation et le démarrage d’Apache HTTP Server se fait ensuite avec
les commandes respectives :
sudo yum install httpd
sudo systemctl enable httpd
sudo systemctl start httpd

Sur une distribution CentOS, c’est MariaDB, la variante libre de MySQL, qui est
proposée dans les paquets ; son installation se fait avec la commande :
sudo yum install mariadb-server mariadb -y

Comme pour le serveur Apache, il est nécessaire d’activer le service et de le


démarrer avec :
sudo systemctl enable mariadb
sudo systemctl start mariadb

puis de lancer la procédure de configuration sécurisée :


sudo mysql_secure_installation

Enfin, l’installation de PHP et un ultime redémarrage du serveur Apache


terminent l’installation :
sudo yum install -y php php-mysqlnd
sudo systemctl restart httpd

Installation complémentaire
Pour compléter et finaliser cette procédure en environnement Linux, vous
aurez également besoin d’un outil d’administration graphique pour
MySQL/MariaDB ; nous vous conseillons d’installer par exemple MySQL
Workbench qui est une application de bureau, ou bien phpMyAdmin qui est une
application web. Ces deux outils populaires offrent sensiblement les mêmes
fonctionnalités quant à la création et la manipulation de bases de données et sont
présents dans les dépôts de paquets des distributions Linux.
c. Installation sous Windows
Une plateforme Apache, PHP, MySQL s’installe très facilement sous Windows à
condition d’utiliser un paquet prêt à l’emploi ; ces paquets sont spécialement conçus
pour installer rapidement un environnement de développement autour de PHP.
Plusieurs produits existent sur le marché, en voici quelques-uns, cette liste n’est pas
exhaustive :
 XAMPP : https://www.apachefriends.org/
 Wamp Server : https://www.wampserver.com/
 Laragon : https://laragon.org/
 AMPPS : http://www.ampps.com/
L’installation de ces différentes solutions passe par un installeur qui met en place la
plateforme rapidement. Les produits se contrôlent et se configurent ensuite par un
panneau de contrôle accessible depuis la zone de notification de Windows.

3. L’installeur Symfony
L’installeur Symfony est un outil en ligne de commande qui constitue un
premier moyen de créer un projet basé sur le framework. L’utilisation de cet outil
n’est pas obligatoire pour développer avec Symfony, mais il apporte quelques petites
facilités non négligeables, comme par exemple un serveur web embarqué pour
exécuter les applications, ou bien encore un utilitaire de vérification des prérequis
avant de démarrer un projet.
L’installeur est téléchargeable à l’adresse https://symfony.com/download.

L’installation ne présente pas de difficulté particulière si vous suivez les instructions


indiquées sur la page de téléchargement en fonction du système d’exploitation cible.
Une fois l’installation terminée, vous pouvez vérifier le bon fonctionnement de l’outil
en tapant la commande symfony dans un terminal ou une invite de commandes.
4. Composer
Composer est un gestionnaire de dépendances pour PHP. Il vous permet
notamment d’intégrer des librairies tierces à vos projets avec une simplicité
déconcertante, mais également de créer des projets de développement PHP en
s’appuyant sur des modèles. Utiliser Composer est une autre possibilité pour la
création d’un projet Symfony. Même si vous choisissez de créer votre projet à partir
de l’installeur Symfony, vous aurez également besoin de Composer pour ajouter des
dépendances et exécuter des recettes Symfony Flex.
a. Installer Composer
Sous Windows
Si vous êtes sous Windows, vous devrez télécharger un exécutable qui se
chargera d’installer Composer. Il est disponible à l’adresse
suivante : https://getcomposer.org/Composer-Setup.exe
Cet assistant d’installation vous guidera tout au long des différentes étapes. Dans la
fenêtre de l’assistant, cliquez sur Next, puis renseignez le chemin vers votre
exécutable PHP.
L’installeur détecte le plus souvent l’exécutable PHP ; dans le cas contraire, ou si
vous souhaitez utiliser une autre installation de PHP, localisez l’exécutable avec le
bouton Browse.
Cliquez sur Next pour lancer l’installation. À la fin de celle-ci, Composer donne
quelques indications pour l’utiliser correctement.

Pour valider l’installation, lancez une nouvelle invite de commandes et tapez la


commande composer ; le texte suivant doit s’afficher :
Sous Linux/Unix
Exécutez les commandes suivantes depuis votre terminal.
php -r "copy('https://getcomposer.org/installer',
'composer-setup.php');"
php -r "if (hash_file('sha384', 'composer-setup.php') ===
'756890a4488ce9024fc62c56153228907f1545c228516cbf63f885e036d37e9a
59d27d63f46af1d4d07ee0f76181c7d3') { echo 'Installer verified'; }
else { echo 'Installer corrupt'; unlink('composer-setup.php'); }
echo PHP_EOL;"
php composer-setup.php
php -r "unlink('composer-setup.php');"

Vous aurez peut-être un ou plusieurs messages d’avertissement si votre installation ou


configuration de PHP est incompatible avec Composer. Si tel est le cas, vous devez
suivre les instructions affichées à l’écran pour corriger la configuration de PHP, puis
recommencer l’opération.
Une archive PHP (PHAR) nommée composer.phar est téléchargée dans le dossier
courant. Il ne reste plus qu’à la déplacer dans un dossier accessible de votre $PATH,
de manière à rendre Composer exécutable depuis n’importe quel emplacement.
sudo mv composer.phar /usr/local/bin/composer

Vérifier son installation de Composer


Pour vérifier que Composer est bien installé, exécutez l’instruction suivante dans
votre invite de commandes / terminal :
composer

Vous devriez voir apparaître une liste de commandes disponibles pour Composer.
Si cette commande ne fonctionne pas, essayez de relancer un nouveau terminal.

5. Les environnements de développement pour


Symfony
Dans le principe, un développeur PHP pourrait se contenter d’un simple éditeur de
texte avancé avec coloration syntaxique du code pour écrire son code. Dans la
pratique, il s’avère que bien d’autres fonctionnalités lui permettraient d’être beaucoup
plus productif.
a. Un IDE pour Symfony !
Un IDE (Integrated Development Environment) ou Environnement de
Développement Intégré, possède tous les outils et assistants nécessaires à la
production d’un produit logiciel. Parmi les fonctionnalités attendues, on trouve
notamment :
 la coloration syntaxique du code ;
 des assistants à la création de projet, de fichiers… ;
 un débogueur intégré ;
 un navigateur de projet ;
et bien d’autres facilités pour améliorer la productivité des développeurs.
Il n’est pas indispensable d’utiliser un tel outil pour développer une application
Symfony, mais cela est tout de même grandement conseillé. Parmi les
outils disponibles sur le marché, en voici trois qui permettent de développer en PHP et
qui offrent une intégration et un support de Symfony :
 PHPStorm (JetBrains) : considéré comme la « Rolls » des outils pour PHP,
PHPStorm est un outil commercial.
 Eclipse IDE for PHP Developers (Eclipse Foundation) : initialement un
environnement de développement pour Java, cette version est une déclinaison
d’Eclipse pour PHP.
 Visual Studio Code (Microsoft) : déclinaison gratuite de l’outil Visual Studio de
Microsoft, VS Code supporte de multiples langages de programmation, dont
PHP.
b. PHPStorm
Édité par la société JetBrains, PHPStorm est un outil commercial complet pour le
développement d’applications PHP. Une version d’évaluation de 30 jours est
disponible sur le site de l’éditeur à l’adresse https://www.jetbrains.com/phpstorm/ ;
elle permet de se faire une idée de la plus-value de l’outil avant son acquisition.
Le support de Composer est natif dans cet outil, celui de Symfony est apporté par
une extension qu’il faut installer.

La fenêtre principale de PHPStorm et l’assistant de création de projet pour Composer.


Support de Symfony
Pour installer l’extension de prise en charge de Symfony, il faut, dans le menu File,
choisir l’entrée Settings. Dans la fenêtre qui apparaît, sélectionnez
ensuite l’entrée Plugins.
Il suffit ensuite de cliquer sur le bouton Install de l’extension Symfony Support, puis
de redémarrer PHPStorm une fois l’installation terminée.
L’assistant de création de projet propose maintenant de créer des projets Symfony en
choisissant la version du framework sur laquelle se baser. D’autres extensions
complémentaires pourront être installées en fonction des besoins, pour, par exemple,
prendre en charge certains fichiers d’un projet Symfony (fichiers .env, templates
Twig…).
c. Eclipse IDE for PHP Developers
Eclipse IDE for PHP Developers est la version d’Eclipse adaptée pour le
développement en PHP, c’est un outil open source et gratuit qui peut être obtenu à
l’adresse https://www.eclipse.org/downloads/. Une fois téléchargé, lancez l’installeur
et choisissez Eclipse IDE for PHP Developers dans la liste proposée ; ajustez
éventuellement les chemins d’installation à l’étape suivante puis cliquez sur Install.
Comme pour PHPStorm, le support de Composer est natif ; cependant, il n’y a pas de
support de Symfony 4 (une extension existe mais elle ne supporte que d’anciennes
versions du framework). La création d’un projet Symfony pourra malgré tout se faire
en passant par un projet Composer.
La fenêtre principale d’Eclipse PDT et l’assistant de création de projet pour
Composer.
d. Visual Studio Code
Visual Studio Code est plus un éditeur de code qu’un véritable IDE, mais ses
extensions de qualité lui confèrent de très nombreuses possibilités pour PHP et
Symfony. C’est un outil gratuit fourni par Microsoft, disponible pour
Windows, Linux et macOS ; il peut être téléchargé à
l’adresse : https://code.visualstudio.com/
Une fois l’installeur téléchargé, lancez la procédure d’installation ; lors de la dernière
étape de l’assistant, il est pratique de cocher les options Ajouter l’option… pour
permettre d’ouvrir des fichiers et des dossiers directement depuis l’explorateur de
fichiers.
Une fois l’installation terminée, lancez Visual Studio Code.

Support de Symfony
Pour ajouter le support de Symfony dans Visual Studio Code, il faut cliquer sur
l’icône Extensions dans la barre d’icônes se trouvant sur la gauche de la fenêtre
principale, puis saisir Symfony dans le champ de recherche. Une multitude de
résultats de recherche sont retournés car plusieurs extensions sont disponibles pour
Symfony. L’une des plus complètes est le Symfony Extension Pack, qui regroupe
plusieurs extensions PHP/Symfony ; les autres sont pertinentes également et libre à
vous de les essayer.
Sélectionnez le bouton Install sur Symfony Extension Pack.

e. Conclusion
D’autres outils de développement existent pour PHP et Symfony, les outils présentés
ci-dessus ne constituent que des exemples d’usage. Vous pouvez tout à fait choisir de
continuer à utiliser vos outils habituels de développement PHP si vous ne souhaitez
pas modifier vos habitudes.
Précédent
Quiz
Suivant
Création d’un projet Symfony
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments
Tout déplier | Tout replier
Informations générales
Titre, auteur...
Introduction
Avant-propos
Public visé
Prérequis
Objectifs du livre
Le développement avec les frameworks
Symfony
Quiz
Mise en place d’un projet Symfony
L’outillage nécessaire
Création d’un projet Symfony

 1. Prérequis
 2. Création via l’installeur Symfony
 3. Création via Composer
 4. Configurer son serveur web
 a. Serveur web PHP
 b. Apache et Nginx

Structure de l’application
Quiz
Architecture du framework
Routage et contrôleur
L’injection de dépendances
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Création d’un projet Symfony


1. Prérequis
Comme évoqué précédemment, vous devez disposer d’une installation de PHP dans
sa version 7.1 au minimum pour créer un projet Symfony 4.
Nous allons présenter plusieurs techniques de création de projet utilisant
l’installeur Symfony ou bien l’installation via Composer. Les deux solutions sont
pertinentes pour créer un projet, mais une installation par l’installeur n’affranchit pas
d’avoir installé Composer pour d’autres tâches ultérieures.
2. Création via l’installeur Symfony
L’installeur Symfony s’utilise en ligne de commande dans un terminal sous Linux ou
macOS et dans une invite de commandes sous Windows. Avant de créer un nouveau
projet, vous pouvez demander la vérification des prérequis en exécutant la
commande :
symfony check:requirements

Le résultat affiché à l’écran peut vous indiquer des erreurs dans la configuration de
PHP qu’il faudra corriger avant d’aller plus loin. En plus des erreurs, il peut
également y avoir des préconisations d’optimisations faites par l’outil, mais tant que
le résultat est « OK », vous pouvez continuer.
La création du projet se fera ensuite grâce à la commande :
symfony new mon_projet --version=4.4 --full

Cette commande crée la structure de projet et installe Symfony 4.4 dans le


dossier mon_projet. Vous remarquerez qu’en fait, la commande symfony utilise
Composer de manière sous-jacente. Vous noterez également qu’un dépôt Git a été
initialisé pour ce projet, ce qui vous permet de gérer le code source et ses versions.

3. Création via Composer


Pour initialiser un projet Symfony, nous utilisons la commande create-project :
composer create-project symfony/website-skeleton mon_projet 4.4.*

Cette commande est beaucoup plus verbeuse lors de la création du projet puisqu’elle
affiche la liste complète des dépendances Composer installées ; elle affiche également
des conseils à la fin de la création du projet. Contrairement à l’installeur Symfony,
elle ne crée pas de dépôt Git pour le projet, mais elle le suggère.
Pour plus d’informations sur la commande create-project, vous pouvez exécuter la
commande suivante : composer help create-project.
La fin de l’affichage généré par la commande create-project de Composer et les
étapes suivantes pour démarrer avec son nouveau projet.
symfony/website-skeleton est un paquet. Il est composé d’un préfixe (symfony), puis
d’un nom (website-skeleton), le préfixe permettant d’éviter les conflits de nommage
entre différentes organisations.
Concrètement, un paquet est un projet PHP : une librairie ou un framework par
exemple. Ici, nous installons un squelette d’application Symfony, avec des librairies
préinstallées, dont nous pouvons nous servir de base pour nos projets.
Composer est préconfiguré avec un dépôt, Packagist (https://packagist.org). Par
défaut, seuls les paquets listés dans ce dépôt sont installables.
Les paquets installés pour votre projet sont définis au sein du fichier composer.json,
à la racine du projet, dans les sections require et require-dev.

4. Configurer son serveur web


Après vous être assuré de la création correcte de votre projet avec Symfony, vous
n’avez plus qu’à démarrer votre application. Comme toute application web, elle
nécessite un serveur web.
a. Serveur web PHP
La manière la plus simple et rapide consiste à utiliser le serveur web interne à PHP
(disponible depuis la version 5.4). Pour le lancer, il vous suffit de lancer la commande
suivante :
php bin/console server:run

Votre application est maintenant accessible ; ouvrez votre navigateur favori et entrez
l’URL suivante : http://localhost:8000
Par défaut, Symfony utilisera le port 8000, mais si celui-ci ne vous convient pas, vous
pouvez tout de même en spécifier un autre :
php bin/console server:run localhost:8080

Le principal avantage du serveur web PHP est sa rapidité d’installation et sa légèreté.


Cependant, le développement ne s’effectuera pas au sein du même environnement que
l’application de production (cette dernière utilise probablement un serveur web à part
entière, Apache, par exemple).
C’est pour cette raison que nous vous conseillons de développer autour d’un serveur
web PHP seulement dans le cas d’applications web basiques.
Si vous pensez que votre serveur web de production devra effectuer certaines tâches
spécifiques, comme la mise en cache de pages, la compression Gzip, ou devra intégrer
certains mécanismes de sécurité, il est préférable d’installer ce serveur web sur votre
environnement de développement. Vous pourrez ainsi vérifier ces fonctionnalités au
fur et à mesure du développement.
b. Apache et Nginx
Avec un serveur web classique, votre configuration devra pointer sur le sous-
répertoire public du projet.
Créer un domaine local
Avant d’aborder les configurations Apache et Nginx pour votre projet Symfony, nous
vous conseillons de créer un nom de domaine local pour votre site web en
développement.
Ainsi, pour accéder à votre site MonJournal, vous pourrez par exemple entrer
l’URL : http://monjournal.local.
Pour cela, il suffit de l’ajouter à un fichier spécifique à votre système d’exploitation :
 Sous Windows, il s’agit de C:\Windows\System32\Drivers\etc\hosts.
 Sous Linux, c’est /etc/hosts.
Voici ce qu’on peut y retrouver (Windows) :
# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.

# This file contains the mappings of IP addresses to host names.


# Each entry should be kept on an individual line. The IP address
# should be placed in the first column followed by the
# corresponding host name.
# The IP address and the host name should be separated by at least
# one space.

# Additionally, comments (such as these) may be inserted on


# individual lines or following the machine name denoted by a '#'
# symbol.
#
# For example:
#
# 102.54.94.97 rhino.acme.com # source server
# 38.25.63.10 x.acme.com # x client host

# localhost name resolution is handled within DNS itself.


# 127.0.0.1 localhost
# ::1 localhost

127.0.0.1 localhost

Ce fichier est utilisé par le système d’exploitation pour la résolution DNS (système de
noms de domaine) et il contient par défaut une entrée, pour localhost.
Cette entrée signifie que, lorsque la machine aura besoin de transcrire le nom de
domaine localhost en adresse IP, elle choisira 127.0.0.1 directement, sans avoir
besoin d’envoyer une requête vers un quelconque serveur DNS.
C’est pour cette raison que, lorsque vous saisissez localhost dans la barre d’adresse de
votre navigateur web, et dans la mesure où un serveur web est installé sur votre
machine, vous pouvez accéder à du contenu local.
Voici le contenu du fichier hosts après avoir ajouté le nom de domaine
local monjournal.local :
# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.

# This file contains the mappings of IP addresses to host names.


# Each entry should be kept on an individual line. The IP address
# should be placed in the first column followed by the corresponding
# host name.
# The IP address and the host name should be separated by at least
# one space.

# Additionally, comments (such as these) may be inserted on


# individual lines or following the machine name denoted by a '#'
# symbol.
#
# For example:
#
# 102.54.94.97 rhino.acme.com # source server
# 38.25.63.10 x.acme.com # x client host

# localhost name resolution is handled within DNS itself.


# 127.0.0.1 localhost
# ::1 localhost

127.0.0.1 localhost
127.0.0.1 monjournal.local

Maintenant que le domaine local est configuré, il ne reste plus qu’à configurer le
serveur web.
Nos configurations se basent sur un projet Symfony installé au sein du
répertoire /var/www/monjournal mais vous devrez adapter cette valeur selon
l’emplacement dans lequel votre projet est installé.
N’oubliez pas d’ajouter le répertoire public après celui-ci. Sous Windows, un chemin
vers votre projet pourrait être C:\wamp\www\monjournal par exemple.
Apache
Voici la configuration minimale d’un VirtualHost pour Apache :
<VirtualHost *:80>
ServerName monjournal.local

DocumentRoot /var/www/monjournal/public
<Directory /var/www/monjournal/public>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>

Installation du apache-pack
Symfony facilite la configuration d’Apache avec un fichier .htaccess au sein du sous-
répertoire public. Avec Symfony 4, ce fichier n’est plus présent par défaut et doit être
installé avec une recette Flex (cette notion sera abordée dans un autre chapitre) ; pour
ce faire, exécutez la commande suivante depuis le répertoire de l’application
Symfony :
composer require apache-pack

Un avertissement est affiché concernant le fait que cette recette provient de


contributions et n’est pas une recette officielle ; répondez par l’affirmative à la
question. Le fichier .htaccess est maintenant présent dans le répertoire public.
Pour tenir compte de ce fichier, il est important de placer, pour ce répertoire,
l’option AllowOverride à All (ou au minimum à FileInfo) car ce
fichier .htaccess contient des règles de réécriture d’URL. Il vous faudra également
activer/vérifier l’activation du module mod_rewrite.
Nginx
Si vous utilisez le serveur web Nginx en lieu et place d’Apache, voici une
configuration pour Nginx et PHP-FPM (FastCGI Process Manager).
server {
server_name monjournal.local;
root /var/www/monjournal/public;

location / {
try_files $uri /index.php$is_args$args;
}

location ~ ^/index\.php(/|$) {
fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
internal;
}

location ~ \.php$ {
return 404;
}
}

La configuration ci-dessus délègue l’exécution des fichiers PHP au


processus FastCGI PHP-FPM. Pour cela, vous devez (selon votre configuration de
PHP-FPM) spécifier un socket TCP/IP ou le chemin vers un socket Unix.
Vérifier la configuration de PHP depuis le Web
Vous êtes désormais prêt à ouvrir votre application, en entrant l’URL suivante dans
votre navigateur : http://monjournal.local/
Précédent
L’outillage nécessaire
Suivant
Structure de l’application
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Titre, auteur...
Introduction
Avant-propos
Public visé
Prérequis
Objectifs du livre
Le développement avec les frameworks
Symfony
Quiz
Mise en place d’un projet Symfony
L’outillage nécessaire
Création d’un projet Symfony
Structure de l’application

 1. Arborescence du projet
 2. Règles et conventions d’organisation du projet
 a. Le standard PSR-4
 b. Conventions de nommage
 3. La configuration d’une application Symfony
 a. Les annotations
 b. Le format YAML
 c. Le format XML
 d. Le format PHP
 e. Choisir son format de configuration

Quiz
Architecture du framework
Routage et contrôleur
L’injection de dépendances
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Structure de l’application
1. Arborescence du projet
Une application Symfony contient différents répertoires et chacun d’eux a un rôle
précis.
 bin : ce dossier contient les exécutables de votre projet ou de ses dépendances. Il
contient notamment la console Symfony.
 config : ce répertoire contient majoritairement les fichiers de configuration de
l’application.
 migrations : lorsque vous concevez les différentes classes d’objets qui
seront persistés en base de données, les outils Symfony (la console !) génèreront
des scripts PHP dans ce répertoire, permettant ainsi de mettre à jour le schéma de
base de données.
 public : ce dossier comporte toutes les ressources publiques. Ces fichiers sont
donc accessibles directement depuis le Web, via l’URL
http://mon_projet.local/<nom_du_fichier>. Les fichiers de ce répertoire sont
souvent statiques (feuilles de style CSS, fichiers de scripts JavaScript, etc.), à une
exception près, le contrôleur frontal (cf. chapitre Architecture du framework -
Architecture de Symfony), c’est-à-dire le fichier index.php.
 src : ce répertoire contient vos classes métiers, contrôleurs, etc. Pour faire simple,
c’est le cœur de votre projet, c’est dans ce dossier que se déroule la quasi-totalité
du développement.
 templates : l’emplacement de stockage des templates et des gabarits Twig. Les
templates constituent les écrans (ou encore les vues) de votre application ; ils
mélangent code HTML et langage Twig pour le rendu dynamique des écrans.
 tests : les différents tests logiciels, unitaires, d’intégration ou fonctionnels seront
stockés dans ce répertoire afin de pouvoir être exécutés par PHPUnit.
 translations : si vous souhaitez internationaliser votre application, vous devez
fournir des fichiers de langues (ou fichiers de traductions) pour chaque langue
supportée par celle-ci. Ces fichiers devront résider dans ce répertoire.
 var : ce dossier contient le cache et les logs (historiques des événements) de votre
application.
 vendor : ce répertoire a été généré par Composer et il contient tous les
paquets (librairies ou bundles) dont votre projet dépend. Vous pouvez ignorer ce
dossier, mais vous ne devez en aucun cas le versionner car Composer est chargé
de le générer pour chaque installation/déploiement de votre projet.

2. Règles et conventions d’organisation du projet


a. Le standard PSR-4
Les normes PSR sont des standards définis par les responsables des différents
frameworks PHP. Elles sont ensuite adoptées au sein de chaque framework,
permettant une meilleure interopérabilité entre ceux-ci (pour retrouver tous les
standards : https://github.com/php-fig/fig-standards).
PSR-4 est un standard concernant le chargement automatique des classes en PHP.
Concrètement, il se base sur une corrélation entre le FQCN d’une classe et le système
de fichiers.
Le FQCN (ou Fully Qualified Class Name) désigne le nom complet d’une classe,
espaces de noms compris.
Ainsi, si l’on définit un chargeur automatique de classe type PSR-4 sur le dossier src,
la classe Eni\MonEspace\MaClasse devra être définie à l’emplacement suivant :
src/Eni/MonEspace/MaClasse.php

Et son contenu pourrait être :


namespace Eni\MonEspace;

class MaClasse
{
public function maMethode()
{
// ...
}

// ...
}
Veillez à ne pas utiliser de tiret bas (underscore) dans vos noms de classes car le tiret
bas était utilisé comme alternative aux espaces de noms avant leur apparition avec
PHP 5.3.0. Il n’est donc pas pris en charge par la norme PSR-4 et provoquerait des
résultats inattendus.
b. Conventions de nommage
Symfony préconise de respecter un certain nombre de conventions de nommage au
sein d’un projet. Ces conventions sont déjà appliquées pour les fichiers présents dans
l’application générée et le seront pour le code généré automatiquement par la console.
Les respecter permet d’harmoniser le code dans l’application et de faciliter la lecture
et la compréhension de celui-ci.
La convention Camel Case pour le code
La convention Camel Case est la convention utilisée par la grande majorité des
langages objets. Elle s’applique aux identificateurs de classes, méthodes, attributs et
constantes.
 Classes : le nom d’une classe doit commencer par une majuscule et chaque mot
qui constitue le nom de la classe commence également par une majuscule.
Exemple : IdentificationUtilisateurService.
 Méthodes : le nom d’une méthode doit commencer par une minuscule et chaque
mot qui constitue le nom de la classe commence par une majuscule.
Exemple : maMethodeDeService().
 Attributs : les attributs suivent les mêmes conventions que les méthodes.
Exemple : monAttribut.
 Constantes : les constantes s’écrivent en majuscules, les mots sont séparés par
des caractères tiret bas (underscore). Exemple : NOMBRE_DE_ROUES.
La convention Snake Case
La convention Snake Case utilise des minuscules exclusivement. Elle permet de
représenter des niveaux hiérarchiques séparés par le point (.) et les mots sont séparés
par le caractère underscore (_).
Cette convention s’utilise dans Symfony pour :
 les variables Twig. Exemple : http_status_code ;
 les paramètres de configuration. Exemple : framework.csrf_protection.
Le nommage des éléments du modèle objets
Au-delà des classes, PHP utilise également la notion d’interfaces, de classes
abstraites, de traits. Des conventions sont appliquées pour ces éléments spécifiques du
modèle objet.
 Les interfaces sont suffixées par Interface. Exemple : UserInterface.
 Les traits sont suffixés par Trait. Exemple : AuthMethodsTrait.
 Les classes d’exceptions personnalisées sont suffixées par Exception.
Exemple : UserLoginErrorException.
 Les classes abstraites sont suffixées par Abstract.
Exemple : AbstractUserService.
Le nommage des fichiers
À cause de la norme PSR-4, les fichiers PHP doivent suivre les conventions de
nommage des classes, c’est-à-dire la convention Camel Case des classes :
 IdentificationUtilisateurService est une classe qui devra se trouver dans le
fichier IdentificationUtilisateurService.php
Les templates Twig et les fichiers statiques (images, css, JavaScript) seront nommés
en utilisant la convention Snake Case.
Exemples :
 Un template Twig : article.html.twig ;
 Un fichier CSS : main.css.
Autres conventions d’écriture
De nombreuses conventions sont encore applicables pour les projets Symfony, vous
en trouverez la liste exhaustive à
l’adresse : https://symfony.com/doc/current/contributing/code/standards.html

3. La configuration d’une application Symfony


Dans tout framework de développement, la configuration tient une place
importante puisque c’est elle qui permet d’ajuster le comportement du framework en
fonction des besoins de l’application. Pour des raisons pratiques et historiques,
Symfony propose quatre formats différents de configuration.
 Les annotations ;
 Les fichiers YAML (YAML Ain’t Markup Language) ;
 Les fichiers XML (eXtensible Markup Language) ;
 Les fichiers PHP.
Quel que soit le format choisi, les possibilités de configuration restent les mêmes ;
seule la syntaxe change.
a. Les annotations
Les annotations sont des métadonnées qui peuvent être ajoutées dans ce qu’on appelle
des DocBlocks. Un DocBlock est un commentaire PHP particulier, commençant
par /** et contenant un astérisque au début de chacune de ses lignes.
L’utilisation des annotations n’est pas native à Symfony mais provient d’un package
installé par défaut : SensioFrameworkExtraBundle.
En voici quelques exemples :
<?php
/**
* Commentaire de type DocBlock sur une classe
*/
class A
{
/**
* Commentaire de type DocBlock sur une méthode.
*
* @MonAnnotation()
*/
public function foo()
{
//...
}
}

Dans l’exemple ci-dessus, nous avons deux DocBlocks, un au niveau de la


définition de la classe A, un autre au-dessus de la définition de la méthode foo. Le
deuxième DocBlock contient une annotation @MonAnnotation.
Les annotations sont reconnaissables au fait qu’elles commencent par le
caractère arobase.
Contrairement au langage Java, en PHP, les annotations ne sont pas natives. C’est
grâce à son API d’introspection que PHP est capable de récupérer le contenu d’un
DocBlock, mais les annotations ne constituent pas pour lui des métadonnées en tant
que telles.
À l’origine, le DocBlock, comme son nom l’indique, a été conçu pour accueillir la
documentation du code. Vous êtes peut-être familier des PHPDocs et/ou d’outils
comme phpDocumentor/Doxygen.
Ensuite, c’est le projet Doctrine, qui, pour la deuxième version de son ORM (Object-
Relational Mapping), fut le premier projet PHP de grande envergure à adopter
l’utilisation d’annotations dans un autre but que de la documentation, en s’inspirant de
l’ORM Java Hibernate. Le framework Symfony a suivi la même direction.
La conséquence est qu’aujourd’hui, sur un projet Symfony, les annotations sont
devenues incontournables bien que non obligatoires (il existe toujours l’alternative
d’utiliser les fichiers de configuration). Nous vous conseillons fortement de les
utiliser. En effet, elles permettent un développement rapide (il n’y a pas besoin de
jongler entre un fichier de configuration et son code) et sont très lisibles, tandis que
l’impact sur la performance du projet est quasi inexistant.
Exemple de configuration
Dans cet exemple, nous illustrons la définition d’une route permettant
l’exécution d’une méthode dans un contrôleur.
/**
* @Route("/hello/world", name="ma_route")
*/
public function hello()
{
return new Response('Hello world!');
}

b. Le format YAML
YAML est un langage simple qui décrit les données. C’est un acronyme récursif
signifiant YAML Ain’t Markup Language (YAML n’est pas un langage de
balisage) pour faire un pied de nez à la verbosité des langages de balisage comme
XML. YAML a été créé par Clark Evans en 2001.
YAML est très présent dans Symfony car c’était le langage par défaut de
configuration du framework dans la toute première version. Il est aujourd’hui
toujours autant utilisé, notamment dans les fichiers existant par défaut dans le dossier
config d’une application Symfony 4.
Comme PHP, il a une syntaxe pour les types simples comme les chaînes, les booléens,
les décimaux ou les entiers. Mais contrairement à PHP, il fait une différence entre les
tableaux (séries) et les hachages (mappings).
Exemple de configuration
Dans cet exemple, nous illustrons la définition d’une route permettant
l’exécution d’une méthode hello() dans une classe de contrôleur
nommée WelcomeController.
ma_route:
path: /hello/world
controller: App\Controller\WelcomeController::hello

c. Le format XML
XML est un format de prédilection depuis de nombreuses années pour la définition de
données de configuration applicatives. Son principal avantage est de pouvoir être
associé à des grammaires de validation, permettant ainsi de vérifier la structure et les
données d’un document. Néanmoins, il reste plus verbeux que YAML ou les
annotations, par exemple.
Exemple de configuration
Dans cet exemple, nous illustrons la définition d’une route permettant
l’exécution d’une méthode hello() dans une classe de contrôleur
nommée WelcomeController.
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">

<route id="ma_route"
path="/hello/world"
controller="App\Controller\WelcomeController::hello" />
</routes>

d. Le format PHP
Une dernière possibilité reste l’utilisation de PHP pour configurer son application
Symfony. Le fait d’utiliser directement PHP peut permettre un (très) léger gain en
performance par rapport au fait d’utiliser des fichiers YAML, XML, ou bien des
annotations. Mais la faible lisibilité du format est clairement un frein.
Exemple de configuration
Dans cet exemple, nous illustrons la définition d’une route permettant
l’exécution d’une méthode hello() dans une classe de contrôleur
nommée WelcomeController.
return function (RoutingConfigurator $routes) {
$routes->add('ma_route', '/hello/world)
->controller([WelcomeController::class, 'hello'])
;
};

e. Choisir son format de configuration


Pour garder son application cohérente, il est conseillé d’opter pour un format et de s’y
tenir, plutôt que de mélanger différents formats de configuration. Il n’y a pas de
format parfait, chacun a ses avantages et ses inconvénients :
 Les annotations sont rapides à mettre en place, facilement
maintenables/modifiables, mais ne sont pas natives à PHP ; ce sont en quelque
sorte des commentaires « spéciaux ». En tant que telles, certains pourraient
considérer que les annotations doivent respecter leur rôle de descripteurs et ne pas
contenir de logique applicative.
 Le langage XML est peu lisible mais d’un autre côté, c’est un langage strict et
structuré, et la plupart des IDE (environnements de développement) intègrent par
défaut un validateur de fichiers XML émettant des alertes en cas d’invalidité de
ceux-ci.
 Quant au YAML, c’est un format assez flexible et lisible, un véritable compromis
entre le XML et les annotations.
Le PHP permet d’utiliser directement les composants internes de Symfony mais son
utilisation est moins répandue. Son avantage, bien que minime, pourrait être une
meilleure performance en environnement de développement.
Précédent
Création d’un projet Symfony
Suivant
Quiz
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Titre, auteur...
Introduction
Avant-propos
Public visé
Prérequis
Objectifs du livre
Le développement avec les frameworks
Symfony
Quiz
Mise en place d’un projet Symfony
L’outillage nécessaire
Création d’un projet Symfony
Structure de l’application
Quiz
Architecture du framework
Le modèle de conception MVC

 1. Définitions et responsabilités
 a. La vue
 b. Le modèle
 c. Le contrôleur
 2. En pratique
 a. Le contrôleur frontal
 b. Le routage
 c. Le contrôleur et le modèle
 d. La vue
 e. En synthèse

Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
L’injection de dépendances
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Le modèle de conception MVC


1. Définitions et responsabilités
L’acronyme MVC (en anglais : Model View Controller) est un terme très
répandu dans l’univers du développement logiciel. Il qualifie un modèle de
conception (ou Design-Pattern en anglais), dont l’objectif est d’identifier précisément
les responsabilités des différents composants d’une application afin d’augmenter sa
maintenabilité et son évolutivité.
Le modèle MVC est un modèle de conception d’architecture logicielle dont les
premières implémentations remontent au début des années 1980 dans le
langage SmallTalk. L’objectif étant de maîtriser une certaine complexité
applicative, il est décidé d’affecter des tâches précises à trois catégories de
composants :
 Le modèle : des composants responsables de la gestion des données et des
traitements métier de l’application.
 La vue : pour prendre en charge l’affichage des informations à destination de
l’utilisateur final.
 Le contrôleur : servant d’aiguilleur entre l’utilisateur, les données et la vue.
Repris à la fin des années 1990 par la plateforme Java Enterprise Edition dédiée au
développement web, il a ensuite été appliqué à bien d’autres technologies de
développement d’applications web et de sites web, et donc en PHP dans Symfony.
a. La vue
La vue correspond à la partie « présentation ». C’est par celle-ci que l’utilisateur
interagit avec l’application.
Elle désigne souvent des templates ; un template est un « gabarit », une mise en page
permettant de présenter des informations aux utilisateurs au travers d’une interface.
Dans le contexte d’une application web, un template contient essentiellement du
HTML pour la mise en page, ainsi que du code spécifique, PHP ou autre, pour y
intégrer les données dynamiques venant du modèle. L’utilisateur interagit avec
l’application en cliquant par exemple sur un lien ou en remplissant un formulaire.
b. Le modèle
Le modèle, quant à lui, désigne les fonctionnalités de l’application. Il couvre un
spectre assez large, allant des « classes métier » (Utilisateur, Produit, Commande,etc.)
aux classes chargées de manipuler celles-ci et de gérer leur lien avec la base de
données.
Il sera donc nécessaire de délimiter chacune des responsabilités des classes du
modèle.
c. Le contrôleur
Le contrôleur est le chef d’orchestre de l’application ; il est l’intermédiaire entre
l’utilisateur et les couches Modèle et Vue.
Le contrôleur est composé d’une multitude d’actions, et une action est associée à
chaque requête. L’action contient de la logique mais aucun traitement, ce dernier étant
pris en charge par la couche Modèle.

2. En pratique
En parcourant les documentations de plusieurs frameworks pour applications web, on
remarque différentes politiques quant à leur compatibilité avec ce modèle de
conception. Tandis que certains s’en targuent fièrement en page d’accueil, d’autres en
parlent très peu, voire ne l’évoquent pas du tout. En réalité, la quasi-totalité le sont.
En effet, ce modèle de conception désigne trois couches assez abstraites. Il n’est ni un
choix architectural fort, ni un qualificatif suffisant pour désigner l’organisation d’un
projet. C’est le minimum qu’un framework se doit de proposer (même si ce modèle de
conception MVC n’est pas obligatoire ou configuré par défaut).
Examinons maintenant le fonctionnement d’un framework MVC au travers d’un
exemple, une requête HTTP donnée :
GET /articles/introduction.html
Par défaut, en recevant cette requête, un serveur web essayerait de mettre en relation
l’URL avec le système de fichiers du serveur. Apache par exemple, irait chercher le
fichier DocumentRoot/articles/introduction.html.
DocumentRoot est le chemin vers le dossier public de l’application, c’est-à-dire celui
dont les fichiers sont accessibles par les utilisateurs.
Ce comportement implique la création d’un fichier pour chaque URL du site, qui
devra contenir le même code nécessaire au démarrage de l’application.
Ceci est incompatible avec le fonctionnement des frameworks, car ils embrassent le
principe du Don’t Repeat Yourself (en français : « ne pas se répéter »).
Répéter le même code sur tous les fichiers complique la maintenance du site web : si
une partie de ce code doit être modifiée, vous devrez mettre à jour beaucoup de
fichiers.
La solution à ce problème est d’utiliser un seul fichier pour gérer les échanges entre le
serveur web et le framework, qui agira en tant qu’intermédiaire entre ces deux
entités : le contrôleur frontal.
a. Le contrôleur frontal
C’est le point d’entrée de l’application ; il contient le code nécessaire à
l’amorçage (bootstrap) de l’application.
Notre requête a donné lieu à l’interprétation de ce fichier et non
de DocumentRoot/articles/introduction.html.
Ceci est rendu possible par les modules de réécriture d’URL proposés par les serveurs
web. Ils sont capables d’analyser la requête HTTP entrante et de la réécrire à la volée
pour qu’elle soit orientée vers un autre fichier.
b. Le routage
Une fois l’application démarrée par le contrôleur frontal, le routage doit décider de
l’action à effectuer pour satisfaire la requête du client, à savoir une requête GET sur la
ressource /articles/introduction.html.
Ici, le routage décide qu’il faut exécuter l’action « voir » du contrôleur article. Cette
décision a été prise car des règles de routage ont préalablement été définies par le
développeur. Nous reviendrons sur ces dernières au cours du chapitre Routage et
contrôleur.
c. Le contrôleur et le modèle
Les actions sont des tâches. Elles sont regroupées par thèmes au sein de contrôleurs.
Concrètement, un contrôleur est une classe et chacune de ses méthodes publiques est
une action. Ici, l’utilisateur souhaite voir un article, c’est-à-dire exécuter
l’action voir du contrôleur Article.
« Exécuter l’action voir du contrôleur Article » se traduit en PHP par « invoquer la
méthode voir de la classe ArticleControleur ».
Voici ce à quoi pourrait ressembler ce contrôleur :
<?php

class ArticleControleur
{
public $modele;
public $vue;

// Le titre de l'article ('introduction' dans notre cas)


// est passé en argument lors de l'invocation de l'action.
public function voir($titre)
{
$article = $this
->modele
->chercherArticle($titre);

$this->vue->afficher(array('article' => $article));


}
}

La classe ci-dessus est une parfaite illustration de la description de la couche


Contrôleur, contenant uniquement de la logique, que nous avons faite (cf. section Le
modèle de conception MVC - Définitions et responsabilités). Ici, le contrôleur
demande à la couche Modèle de rechercher l’article, puis le transmet, via une
méthode afficher, à la couche Vue.
d. La vue
En dernier lieu, la vue s’occupe de présenter la page HTML. Ici, elle affiche le titre de
l’article ainsi que son contenu :
<!DOCTYPE html>
<html>
<body>
<h1><?php echo $article->getTitre() ?></h1>
<p><?php echo $article->getContenu()?></p>
</body>
</html>

e. En synthèse
Bien qu’il ne s’agisse ici que de « pseudo code » et en aucun cas du code Symfony, il
n’en illustre pas moins le principe du modèle MVC tel qu’il est mis en œuvre dans les
frameworks. Les frameworks PHP tels que Symfony, Zend Framework ou encore
Laravel, utilisent ce principe et seule la syntaxe d’appel diffère, le principe reste le
même.
Une bonne compréhension du principe de fonctionnement du modèle MVC est
aujourd’hui une compétence essentielle pour appréhender les frameworks de
développement, et pas seulement Symfony ou bien les autres frameworks PHP.
Précédent
Quiz
Suivant
Architecture de Symfony
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Titre, auteur...
Introduction
Avant-propos
Public visé
Prérequis
Objectifs du livre
Le développement avec les frameworks
Symfony
Quiz
Mise en place d’un projet Symfony
L’outillage nécessaire
Création d’un projet Symfony
Structure de l’application
Quiz
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony

 1. Présentation
 2. Le contrôleur frontal
 3. Le Service Container
 4. Le modèle MVC dans Symfony ?
 5. L’approche par composant

Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
L’injection de dépendances
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Architecture de Symfony
1. Présentation
Nous allons maintenant découvrir les principales entités du framework participant au
traitement d’une requête, et en découvrir deux qui sont spécifiques à Symfony :
le Kernel et le Service Container.
Avant de schématiser cette architecture, voici un bref descriptif de ces deux entités :
 Le Kernel (noyau, en français) est le cœur du framework. C’est un composant
interne et il n’est pas obligatoire de connaître son existence pour développer sous
Symfony. Cependant, comprendre son fonctionnement et savoir écouter les
signaux qu’il émet est essentiel pour bénéficier pleinement des possibilités
offertes par le framework.
 Le Service Container est un composant incontournable ; c’est une sorte de boîte
à outils, dans laquelle vous trouverez ce qu’on appelle des « services ». Ces
services sont divers et variés. Certains vous permettent de requêter des bases de
données, d’autres de « serializer » (linéariser) des objets. Vous aurez même le
droit de créer vos propres services.

2. Le contrôleur frontal
Il existe un troisième composant important : le contrôleur frontal.
Le contrôleur frontal n’est pas véritablement une spécificité de Symfony ; il
correspond à une approche standard du modèle MVC qui part du principe que tout le
traitement bas niveau des requêtes se fait par un seul et unique point d’entrée. Ce
contrôleur frontal est notamment chargé de transformer les requêtes et réponses HTTP
en objets PHP.
Avant d’approfondir, découvrons au travers d’un schéma le traitement d’une requête
dans une application Symfony :
Flux applicatifs entre une requête et une réponse dans une application Symfony.
Le Kernel reçoit la requête du client, il interroge le « router » dans le but de
savoir quel contrôleur il doit invoquer.
Une fois ce contrôleur invoqué, le Kernel attend de lui qu’il retourne une
réponse HTTP, peu importe la manière dont elle est générée.
De ce fait, le contrôleur est libre d’invoquer différents services, comme le modèle ou
la vue, mis à sa disposition au sein du Service Container. De ces
différentes interactions naît une réponse HTTP, qui est renvoyée au Kernel.
Finalement, le Kernel transmet cette réponse au client.

3. Le Service Container
Le Service Container contient des « services » ; sur le schéma précédent, on y aperçoit
par exemple les services « Modèle » ou « Vue ».
Sur le schéma, nous décrivons uniquement « Modèle » et « Vue », mais il existe bien
d’autres services ; certains sont présents par défaut, d’autres sont créés par le
développeur.
Les services ne sont pas des concepts abstraits : chaque service est un objet PHP.
Le Service Container est la seule entité mise à disposition du contrôleur pour l’aider à
générer sa requête HTTP, c’est donc un élément central de l’application.
En installant Symfony, nous nous rendons compte qu’il est préconfiguré avec un
certain nombre de services, chacun répondant à un besoin particulier (gérer les
templates, communiquer avec des bases de données, etc.).

4. Le modèle MVC dans Symfony ?


Il serait réducteur de qualifier Symfony de framework MVC. En effet, bien que celui-
ci soit configuré par défaut, nous voyons par exemple que le service Modèle n’est pas
obligatoire. De plus, les services Modèle et Vue étant contenus dans le Service
Container au milieu d’autres services, ils sont considérés comme des services
quelconques par le framework, qui n’a en aucun cas conscience de cette architecture.
Le fait est que Symfony n’a pas été spécialement conçu pour accueillir ce modèle de
conception. Hormis la couche Contrôleur qui est obligatoire, le développeur est libre
d’implémenter l’architecture de son choix pour son application.
Cependant, nous travaillons essentiellement selon l’approche MVC tout au long de cet
ouvrage, car ce modèle de conception répond naturellement à la plupart des besoins
d’une application web moderne et est gage d’évolutivité et de maintenabilité pour les
applications.

5. L’approche par composant


Symfony est connu pour être un puissant framework PHP mais il est tout de même
important de savoir que c’est aussi un ensemble de composants.
Chaque composant est une sorte de librairie autonome, utilisable dans n’importe quel
projet PHP. En voilà quelques-uns :
 Form : gestion des formulaires.
 Console : création de programmes en ligne de commande.
 Translation : internationalisation d’applications.
Pour un listing complet : https://symfony.com/components
Les composants Symfony peuvent être utilisés dans n’importe quel projet PHP. Le
framework Symfony est le projet qui les exploite pleinement et de manière
approfondie. Mais il en existe d’autres : Silex, par exemple, est un framework PHP
ultraléger, développé entièrement autour des composants Symfony ; autre exemple :
Drupal, un système de gestion de contenu pour le Web (CMS : Content Management
System) lui aussi construit majoritairement autour des composants de Symfony.
Dans leurs premières versions, les composants étaient très difficilement utilisables de
manière autonome, notamment à cause d’un système d’autoload (autochargement des
classes) peu standard et d’une omniprésence du modèle de conception Singleton
rendant très monolithique la structure du framework.
Ces défauts ne sont plus d’actualité depuis Symfony 3. En plus des traditionnelles
références de sites web à fort trafic utilisant le framework, comme c’était le cas avec
« Yahoo! Questions/Réponses » ou « Delicious », Symfony en possède désormais un
autre type : les projets PHP open source ayant adopté ses composants. On citera
notamment le framework Laravel et les CMS (Content Management System) Drupal
ou EZ Publish.
Précédent
Le modèle de conception MVC
Suivant
Symfony Flex
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Titre, auteur...
Introduction
Avant-propos
Public visé
Prérequis
Objectifs du livre
Le développement avec les frameworks
Symfony
Quiz
Mise en place d’un projet Symfony
L’outillage nécessaire
Création d’un projet Symfony
Structure de l’application
Quiz
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex

 1. Présentation
 2. Fonctionnement de Symfony Flex
 3. Les recettes Flex
 a. Un exemple concret

Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
L’injection de dépendances
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes
Symfony Flex
1. Présentation
Symfony Flex représente la nouvelle approche pour installer des dépendances et donc
des fonctionnalités supplémentaires dans un projet Symfony. Il est introduit avec la
version 3.3 de Symfony mais il requiert cependant la structure d’un projet Symfony 4,
c’est donc dans cette version qu’il est pleinement exploitable.
Symfony Flex rend obsolète l’ancien système de « bundle », jugé finalement trop
contraignant pour installer de nouvelles dépendances. Dans cet ancien système, en
plus de devoir installer une nouvelle dépendance, il était souvent nécessaire d’agir
manuellement sur la configuration d’un nouveau bundle ; avec Symfony Flex, tout est
pris en charge.
L’idée fondamentale qui fait la différence avec les bundles, c’est que Flex ajoute une
configuration par défaut immédiatement opérationnelle en plus de la dépendance, et,
dans la majorité des cas, cette configuration n’a pas besoin d’être modifiée.
Toutes les tâches que Flex doit exécuter sont décrites dans un fichier au format JSON
(JavaScript Object Notation) appelé Recette Flex.

2. Fonctionnement de Symfony Flex


À la base du fonctionnement de Symfony Flex, il y a l’outil Composer présenté dans
le chapitre Mise en place d’un projet Symfony. Symfony Flex est en fait une
extension de Composer permettant d’ajouter des traitements en plus de la simple
installation de bibliothèques PHP. Symfony Flex est d’ailleurs le
paquet Composer symfony/flex ; il étend Composer et s’utilise avec la
commande composer. Ainsi, quand vous utilisez la commande composer
require nom_dependance, Flex regarde s’il existe une recette de ce nom et, si c’est
le cas, il l’installe. Dans le cas contraire, il laisse la main à Composer
Tout comme Composer, Flex utilise un dépôt dans lequel sont concentrées toutes les
recettes ; ce dépôt est accessible à l’adresse : https://flex.symfony.com
Les recettes Flex encapsulent donc des dépendances Composer avec une
configuration par défaut. Afin de simplifier l’installation des recettes, Symfony Flex
utilise un système d’alias permettant de raccourcir le nom des recettes par rapport au
nom original des dépendances Composer. Ainsi, le paquet
Composer s’appelant symfony/apache-pack possède un alias apache-pack qui peut
être utilisé dans la commande d’installation.

3. Les recettes Flex


Les recettes Symfony Flex sont décrites dans un fichier nommé manifest.json ; le
format de ce fichier est très semblable au fichier composer.json de Composer.
a. Un exemple concret
Prenons à titre d’exemple la recette cs-fixer développée par FriendsOfPHP ; cs-
fixer est un outil permettant de reformater correctement son code PHP. En faisant une
recherche sur le site https://flex.symfony.com, nous trouvons une correspondance
avec friendsofphp/php-cs-fixer ; cs-fixer n’est qu’un alias pour Symfony Flex.
En cliquant sur le lien Recipe, nous avons accès au détail de la recette et donc au
fichier manifest.json ; dont le contenu est le suivant :
{
"aliases": ["cs-fixer", "php-cs-fixer"],
"copy-from-recipe": {
".php-cs-fixer.dist.php": ".php-cs-fixer.dist.php"
},
"gitignore": [
"/.php-cs-fixer.php",
"/.php-cs-fixer.cache"
]
}

Dans cette recette, on voit clairement la déclaration des alias pour cette dernière, ainsi
qu’une action copy-from-recipe qui consiste à copier des fichiers depuis la recette
vers le projet. Le fichier .php-cs-fixer.dist.php mentionné ici fait partie des fichiers
de la recette et sera copié dans le projet. Ainsi, en exécutant la commande :
composer require cs-fixer

Symfony Flex installera la dépendance friendsofphp/php-cs-fixer et copiera le


fichier mentionné dans le projet. C’est là toute la force de Symfony Flex : ajouter de
la configuration au moment de l’installation d’une dépendance.
Précédent
Architecture de Symfony
Suivant
Les environnements
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Titre, auteur...
Introduction
Avant-propos
Public visé
Prérequis
Objectifs du livre
Le développement avec les frameworks
Symfony
Quiz
Mise en place d’un projet Symfony
L’outillage nécessaire
Création d’un projet Symfony
Structure de l’application
Quiz
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements

 1. Principe et apports
 2. Les fichiers de configuration
 3. Dans le contexte HTTP
 4. Dans le contexte CLI (Command Line Interface)

Le chargement automatique de classes


La console
Les outils pour le débogage
Quiz
Routage et contrôleur
L’injection de dépendances
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Les environnements
1. Principe et apports
Un projet de site ou d’application web dispose généralement de plusieurs copies. Il est
par exemple installé sur le serveur de production, sur les postes des différents
développeurs y participant ou sur des serveurs de test, de préproduction, de recette,
etc.
Ces différentes copies n’ont pas les mêmes attentes et il est important de pouvoir y
répondre. Elles n’utilisent pas les mêmes bases de données, ne nécessitent pas le
même type de débogage et possèdent potentiellement des configurations applicatives
différentes.
Les environnements sont une fonctionnalité majeure de Symfony. Ils permettent au
projet de réagir différemment selon le « mode » sous lequel vous choisissez de
l’exécuter.
Historiquement, Symfony définit les environnements prod, dev et test. Ils
correspondent à des noms prédéfinis depuis Symfony2, mais Symfony 4 permet de
choisir le nom souhaité pour un environnement. Le fichier .env, situé à la racine du
projet est la clé de ce système d’environnement et décrit, dans ses commentaires, un
ordre de chargement de fichiers d’environnement. Ce fichier contient également une
variable d’environnement APP_ENV (par défaut, elle est positionnée à dev)
permettant de spécifier l’environnement actif dans le projet.

2. Les fichiers de configuration


Le fichier de configuration principal des environnements est donc le fichier .env.
D’autres fichiers peuvent être présents dans ce répertoire de projet et leur chargement
est défini dans l’ordre suivant :
 .env
 .env.local
 .env.APP_ENV
 .env.APP_ENV.local
Le dernier fichier chargé de cette liste a précédence sur les autres et son contenu va
donc surcharger les valeurs définies précédemment. Les fichiers d’extension .local ne
seront pas référencés dans le dépôt Git associé au projet (ils sont référencés dans le
fichier .gitignore) ils sont donc utilisés pour permettre à chaque développeur du
projet de définir des informations propres à son environnement de travail local.
Le fichier .env est donc toujours présent. S’il définit la variable APP_ENV à la
valeur dev, alors Symfony cherchera à charger les
fichiers .env.dev puis .env.dev.local, s’ils existent, et les informations qu’ils
contiennent écraseront celles définies dans le fichier .env.
Le fichier .env est donc un fichier de configuration par défaut, qui peut être surchargé
par ces fichiers d’environnement spécifiques.

3. Dans le contexte HTTP


En pratique, dans le contexte web, l’environnement a un effet sur le rendu des pages.
En effet, le contrôleur frontal ajoute le Profiler Symfony (présenté plus loin dans ce
chapitre) à toutes les pages lorsqu’il fonctionne en environnement dev.
Le nom de cet environnement est donc à conserver pour pouvoir utiliser cet outil
pratique pendant les phases de développement et de mise au point des applications.

4. Dans le contexte CLI (Command Line Interface)


Avec la console (présentée plus loin dans ce chapitre), l’environnement est
défini grâce à l’option --env ou -e et permet donc d’utiliser des préférences
d’environnement différentes selon les commandes de la console.
L’environnement de test est quant à lui utilisé pour lancer la suite de tests du projet.
Cette dernière étant exécutée en ligne de commande, il n’existe pas de contrôleur
frontal pour cet environnement. Pour plus d’informations sur les tests, reportez-vous
au chapitre Tester son application Symfony.
Précédent
Symfony Flex
Suivant
Le chargement automatique de classes
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Titre, auteur...
Introduction
Avant-propos
Public visé
Prérequis
Objectifs du livre
Le développement avec les frameworks
Symfony
Quiz
Mise en place d’un projet Symfony
L’outillage nécessaire
Création d’un projet Symfony
Structure de l’application
Quiz
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes

 1. Le standard PSR-4
 2. Mécanismes alternatifs
 3. Application aux applications Symfony

La console
Les outils pour le débogage
Quiz
Routage et contrôleur
L’injection de dépendances
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Le chargement automatique de
classes
Le chargement automatique des classes (ou autoload) est un concept clé de Symfony.
L’organisation d’un projet Symfony est massivement orientée objet : hormis les
contrôleurs frontaux et la console, les fichiers PHP contenant du code procédural sont
quasi-inexistants. En pratique, chaque fichier PHP d’un projet contient une classe et
ce fichier est placé à un endroit précis, de manière à faciliter le chargement de la
classe qu’il contient.
Cette organisation n’est pas spécifique à Symfony, mais à Composer. Composer est
capable d’autocharger toutes vos classes, grâce à la configuration de la
section autoload du fichier composer.json.

1. Le standard PSR-4
Par défaut, dans un projet Symfony, la section autoload du
fichier composer.json contient la configuration suivante :
{
"autoload": {
"psr-4": { "App\\": "src/" }
}
}

Cette dernière configure un chargeur automatique de classe de type PSR-4. Le


standard PSR-4 est présenté dans le chapitre Mise en place d’un projet Symfony dans
la section Règles et conventions d’organisation du projet. Cette configuration signifie
que le préfixe d’espace de noms App est appliqué à l’ensemble des classes créées
dans le projet.
Ainsi, et conformément à cette norme PSR-4, si une classe
nommée AccueilController est définie dans le
fichier src/Controller/AccueilController.php, alors son espace de noms sera App\
Controller et son nom de classe pleinement qualifié (FQCN : Fully-Qualified Class
Name) sera App\Controller\AccueilController.
Il est possible de remplacer le préfix App attribué par défaut par un autre (identifiant
votre entreprise, par exemple) en modifiant cette valeur dans le
fichier composer.json ; il faudra simplement penser à exécuter la
commande composer update pour prendre en compte ce changement.

2. Mécanismes alternatifs
Au-delà de la norme PSR-4, Composer prend également en charge d’autres
mécanismes d’autochargement des classes, comme classmap ou files.
Cependant, ces derniers sont souvent utilisés pour des projets ne respectant pas la
norme PSR-4, ce que nous vous déconseillons fortement. Pour vos projets, un
autoload de type PSR-4 devrait être capable de charger toutes vos classes.
Si vous souhaitez tout de même vous documenter sur les mécanismes
d’autoload alternatifs, nous vous renvoyons vers la page
suivante : http://getcomposer.org/doc/04-schema.md#autoload

3. Application aux applications Symfony


Le fichier vendor/autoload.php contient le chargeur de classe de votre application,
fichier qui est automatiquement généré par Composer :
 À la création du projet Symfony avec la commande composer create-project
symfony/website-skeleton.
 Après l’ajout ou la mise à jour de dépendances du projet.
 Avec la commande composer dump-autoload.
Ce fichier ne comporte pas seulement le chargeur des classes présentes dans le
dossier src, mais également ceux de toutes les dépendances de votre projet, contenues
dans le dossier vendor.
La seule inclusion du fichier vendor/autoload.php est donc suffisante pour garantir
le chargement automatique de toutes les classes de votre projet
(dépendances comprises). Pour vous en rendre compte, vous pouvez analyser le
contenu des scripts en « point d’entrée » du projet, à savoir la console (bin/console) et
le contrôleur frontal (public/index.php). Vous constaterez que, directement ou
indirectement (au travers du fichier config/bootstrap.php), l’inclusion du
fichier vendor/autoload.php est faite.
Précédent
Les environnements
Suivant
La console
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Titre, auteur...
Introduction
Avant-propos
Public visé
Prérequis
Objectifs du livre
Le développement avec les frameworks
Symfony
Quiz
Mise en place d’un projet Symfony
L’outillage nécessaire
Création d’un projet Symfony
Structure de l’application
Quiz
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console

 1. Présentation
 2. Les commandes
 a. Lister les commandes disponibles
 b. Exécuter une commande
 3. Les options
 4. Les arguments
 5. L’aide sur les commandes
 6. Exécuter rapidement des commandes

Les outils pour le débogage


Quiz
Routage et contrôleur
L’injection de dépendances
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

La console
1. Présentation
La console est l’outil permettant d’interagir avec une application Symfony en ligne de
commandes (CLI ou Command Line Interface).
Contrairement à l’interface web, accessible au travers du contrôleur frontal, la console
est un outil du développeur et n’est en aucun cas destinée à être utilisée par les
utilisateurs de l’application.
La console est située dans le dossier bin et elle correspond au fichier console. C’est
un fichier PHP, la console peut donc être exécutée de cette manière (dans un
interpréteur de commandes) :
php bin/console

Exécuter le fichier tel quel ne fait qu’afficher une liste de commandes. En


pratique, vous utiliserez différents arguments, dont le plus important est le nom de la
commande.
Note aux utilisateurs de systèmes Unix
Ce fichier comportant un shebang, vous avez la possibilité d’utiliser le
raccourci suivant :
bin/console

Dans ce cas-là, assurez-vous d’avoir, en plus des droits de lecture, les droits
d’exécution sur ce fichier.

2. Les commandes
La console est utilisée pour lancer des commandes. C’est son seul et unique usage.
Chaque commande est chargée d’effectuer une tâche donnée. Ces tâches sont par
exemple le vidage du cache, l’assistance au développeur en
générant automatiquement certains fichiers pour lui, en mettant à jour la base de
données, ou en débogant certaines parties de l’application.
Les commandes disponibles sont trop nombreuses pour pouvoir être listées et décrites
ici, mais vous pouvez facilement obtenir des informations sur celles-ci (cf. section
Lister les commandes disponibles ci-après et section L’aide sur les commandes, plus
loin dans ce chapitre).
a. Lister les commandes disponibles
La console, exécutée sans argument, ou avec l’argument list, affiche la liste des
commandes disponibles :
php bin/console list

Pour une meilleure organisation, certaines commandes sont réparties en espaces de


noms. À titre d’exemple, les commandes liées à la base de données sont préfixées par
« doctrine ».
b. Exécuter une commande
Pour exécuter une commande, il suffit de passer son nom en tant
qu’argument, comme nous l’avons fait précédemment avec list. L’exemple ci-dessous
exécute la commande cache:clear, chargée de vider le cache de Symfony :
php bin/console cache:clear

3. Les options
L’exécution d’une commande peut être complétée par des options. Celles-ci sont
utilisées pour modifier le comportement de la commande. Elles sont préfixées par
deux tirets (« -- ») et peuvent être placées n’importe où dans l’instruction (mais après
« bin/console »).
Illustrons les options avec un exemple : Symfony utilise un répertoire de cache
différent pour chaque environnement (dev, prod, etc.). Lorsque vous exécutez la
commande cache:clear sans aucun argument, c’est le cache de
l’environnement dev qui est vidé. L’environnement peut être modifié grâce à
l’option --env :
php bin/console cache:clear --env="prod"

php bin/console cache:clear --env=prod

php bin/console --env=prod cache:clear

php bin/console cache:clear --env prod

php bin/console cache:clear -e prod

php bin/console cache:clear -e=prod

Toutes les instructions ci-dessus sont équivalentes. Comme vous pouvez le constater,
les options peuvent être placées à n’importe quel endroit après bin/console et sont
assez souples sur la syntaxe, certaines disposant même de raccourcis (ici, -e). Un
raccourci d’option ne comporte qu’une seule lettre et est préfixé d’un seul tiret.
Certaines options sont globales tandis que d’autres sont spécifiques à une commande.

4. Les arguments
Les arguments de commande permettent de modifier le comportement d’une
commande. Contrairement aux options, les arguments ne sont pas nommés ; ils sont
identifiés par leur position. Ils doivent être placés après le nom de la commande :
php bin/console commande argument_1 argument_2

Les options peuvent tout à fait être utilisées conjointement aux arguments :
php bin/console -e prod commande --option_1 argument_1 argument_2
--option_2=valeur

5. L’aide sur les commandes


Nous savons maintenant comment renseigner des options et arguments de commande,
mais encore faut-il connaître ceux qui sont disponibles pour chaque commande. Pour
cela, la commande help est d’une grande aide ; il suffit de l’invoquer avec, en
argument, le nom de la commande à décrire, par exemple cache:clear :
Symfony affiche la syntaxe de la commande :
cache:clear [options]

Contrairement à l’option --env que nous avons utilisée précédemment, certaines


options n’attendent pas de valeur comme par exemple --no-warmup, ce qui sera
d’ailleurs le cas avec la plupart des options. En règle générale, une option sert
seulement à activer ou désactiver un certain comportement.
Chaque option est ensuite décrite en détail au sein d’une liste. En plus des
options spécifiques à la commande, les options générales sont également décrites.
La commande suivante vide le cache Symfony sans le recharger :
php bin/console -e test cache:clear --no-warmup

Lancer cette commande est équivalent à supprimer le dossier de cache d’un


environnement (ici test) dans var/cache.
Si la commande dispose d’arguments, ils seront listés et décrits au même titre que les
options.
L’option --help
Vous pouvez également exécuter la commande help pour une commande donnée en
ajoutant l’option --help (ou son raccourci -h) :
php bin/console cache:clear --help

php bin/console cache:clear -h

6. Exécuter rapidement des commandes


Avec le temps, vous vous rendrez compte que quelques commandes sont
indispensables au développement et sont régulièrement utilisées.
En général, chaque commande est composée de plusieurs « sections ». C’est le cas
de cache:clear.
Vous avez la possibilité d’omettre les fins de section. Ainsi, les deux
commandes suivantes sont équivalentes :
php bin/console cache:clear

php bin/console cac:c


Précédent
Le chargement automatique de classes
Suivant
Les outils pour le débogage
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex

 1. Présentation
 2. Fonctionnement de Symfony Flex
 3. Les recettes Flex
 a. Un exemple concret

Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
L’injection de dépendances
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Symfony Flex
1. Présentation
Symfony Flex représente la nouvelle approche pour installer des dépendances et donc
des fonctionnalités supplémentaires dans un projet Symfony. Il est introduit avec la
version 3.3 de Symfony mais il requiert cependant la structure d’un projet Symfony 4,
c’est donc dans cette version qu’il est pleinement exploitable.
Symfony Flex rend obsolète l’ancien système de « bundle », jugé finalement trop
contraignant pour installer de nouvelles dépendances. Dans cet ancien système, en
plus de devoir installer une nouvelle dépendance, il était souvent nécessaire d’agir
manuellement sur la configuration d’un nouveau bundle ; avec Symfony Flex, tout est
pris en charge.
L’idée fondamentale qui fait la différence avec les bundles, c’est que Flex ajoute une
configuration par défaut immédiatement opérationnelle en plus de la dépendance, et,
dans la majorité des cas, cette configuration n’a pas besoin d’être modifiée.
Toutes les tâches que Flex doit exécuter sont décrites dans un fichier au format JSON
(JavaScript Object Notation) appelé Recette Flex.

2. Fonctionnement de Symfony Flex


À la base du fonctionnement de Symfony Flex, il y a l’outil Composer présenté dans
le chapitre Mise en place d’un projet Symfony. Symfony Flex est en fait une
extension de Composer permettant d’ajouter des traitements en plus de la simple
installation de bibliothèques PHP. Symfony Flex est d’ailleurs le
paquet Composer symfony/flex ; il étend Composer et s’utilise avec la
commande composer. Ainsi, quand vous utilisez la commande composer
require nom_dependance, Flex regarde s’il existe une recette de ce nom et, si c’est
le cas, il l’installe. Dans le cas contraire, il laisse la main à Composer
Tout comme Composer, Flex utilise un dépôt dans lequel sont concentrées toutes les
recettes ; ce dépôt est accessible à l’adresse : https://flex.symfony.com
Les recettes Flex encapsulent donc des dépendances Composer avec une
configuration par défaut. Afin de simplifier l’installation des recettes, Symfony Flex
utilise un système d’alias permettant de raccourcir le nom des recettes par rapport au
nom original des dépendances Composer. Ainsi, le paquet
Composer s’appelant symfony/apache-pack possède un alias apache-pack qui peut
être utilisé dans la commande d’installation.

3. Les recettes Flex


Les recettes Symfony Flex sont décrites dans un fichier nommé manifest.json ; le
format de ce fichier est très semblable au fichier composer.json de Composer.
a. Un exemple concret
Prenons à titre d’exemple la recette cs-fixer développée par FriendsOfPHP ; cs-
fixer est un outil permettant de reformater correctement son code PHP. En faisant une
recherche sur le site https://flex.symfony.com, nous trouvons une correspondance
avec friendsofphp/php-cs-fixer ; cs-fixer n’est qu’un alias pour Symfony Flex.
En cliquant sur le lien Recipe, nous avons accès au détail de la recette et donc au
fichier manifest.json ; dont le contenu est le suivant :
{
"aliases": ["cs-fixer", "php-cs-fixer"],
"copy-from-recipe": {
".php-cs-fixer.dist.php": ".php-cs-fixer.dist.php"
},
"gitignore": [
"/.php-cs-fixer.php",
"/.php-cs-fixer.cache"
]
}

Dans cette recette, on voit clairement la déclaration des alias pour cette dernière, ainsi
qu’une action copy-from-recipe qui consiste à copier des fichiers depuis la recette
vers le projet. Le fichier .php-cs-fixer.dist.php mentionné ici fait partie des fichiers
de la recette et sera copié dans le projet. Ainsi, en exécutant la commande :
composer require cs-fixer

Symfony Flex installera la dépendance friendsofphp/php-cs-fixer et copiera le


fichier mentionné dans le projet. C’est là toute la force de Symfony Flex : ajouter de
la configuration au moment de l’installation d’une dépendance.
Précédent
Architecture de Symfony
Suivant
Les environnements
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements

 1. Principe et apports
 2. Les fichiers de configuration
 3. Dans le contexte HTTP
 4. Dans le contexte CLI (Command Line Interface)

Le chargement automatique de classes


La console
Les outils pour le débogage
Quiz
Routage et contrôleur
L’injection de dépendances
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Les environnements
1. Principe et apports
Un projet de site ou d’application web dispose généralement de plusieurs copies. Il est
par exemple installé sur le serveur de production, sur les postes des différents
développeurs y participant ou sur des serveurs de test, de préproduction, de recette,
etc.
Ces différentes copies n’ont pas les mêmes attentes et il est important de pouvoir y
répondre. Elles n’utilisent pas les mêmes bases de données, ne nécessitent pas le
même type de débogage et possèdent potentiellement des configurations applicatives
différentes.
Les environnements sont une fonctionnalité majeure de Symfony. Ils permettent au
projet de réagir différemment selon le « mode » sous lequel vous choisissez de
l’exécuter.
Historiquement, Symfony définit les environnements prod, dev et test. Ils
correspondent à des noms prédéfinis depuis Symfony2, mais Symfony 4 permet de
choisir le nom souhaité pour un environnement. Le fichier .env, situé à la racine du
projet est la clé de ce système d’environnement et décrit, dans ses commentaires, un
ordre de chargement de fichiers d’environnement. Ce fichier contient également une
variable d’environnement APP_ENV (par défaut, elle est positionnée à dev)
permettant de spécifier l’environnement actif dans le projet.

2. Les fichiers de configuration


Le fichier de configuration principal des environnements est donc le fichier .env.
D’autres fichiers peuvent être présents dans ce répertoire de projet et leur chargement
est défini dans l’ordre suivant :
 .env
 .env.local
 .env.APP_ENV
 .env.APP_ENV.local
Le dernier fichier chargé de cette liste a précédence sur les autres et son contenu va
donc surcharger les valeurs définies précédemment. Les fichiers d’extension .local ne
seront pas référencés dans le dépôt Git associé au projet (ils sont référencés dans le
fichier .gitignore) ils sont donc utilisés pour permettre à chaque développeur du
projet de définir des informations propres à son environnement de travail local.
Le fichier .env est donc toujours présent. S’il définit la variable APP_ENV à la
valeur dev, alors Symfony cherchera à charger les
fichiers .env.dev puis .env.dev.local, s’ils existent, et les informations qu’ils
contiennent écraseront celles définies dans le fichier .env.
Le fichier .env est donc un fichier de configuration par défaut, qui peut être surchargé
par ces fichiers d’environnement spécifiques.

3. Dans le contexte HTTP


En pratique, dans le contexte web, l’environnement a un effet sur le rendu des pages.
En effet, le contrôleur frontal ajoute le Profiler Symfony (présenté plus loin dans ce
chapitre) à toutes les pages lorsqu’il fonctionne en environnement dev.
Le nom de cet environnement est donc à conserver pour pouvoir utiliser cet outil
pratique pendant les phases de développement et de mise au point des applications.

4. Dans le contexte CLI (Command Line Interface)


Avec la console (présentée plus loin dans ce chapitre), l’environnement est
défini grâce à l’option --env ou -e et permet donc d’utiliser des préférences
d’environnement différentes selon les commandes de la console.
L’environnement de test est quant à lui utilisé pour lancer la suite de tests du projet.
Cette dernière étant exécutée en ligne de commande, il n’existe pas de contrôleur
frontal pour cet environnement. Pour plus d’informations sur les tests, reportez-vous
au chapitre Tester son application Symfony.
Précédent
Symfony Flex
Suivant
Le chargement automatique de classes
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes

 1. Le standard PSR-4
 2. Mécanismes alternatifs
 3. Application aux applications Symfony

La console
Les outils pour le débogage
Quiz
Routage et contrôleur
L’injection de dépendances
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Le chargement automatique de
classes
Le chargement automatique des classes (ou autoload) est un concept clé de Symfony.
L’organisation d’un projet Symfony est massivement orientée objet : hormis les
contrôleurs frontaux et la console, les fichiers PHP contenant du code procédural sont
quasi-inexistants. En pratique, chaque fichier PHP d’un projet contient une classe et
ce fichier est placé à un endroit précis, de manière à faciliter le chargement de la
classe qu’il contient.
Cette organisation n’est pas spécifique à Symfony, mais à Composer. Composer est
capable d’autocharger toutes vos classes, grâce à la configuration de la
section autoload du fichier composer.json.

1. Le standard PSR-4
Par défaut, dans un projet Symfony, la section autoload du
fichier composer.json contient la configuration suivante :
{
"autoload": {
"psr-4": { "App\\": "src/" }
}
}

Cette dernière configure un chargeur automatique de classe de type PSR-4. Le


standard PSR-4 est présenté dans le chapitre Mise en place d’un projet Symfony dans
la section Règles et conventions d’organisation du projet. Cette configuration signifie
que le préfixe d’espace de noms App est appliqué à l’ensemble des classes créées
dans le projet.
Ainsi, et conformément à cette norme PSR-4, si une classe
nommée AccueilController est définie dans le
fichier src/Controller/AccueilController.php, alors son espace de noms sera App\
Controller et son nom de classe pleinement qualifié (FQCN : Fully-Qualified Class
Name) sera App\Controller\AccueilController.
Il est possible de remplacer le préfix App attribué par défaut par un autre (identifiant
votre entreprise, par exemple) en modifiant cette valeur dans le
fichier composer.json ; il faudra simplement penser à exécuter la
commande composer update pour prendre en compte ce changement.

2. Mécanismes alternatifs
Au-delà de la norme PSR-4, Composer prend également en charge d’autres
mécanismes d’autochargement des classes, comme classmap ou files.
Cependant, ces derniers sont souvent utilisés pour des projets ne respectant pas la
norme PSR-4, ce que nous vous déconseillons fortement. Pour vos projets, un
autoload de type PSR-4 devrait être capable de charger toutes vos classes.
Si vous souhaitez tout de même vous documenter sur les mécanismes
d’autoload alternatifs, nous vous renvoyons vers la page
suivante : http://getcomposer.org/doc/04-schema.md#autoload

3. Application aux applications Symfony


Le fichier vendor/autoload.php contient le chargeur de classe de votre application,
fichier qui est automatiquement généré par Composer :
 À la création du projet Symfony avec la commande composer create-project
symfony/website-skeleton.
 Après l’ajout ou la mise à jour de dépendances du projet.
 Avec la commande composer dump-autoload.
Ce fichier ne comporte pas seulement le chargeur des classes présentes dans le
dossier src, mais également ceux de toutes les dépendances de votre projet, contenues
dans le dossier vendor.
La seule inclusion du fichier vendor/autoload.php est donc suffisante pour garantir
le chargement automatique de toutes les classes de votre projet
(dépendances comprises). Pour vous en rendre compte, vous pouvez analyser le
contenu des scripts en « point d’entrée » du projet, à savoir la console (bin/console) et
le contrôleur frontal (public/index.php). Vous constaterez que, directement ou
indirectement (au travers du fichier config/bootstrap.php), l’inclusion du
fichier vendor/autoload.php est faite.
Précédent
Les environnements
Suivant
La console
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console

 1. Présentation
 2. Les commandes
 a. Lister les commandes disponibles
 b. Exécuter une commande
 3. Les options
 4. Les arguments
 5. L’aide sur les commandes
 6. Exécuter rapidement des commandes

Les outils pour le débogage


Quiz
Routage et contrôleur
L’injection de dépendances
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

La console
1. Présentation
La console est l’outil permettant d’interagir avec une application Symfony en ligne de
commandes (CLI ou Command Line Interface).
Contrairement à l’interface web, accessible au travers du contrôleur frontal, la console
est un outil du développeur et n’est en aucun cas destinée à être utilisée par les
utilisateurs de l’application.
La console est située dans le dossier bin et elle correspond au fichier console. C’est
un fichier PHP, la console peut donc être exécutée de cette manière (dans un
interpréteur de commandes) :
php bin/console

Exécuter le fichier tel quel ne fait qu’afficher une liste de commandes. En


pratique, vous utiliserez différents arguments, dont le plus important est le nom de la
commande.
Note aux utilisateurs de systèmes Unix
Ce fichier comportant un shebang, vous avez la possibilité d’utiliser le
raccourci suivant :
bin/console

Dans ce cas-là, assurez-vous d’avoir, en plus des droits de lecture, les droits
d’exécution sur ce fichier.

2. Les commandes
La console est utilisée pour lancer des commandes. C’est son seul et unique usage.
Chaque commande est chargée d’effectuer une tâche donnée. Ces tâches sont par
exemple le vidage du cache, l’assistance au développeur en
générant automatiquement certains fichiers pour lui, en mettant à jour la base de
données, ou en débogant certaines parties de l’application.
Les commandes disponibles sont trop nombreuses pour pouvoir être listées et décrites
ici, mais vous pouvez facilement obtenir des informations sur celles-ci (cf. section
Lister les commandes disponibles ci-après et section L’aide sur les commandes, plus
loin dans ce chapitre).
a. Lister les commandes disponibles
La console, exécutée sans argument, ou avec l’argument list, affiche la liste des
commandes disponibles :
php bin/console list
Pour une meilleure organisation, certaines commandes sont réparties en espaces de
noms. À titre d’exemple, les commandes liées à la base de données sont préfixées par
« doctrine ».
b. Exécuter une commande
Pour exécuter une commande, il suffit de passer son nom en tant
qu’argument, comme nous l’avons fait précédemment avec list. L’exemple ci-dessous
exécute la commande cache:clear, chargée de vider le cache de Symfony :
php bin/console cache:clear

3. Les options
L’exécution d’une commande peut être complétée par des options. Celles-ci sont
utilisées pour modifier le comportement de la commande. Elles sont préfixées par
deux tirets (« -- ») et peuvent être placées n’importe où dans l’instruction (mais après
« bin/console »).
Illustrons les options avec un exemple : Symfony utilise un répertoire de cache
différent pour chaque environnement (dev, prod, etc.). Lorsque vous exécutez la
commande cache:clear sans aucun argument, c’est le cache de
l’environnement dev qui est vidé. L’environnement peut être modifié grâce à
l’option --env :
php bin/console cache:clear --env="prod"

php bin/console cache:clear --env=prod

php bin/console --env=prod cache:clear

php bin/console cache:clear --env prod

php bin/console cache:clear -e prod

php bin/console cache:clear -e=prod

Toutes les instructions ci-dessus sont équivalentes. Comme vous pouvez le constater,
les options peuvent être placées à n’importe quel endroit après bin/console et sont
assez souples sur la syntaxe, certaines disposant même de raccourcis (ici, -e). Un
raccourci d’option ne comporte qu’une seule lettre et est préfixé d’un seul tiret.
Certaines options sont globales tandis que d’autres sont spécifiques à une commande.

4. Les arguments
Les arguments de commande permettent de modifier le comportement d’une
commande. Contrairement aux options, les arguments ne sont pas nommés ; ils sont
identifiés par leur position. Ils doivent être placés après le nom de la commande :
php bin/console commande argument_1 argument_2

Les options peuvent tout à fait être utilisées conjointement aux arguments :
php bin/console -e prod commande --option_1 argument_1 argument_2
--option_2=valeur

5. L’aide sur les commandes


Nous savons maintenant comment renseigner des options et arguments de commande,
mais encore faut-il connaître ceux qui sont disponibles pour chaque commande. Pour
cela, la commande help est d’une grande aide ; il suffit de l’invoquer avec, en
argument, le nom de la commande à décrire, par exemple cache:clear :

Symfony affiche la syntaxe de la commande :


cache:clear [options]

Contrairement à l’option --env que nous avons utilisée précédemment, certaines


options n’attendent pas de valeur comme par exemple --no-warmup, ce qui sera
d’ailleurs le cas avec la plupart des options. En règle générale, une option sert
seulement à activer ou désactiver un certain comportement.
Chaque option est ensuite décrite en détail au sein d’une liste. En plus des
options spécifiques à la commande, les options générales sont également décrites.
La commande suivante vide le cache Symfony sans le recharger :
php bin/console -e test cache:clear --no-warmup

Lancer cette commande est équivalent à supprimer le dossier de cache d’un


environnement (ici test) dans var/cache.
Si la commande dispose d’arguments, ils seront listés et décrits au même titre que les
options.
L’option --help
Vous pouvez également exécuter la commande help pour une commande donnée en
ajoutant l’option --help (ou son raccourci -h) :
php bin/console cache:clear --help

php bin/console cache:clear -h

6. Exécuter rapidement des commandes


Avec le temps, vous vous rendrez compte que quelques commandes sont
indispensables au développement et sont régulièrement utilisées.
En général, chaque commande est composée de plusieurs « sections ». C’est le cas
de cache:clear.
Vous avez la possibilité d’omettre les fins de section. Ainsi, les deux
commandes suivantes sont équivalentes :
php bin/console cache:clear

php bin/console cac:c


Précédent
Le chargement automatique de classes
Suivant
Les outils pour le débogage
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage

 1. Le profiler Symfony
 2. La fonction dump()

Quiz
Routage et contrôleur
L’injection de dépendances
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Les outils pour le débogage


1. Le profiler Symfony
Le profiler de Symfony est un outil qui s’intègre à l’ensemble des pages de votre
application ou site web dès lors que l’environnement est défini à dev. Le profiler se
présente sous la forme d’une barre de débogage présente en bas des pages de
l’application lorsque cette dernière est sollicitée dans un navigateur web.

Le profiler Symfony affiché sur un contrôleur nouvellement généré et indiquant la


version du framework (4.4.22) à droite.
Le profiler Symfony permet d’avoir un aperçu de toutes les informations relatives à la
requête reçue ainsi qu’à la réponse renvoyée pour une action donnée, comme :
 les erreurs et exceptions survenues lors de l’exécution des traitements ;
 la structure des formulaires invoqués ;
 les informations d’authentification (si les fonctionnalités de sécurité Symfony
sont mises en œuvre) ;
 les requêtes SQL émises vers la base de données.
Ainsi, en cliquant sur l’icône associé aux informations de base de données
(entouré sur le schéma ci-dessous), on peut voir les requêtes SQL émises par
l’application vers la base de données.

Le profiler Symfony affiche alors les informations relatives à l’exécution de la requête


SQL et la requête elle-même :

Le menu de navigation situé sur la gauche permet d’inspecter les autres informations
associées à la requête, comme les données de formulaire, le routage ou bien les
statistiques de performance.
En phase de développement tout comme en phase de mise au point, le profiler
Symfony est un outil essentiel.
2. La fonction dump()
Dans le processus de développement, l’une des principales difficultés est de pouvoir
connaître l’état des données lors de l’invocation d’une requête sur l’application. Il est
toujours possible d’utiliser un débogueur sur son application, mais une alternative
intéressante est par exemple d’afficher dans la réponse la structure de données
obtenue par l’appel d’une fonctionnalité métier.
La fonction dump() de Symfony permet d’afficher dans le profiler la structure, et ce
récursivement, d’un objet PHP. Par exemple, si dans un contrôleur vous invoquez une
fonctionnalité renvoyant un tableau de données ou bien un objet, le passer à la
fonction dump() vous permet d’en consulter le contenu, et ce même si une erreur est
générée.
Par exemple, si la variable $listeDesArticles contient un tableau d’objets PHP, on
peut visualiser la structure complète du tableau et des objets en utilisant la
fonction dump($listeDesArticles) dans le code du contrôleur comme ceci :
public function index(ArticleService $articleService): Response
{
$listeDesArticles = $articleService->rechercherTousLesArticles();
dump($listeDesArticles);
...
}

Le profiler permet d’afficher le contenu passé à la fonction dump() en cliquant sur


l’icône en forme de cible (encadré dans le schéma ci-dessous) :

La fonction dump() est donc un très bon complément au débogueur présent dans
votre environnement de développement intégré, et un très bon substitutif à la
fonction var_dump() de PHP.
Précédent
La console
Suivant
Quiz
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony

 1. Définition
 2. Le répertoire public et le contrôleur frontal
 3. Une requête, une action

Définition des routes


Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Fonctionnement du routage dans


Symfony
1. Définition
Comme nous l’avions vu dans le chapitre sur l’architecture du framework, le
framework n’utilise pas la mise en relation entre l’URL et le système de fichiers pour
servir les pages web.
Les URL sont gérées par le routage (composant Router du framework). Le rôle de ce
composant est de trouver l’action à exécuter pour une requête donnée et pour cela, il
s’appuie sur un ensemble de règles de routage définies par le développeur.
Si vous êtes familier des systèmes de réseaux, vous avez probablement déjà entendu
parler de ce terme, voire du routeur. Dans un contexte applicatif, cela correspond à
l’action sélectionnée pour une requête donnée ; les actions sont contenues dans des
contrôleurs sous forme de méthodes.

2. Le répertoire public et le contrôleur frontal


À la racine de votre projet, vous avez un répertoire nommé public. Il contient tous
vos fichiers publics. Ce sont typiquement des images, des feuilles de style CSS, des
fichiers de scripts JavaScript, et, plus largement, tous les fichiers destinés à être servis
directement par le serveur web.
Pour l’URL http://monjournal.local/robots.txt (ou
bien http://localhost:8000/robots.txt si vous utilisez le serveur web intégré de PHP),
le fichier robots.txt du répertoire public sera servi.
Tandis que pour http://monjournal.local/hello/world, comme le dossier public ne
contient pas de sous-dossier hello avec, à l’intérieur un fichier world, le contrôleur
frontal est invoqué et c’est votre application qui, après avoir analysé la requête HTTP,
peut décider de retourner une réponse, dont le corps serait « Hello world! ».
Pour ce faire, le Kernel fait appel au composant Router (cf. Architecture du
framework - Architecture de Symfony pour un schéma explicatif).

3. Une requête, une action


Le rôle du routage est donc de sélectionner l’action à invoquer selon un ensemble de
règles. Ces règles sont en rapport avec la requête HTTP envoyée par le client, requête
qui est la seule entité permettant au routage de pouvoir faire son choix.
Les principales règles de routage sont :
 la méthode de la requête (GET, POST, etc.)
 le path de l’URL (http://www.website.com/hello/world)
 l’hôte de l’URL (http://www.website.com/hello/world)
Pour le path et l’hôte, un système similaire aux REGEX (expressions régulières) est
disponible et permet de définir des URL dynamiques.
Nous allons maintenant aborder les différentes règles de routage au travers
d’exemples.
Précédent
Quiz
Suivant
Définition des routes
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes

 1. Les différents formats de définition


 2. Les options sur la définition des routes

Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Définition des routes


Une route est une règle de routage. Chaque route est constituée de différentes règles,
et pointe vers une action donnée.
Par défaut, le fichier de routage de l’application est config/routes.yaml, celui-ci étant
complété par les fichiers se trouvant dans config/routes/ ; dans ce dernier, on trouve
notamment le fichier annotations.yaml permettant d’activer la configuration du
routage via des annotations :
controllers:
resource: ../../src/Controller/
type: annotation

kernel:
resource: ../../src/Kernel.php
type: annotation

1. Les différents formats de définition


Comme évoqué dans le chapitre Mise en place d’un projet Symfony, section Structure
de l’application, il existe différentes manières de définir la configuration d’une
application Symfony : via des annotations et/ou via des fichiers de configuration.
Plusieurs formats sont disponibles pour les fichiers de configuration : YAML (YAML
Ain’t Markup Language), XML (eXtensible Markup Language) ou PHP.
En ce qui concerne le routage, ce sont principalement les annotations et les fichiers
au format YAML qui sont utilisés, les formats XML et PHP étant, de manière
général dans Symfony, de plus en plus délaissés.
Les informations obligatoires pour la définition des routes sont :
 le path : il correspond à l’URI de l’application sollicitée ;
 l’action : c’est la méthode d’un contrôleur à laquelle sera associé le path ;
l’exécution sera donc déclenchée par la réception d’une requête à ce path.
Pour la configuration avec des annotations, on utilise @Route sur les actions à
exécuter ; la valeur définie dans cette annotation est le path :
/**
* @Route("/")
*/
public function index(): Response
{
...
}

Lors d’une configuration en YAML, il est nécessaire de donner un nom à la route


(avec les annotations, un nom par défaut est attribué) et le nom de l’action du
contrôleur, en plus du path :
index:
path: /
controller: App\Controller\DefaultController::index

Ici, la clé de premier niveau index représente le nom de la route ; le path et la


spécification de l’action doivent être écrit avec un décalage de plusieurs
espaces (quatre en général) par rapport au nom de la route.
En plus de ces informations obligatoires, d’autres, optionnelles, peuvent être ajoutées.

2. Les options sur la définition des routes


Selon le format de configuration des routes, les options complémentaires de
configuration peuvent revêtir la forme de clés supplémentaires dans le fichier YAML
ou bien d’attributs nommés dans l’annotation @Route. Par exemple, il est possible
d’indiquer le verbe HTTP (GET, POST…) par lequel l’action sera sollicitée. Voici la
définition en annotation pour limiter à une invocation en HTTP GET et POST :
/**
* @Route("/", methods={"GET","POST"})
*/
public function index(): Response
{
...
}

Et la même chose via un fichier YAML :


index:
path: /
controller: App\Controller\DefaultController::index
methods: GET|POST

Les options de configuration du routage seront présentées dans la suite de ce chapitre.


Précédent
Fonctionnement du routage dans Symfony
Suivant
Configurer le path
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes

 1. Les différents formats de définition


 2. Les options sur la définition des routes

Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Définition des routes


Une route est une règle de routage. Chaque route est constituée de différentes règles,
et pointe vers une action donnée.
Par défaut, le fichier de routage de l’application est config/routes.yaml, celui-ci étant
complété par les fichiers se trouvant dans config/routes/ ; dans ce dernier, on trouve
notamment le fichier annotations.yaml permettant d’activer la configuration du
routage via des annotations :
controllers:
resource: ../../src/Controller/
type: annotation

kernel:
resource: ../../src/Kernel.php
type: annotation

1. Les différents formats de définition


Comme évoqué dans le chapitre Mise en place d’un projet Symfony, section Structure
de l’application, il existe différentes manières de définir la configuration d’une
application Symfony : via des annotations et/ou via des fichiers de configuration.
Plusieurs formats sont disponibles pour les fichiers de configuration : YAML (YAML
Ain’t Markup Language), XML (eXtensible Markup Language) ou PHP.
En ce qui concerne le routage, ce sont principalement les annotations et les fichiers
au format YAML qui sont utilisés, les formats XML et PHP étant, de manière
général dans Symfony, de plus en plus délaissés.
Les informations obligatoires pour la définition des routes sont :
 le path : il correspond à l’URI de l’application sollicitée ;
 l’action : c’est la méthode d’un contrôleur à laquelle sera associé le path ;
l’exécution sera donc déclenchée par la réception d’une requête à ce path.
Pour la configuration avec des annotations, on utilise @Route sur les actions à
exécuter ; la valeur définie dans cette annotation est le path :
/**
* @Route("/")
*/
public function index(): Response
{
...
}

Lors d’une configuration en YAML, il est nécessaire de donner un nom à la route


(avec les annotations, un nom par défaut est attribué) et le nom de l’action du
contrôleur, en plus du path :
index:
path: /
controller: App\Controller\DefaultController::index

Ici, la clé de premier niveau index représente le nom de la route ; le path et la


spécification de l’action doivent être écrit avec un décalage de plusieurs
espaces (quatre en général) par rapport au nom de la route.
En plus de ces informations obligatoires, d’autres, optionnelles, peuvent être ajoutées.

2. Les options sur la définition des routes


Selon le format de configuration des routes, les options complémentaires de
configuration peuvent revêtir la forme de clés supplémentaires dans le fichier YAML
ou bien d’attributs nommés dans l’annotation @Route. Par exemple, il est possible
d’indiquer le verbe HTTP (GET, POST…) par lequel l’action sera sollicitée. Voici la
définition en annotation pour limiter à une invocation en HTTP GET et POST :
/**
* @Route("/", methods={"GET","POST"})
*/
public function index(): Response
{
...
}

Et la même chose via un fichier YAML :


index:
path: /
controller: App\Controller\DefaultController::index
methods: GET|POST

Les options de configuration du routage seront présentées dans la suite de ce chapitre.


Précédent
Fonctionnement du routage dans Symfony
Suivant
Configurer le path
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path

 1. Illustration par l’exemple : /hello/world


 2. La notation du contrôleur
 3. Importer des routes depuis d’autres fichiers
 4. Comprendre l’ordre de chargement des routes
 5. Préfixer les routes
 6. Les paramètres de substitution des routes
 7. Les restrictions sur les paramètres
 8. Obtenir des informations sur le routage

Routage par nom de domaine


Le contrôleur
Quiz
L’injection de dépendances
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Configurer le path
1. Illustration par l’exemple : /hello/world
La règle de routage la plus importante est le path ; elle correspond à la variable
superglobale $_SERVER[’PATH_INFO’].
Configurons une route pour un path donné :/hello/world.
Annotations
Selon les définitions du chapitre Architecture du framework - Le modèle de
conception MVC, les actions sont des méthodes de classe dites « contrôleur » et c’est
donc au-dessus des actions que nous devons placer notre règle de routage sous forme
d’annotation :
namespace App\Controller;

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

class WelcomeController extends AbstractController


{
/**
* @Route("/hello/world")
*/
public function hello()
{
return new Response('Hello world!');
}
}

Ici, l’action hello sera exécutée pour toute requête dont le path est /hello/world, cette
règle de routage étant définie grâce à l’annotation @Route.
Pour que le routage du contrôleur soit effectif, il faut qu’il soit activé dans le
fichier config/routes/annotations.yaml. Pour nous faciliter la tâche,
Symfony autorise la configuration de tous les contrôleurs d’une application en
référençant un dossier en tant que ressource ; c’est d’ailleurs la configuration par
défaut présente dans ce fichier :
controllers:
resource: ../../src/Controller/
type: annotation

Une fois les règles de routage configurées, le fait de demander l’URL


http://monjournal.local/hello/world au travers de votre navigateur affiche une page
blanche dont le contenu est « Hello world! ».
Le nom des routes avec les annotations
Nous allons voir ci-dessous avec les autres formats qu’un nom doit être assigné aux
routes. Avec les annotations, ce n’est pas obligatoire car un nom est généré
automatiquement.
Ce nom suit le format : bundle_controleur_action. Dans l’exemple précédent, le
nom de la route est donc : app_welcome_hello.
Si vous souhaitez renseigner manuellement le nom d’une route, il faut utiliser le
paramètre name :
/**
* @Route("/hello/world", name="ma_route")
*/
public function hello()
{
return new Response('Hello world!');
}
}

Le nom des routes est un élément primordial. Pour générer des URL, c’est au travers
du nom que nous référençons une route.
YAML
Les routes peuvent également être définies directement depuis le
fichier config/routes.yaml :
ma_route:
path: /hello/world
controller: App\Controller\WelcomeController::hello

Ici, une route nommée ma_route est définie. Elle est l’équivalent en YAML de la
route configurée en annotation dans la section précédente et
son path est /hello/world.
À la différence des annotations, où l’action à exécuter pour la route est implicite (c’est
la méthode en dessous de l’annotation), en YAML, il faut la référencer. Cela se fait
via la section controller.
XML
Pour utiliser le format XML, il faut créer un fichier config/routes.xml. Voilà la
définition de notre même route :
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd"
>

<route id="ma_route"
path="/hello/world"
controller="App\Controller\WelcomeController::hello />
</routes>

Par rapport au YAML, le fichier est certes moins lisible, mais le format XML a pour
avantage d’être plus structuré, grâce notamment au schéma de validation XSD (XML
Schema Definition) mis à notre disposition. C’est également un langage historique et
toujours massivement utilisé dans l’univers informatique.
PHP
Une dernière possibilité pour configurer ses routes est l’utilisation de PHP. Pour cela,
un fichier config/routes.php est nécessaire et voici, pour la même route que
précédemment, sa structure :
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
use App\Controller\WelcomeController;

return function(RoutingConfigurator $route) {


$routes->add('ma_route', '/hello/world')
->controller([WelcomeController::class, 'hello'])
;
};

2. La notation du contrôleur
Pour la plupart des formats de configuration (à l’exception des annotations), il faut
spécifier un paramètre controller. Son nom peut être assez déstabilisant car il ne
correspond pas à un contrôleur uniquement, mais à l’action d’un contrôleur qu’il
faudra exécuter pour une route donnée.
Ce paramètre doit être défini selon une convention de codage spécifique à Symfony, à
savoir :
Espace_De_Noms\Nom_De_La_Classe_De_Contrôleur::action
À titre d’exemple, App\Controller\AccueilController::index correspond à la
méthode index() du contrôleur AccueilController, de l’espace de noms App\
Controller. Concrètement, le fichier en question serait :
src/Controller/AccueilController.php

3. Importer des routes depuis d’autres fichiers


Les configurations vues précédemment sont assez simples : toutes les routes sont
regroupées au sein du même fichier (config/routes.yaml). Cependant, celui-ci étant
global à toute l’application, il deviendrait rapidement gigantesque et illisible si toutes
nos routes y étaient déclarées.
Pour éviter cela, une bonne pratique est de regrouper ses routes par thèmes dans
différents fichiers, qui seront importés depuis le fichier principal config/routes.yaml.
Par exemple, le fichier config/admin-routes.yml pourrait contenir les routes du
module d’administration de votre application.
Voici les différentes configurations pour l’import de fichiers de routage :
YAML
# Dans le fichier config/routes.yaml
app_admin:
resource: "admin-routes.yml"

Les noms des sections ont peu d’importance (app_admin) ; il faut simplement veiller
à ce qu’ils soient uniques au sein de chaque fichier.
XML
<!-- Dans le fichier config/routes.xml -->
<?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd"
>

<import resource="admin-routes.xml" />


</routes>

PHP
// Dans le fichier config/routes.php
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;

return function(RoutingConfigurator $routes) {


$routes->import("admin-routes.php");
};

4. Comprendre l’ordre de chargement des routes


Au fur et à mesure de l’évolution de l’application, les routes devenant de plus en plus
nombreses, un problème risque de se poser en cas de mauvaise organisation de celles-
ci : les conflits entre plusieurs routes.
Par « conflits entre routes », nous entendons que, pour une requête HTTP donnée, la
seule raison pour laquelle une route ait été sélectionnée plutôt qu’une autre, est le fait
que l’une ait été déclarée avant l’autre.
Nous avons vu qu’il y a différents moyens de configurer ses routes. Cela peut paraître
complexe car elles semblent éparpillées et des fichiers incluent d’autres fichiers, qui
eux-mêmes peuvent en inclure d’autres. Mais en réalité, c’est beaucoup plus simple :
une fois toutes les routes chargées, le framework les regroupe en une seule et même
liste.
Au final, pour trouver la route à utiliser, Symfony va consulter chacune des entrées de
cette liste jusqu’à trouver celle qui satisfait la requête. Une fois celle-ci trouvée, les
routes suivantes ne sont pas parcourues, même si l’une d’entre elles aurait également
pu satisfaire la requête.
Ce comportement est tout à fait normal car avoir plusieurs routes pouvant
correspondre à la même requête n’aurait aucun sens. Ces situations arrivent souvent
de manière fortuite, le développeur n’en a pas conscience et ne le souhaite pas.

5. Préfixer les routes


Une technique pour éviter les conflits de routes consiste à les préfixer. Les
préfixes peuvent être utilisés lors de l’import d’une ressource.
YAML
ma_section:
resource: "admin-routes.yml"
prefix: /admin

XML
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd"
>

<import
resource="admin-routes.xml"
prefix="/ma-section"
/>

</routes>

PHP
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;

return function(RoutingConfigurator $routes) {


$routes->import("admin-routes.php")->prefix("/admin");
};

Annotations
Avec les annotations, une fonctionnalité supplémentaire est disponible : le préfixe
peut être ajouté au niveau du contrôleur, au-dessus de la classe.
/**
* @Route("/admin")
*/
class WelcomeAdminController extends AbstractController
{
/**
* @Route("/{name}")
*/
public function hello($name)
{
return new Response('Hello '.$name.'!');
}
}

Ici, toutes les actions créées auront par défaut le préfixe /admin.
6. Les paramètres de substitution des routes
Nous savons comment configurer un path statique (par exemple /hello/world) mais
cela se révèle cependant assez limité : les applications web sont dynamiques et leurs
URL sont donc amenées à transmettre des données aux applications.
Il serait beaucoup trop fastidieux de configurer manuellement toutes les
possibilités. Dans le cas d’une URL contenant un identifiant correspondant à une
entrée en base de données, il faudrait rajouter une route à chaque insertion en base !
Heureusement, le framework permet la gestion de ce type de routes, notamment grâce
aux paramètres de substitution.
Nous allons repartir de notre path précédent (/hello/world) et y placer un
paramètre de substitution dynamique :
Annotations
class WelcomeController extends AbstractController
{
/**
* @Route("/hello/{name}")
*/
public function hello($name)
{
return new Response('Hello '.$name.'!');
}
}

Ici, l’action hello est exécutée pour le path/hello/world. Le résultat est a priori le
même que pour l’exemple précédent : une page dont le contenu est « Hello world! ».
Cependant, ce path est dorénavant une des infinies possibilités pour cette route.
Ici, world peut être remplacé par n’importe quel autre
nom : /hello/sam ou /hello/bruno sont autant d’autres paths qui satisfont cette règle
de routage.
Cela est rendu possible grâce au paramètre de substitution {name}, qui rend la règle
de routage dynamique. Cette route pourrait être comparée à l’expression régulière
(Regex) ^/hello/(.+)$.
Le paramètre de substitution (name) est ensuite disponible depuis l’action. La
manière la plus simple pour le récupérer est de l’indiquer en paramètre de la méthode
correspondant à l’action. C’est un « paramètre magique », nous en parlons plus loin
dans ce chapitre.
En réalité, la Regex utilisée pour un paramètre de substitution n’est pas (.+) mais ([^/]
+). En effet, Symfony considère le slash (/) comme un caractère spécial : il agit en
tant que délimiteur entre les différents paramètres. Nous utilisons (.+) par souci de
simplicité pour le lecteur, il est toutefois important de noter qu’un paramètre de
substitution ne peut pas comporter de slash. C’est le comportement par défaut du
framework, cela permet d’éviter les conflits entre différentes routes.
YAML
ma_route:
path: /hello/{name}
controller: App\Controller\WelcomeController::hello

XML
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd"
>

<route id="ma_route"
path="/hello/{name}"
controller="App\Controller\WelcomeController::hello" />

</routes>

PHP
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
use App\Controller\WelcomeController;

return function(RoutingConfigurator $route) {


$routes->add('ma_route', '/hello/{name}')
->controller([WelcomeController::class, 'hello'])
;
};

7. Les restrictions sur les paramètres


Les paramètres de substitution acceptent par défaut toutes les valeurs. Il est cependant
fréquent de vouloir gérer plus finement les valeurs acceptées.
Heureusement, des règles spécifiques (sous forme d’expressions régulières) peuvent
être ajoutées pour ces paramètres de substitution, ainsi que d’autres critères, comme la
méthode HTTP.
En partant encore une fois de notre exemple précédent, ajoutons quelques
restrictions :
 La méthode de la requête HTTP doit être GET.
 Le paramètre name doit obligatoirement valoir sam ou bruno.
YAML
ma_route:
path: /hello/{name}
controller: App\Controller\WelcomeController::hello
methods: GET
requirements:
name: sam|bruno

XML
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd"
>

<route id="ma_route"
path="/hello/{name}"
controller="App\Controller\WelcomeController::hello"
methods="GET"
>
<requirement key="name">sam|bruno</requirement>
</route>

</routes>

PHP
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
use App\Controller\WelcomeController;

return function(RoutingConfigurator $route) {


$routes->add('ma_route', '/hello/{name}')
->controller([WelcomeController::class, 'hello'])
->methods('GET')
->requirements(['name' => 'sam|bruno'])
;
};

Annotations
class WelcomeController extends AbstractController
{
/**
* @Route(
* "/hello/{name}",
* requirements={"name"="sam|bruno"},
* methods="{GET}"
* )
*/
public function hello($name)
{
return new Response('Hello '.$name.'!');
}
}

8. Obtenir des informations sur le routage


Lors de la construction du routage d’une application, il est parfois nécessaire d’avoir
une vue d’ensemble des routes de l’application et de vérifier l’absence de conflits sur
les noms de route ou bien sur les paths.
La console possède la commande debug:router, permettant d’afficher la table de
routage de l’application :
Comme vous pouvez le constater, la table de routage indique également des routes
particulières qui n’ont rien à voir avec celles configurées dans l’application ! Ce sont
les routes du Profiler Symfony ; elles sont affichées lorsque la commande est exécutée
avec l’environnement de dev. En ajoutant l’option -e prod, ces routes n’apparaissent
plus.

Précédent
Définition des routes
Suivant
Routage par nom de domaine
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine

 1. Prérequis
 2. Exemple de mise en œuvre
 3. Explications

Le contrôleur
Quiz
L’injection de dépendances
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Routage par nom de domaine


1. Prérequis
Nous avons pour l’instant utilisé l’URL pour configurer nos routes, mais il est
également possible d’utiliser le nom de domaine.
Cette fonctionnalité semble moins utile au premier abord car un site web est
généralement accessible via un seul et unique nom de domaine, mais elle
permet principalement la gestion des sous-domaines. Grâce à elle, les
domaines www.example.com, admin.example.com, fr.example.com ou mobile.exa
mple.com peuvent être interprétés différemment par le routeur du framework.
Cette technique est très répandue sur le Web, le site étant alors « cloisonné » en
plusieurs parties autonomes :
 des sections pour les smartphones/tablettes (par exemple m.example.com,
tablet.example.com ou mobile.example.com),
 des sections pour les différentes langues disponibles (par exemple
fr.example.com ou en.example.com),
 des sections pour chaque espace du site (par exemple admin.example.com ou
payment.example.com).
Cela peut également s’avérer efficace pour les gestionnaires de sites de publication de
contenu (par exemple des blogs), où l’on peut imaginer un zonage par utilisateur, le
sous-domaine étant, par exemple, le nom d’utilisateur du membre en question
(jean.example.com ou lucie.example.com).
Avant de vous aventurer dans le routage par nom de domaine, il vous faut créer les
différents sous-domaines que votre site web utilise. Cette procédure est décrite dans le
chapitre Mise en place d’un projet Symfony - Configurer son serveur web.
2. Exemple de mise en œuvre
Une fois vos sous-domaines configurés au niveau du système, vous êtes prêt à utiliser
la fonctionnalité du routage par nom de domaine.
Configurons une route s’appuyant sur le nom de domaine, le sous-domaine
correspondant à la langue du contenu :
YAML
# Dans le fichier config/routes.yaml

ma_route:
path: /bonjour/{name}
host: {_locale}.mon-projet.local
controller: App\Controller\WelcomeController::hello
requirements:
_locale: fr

my_route:
path: /hello/{name}
host: {_locale}.mon-projet.local
controller: App\Controller\WelcomeController::hello
requirements:
_locale: en

XML
<!-- Dans le fichier config/routes.xml -->

<?xml version="1.0" encoding="UTF-8" ?>


<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd"
>
<route id="ma_route"
path="/bonjour/{name}"
host="{_locale}.mon-projet.local"
controller="App\Controller\WelcomeController::hello"
<requirement key="_locale">fr</requirement>
</route>

<route id="my_route"
path="/hello/{name}"
host="{_locale}.mon-projet.local"
controller="App\Controller\WelcomeController:hello">
<requirement key="_locale">en</requirement>
</route>
</routes>

PHP
<?php

use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
use App\Controller\WelcomeController;

return function(RoutingConfigurator $route) {


$routes->add('ma_route', '/bonjour/{name}')
->controller([WelcomeController::class, 'hello'])
->host('{_locale}.mon-projet.local')
->requirements(['_locale' => 'fr'])
;

$routes->add('my_route', '/hello/{name}')
->controller([WelcomeController::class, 'hello'])
->host('{_locale}.mon-projet.local')
->requirements(['_locale' => 'en'])
;
};

Annotations
class WelcomeController extends AbstractController
{
/**
* @Route(
* "/hello/{name}",
* host="en.mon-projet.local",
* name="my_route"
* )
* @Route(
* "/bonjour/{name}",
* host="fr.mon-projet.local",
* name="ma_route"
* )
*/
public function hello($name)
{
return new Response('Hello '.$name.'!');
}
}

3. Explications
L’exemple précédent définit deux routes dont le sous-domaine et le path sont
différents. Ces routes correspondraient aux URL suivantes :
 http://fr.mon-projet/bonjour/adam
 http://en.mon-projet/hello/adam
Dans la définition de ces routes, un sous-domaine est utilisé pour la langue et la même
action peut être invoquée via une URL en français ou en anglais.
Avant de continuer, nous tenons à préciser que cet exemple existe uniquement à titre
pédagogique, pour illustrer le concept de routage par sous-domaine. Si vous souhaitez
utiliser des URL en plusieurs langues, reportez-vous à l’annexe Les traductions du
présent ouvrage.
Si un host est renseigné, ce n’est plus seulement le path de la requête qui est comparé
avec l’expression régulière de la route, mais aussi le nom de domaine. L’URL
http://en.mon-projet/bonjour/toto renverrait donc une page d’erreur 404, car le path
commençant par/bonjour est associé au sous-domaine fr et non en.
Vous aurez peut-être remarqué une limitation dans l’exemple précédent : nous
utilisons le nom de domaine mon-projet.local, la route ne serait donc pas trouvée sur
le site de production. En effet, ce dernier a forcément un nom de domaine différent de
celui du site de développement.
Pour régler ce problème, vous devez utiliser un paramètre dynamique mais nous ne
nous attarderons pas sur ce sujet, car il est relatif au composant d’injection de
dépendances (Dependency Injection), auquel un chapitre est déjà consacré.
Précédent
Configurer le path
Suivant
Le contrôleur
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur

 1. Modèle de programmation et règles


 2. Travailler avec les services
 3. Utiliser les paramètres de substitution
 a. Paramètres de substitution des routes
 b. Exemples
 4. Travailler avec les URL
 5. Effectuer une redirection
 6. La délégation de requête
 7. La gestion des erreurs et des pages d’erreurs dans les contôleurs
 a. Le contrôleur
 b. La vue

Quiz
L’injection de dépendances
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Le contrôleur
Les contrôleurs sont des éléments centraux car ils contiennent la logique de votre
application. Ils disposent de plusieurs fonctionnalités, mises à leur disposition par
différents services.
Vous pouvez apercevoir ces fonctionnalités en parcourant la classe Symfony\Bundle\
FrameworkBundle\Controller\AbstractController, dont vos contrôleurs héritent.
Chaque méthode fait appel à un service du Service Container, ces méthodes
correspondant à des raccourcis.

1. Modèle de programmation et règles


Un contrôleur est une classe PHP qui doit étendre la classe Symfony\Bundle\
FrameworkBundle\Controller\AbstractController. Cet héritage permet de
bénéficier de mécanismes forts comme la recherche de services dans le
Service Container ainsi que de l’injection de dépendances dans les méthodes. Au-delà
de cette contrainte d’héritage, les classes doivent être nommées avec le
suffixe Controller (WelcomeController, AdminController, etc.).
Les actions du contrôleur sont des méthodes d’instance publique ; leur nom est libre
contrairement aux versions précédentes du framework, où elles
devaient impérativement se terminer par le suffixe Action.

2. Travailler avec les services


Tous les services sont accessibles depuis le contrôleur. Pour y accéder, il suffit
d’invoquer la méthode get(’id_du_service’).
public function index()
{
$monService = $this->get('mon_service');
// Récupère le service dont l'identifiant est 'mon_service'
// ...
}

Cette méthode est aussi bien capable de récupérer les services du framework que vos
services personnalisés. Pour plus d’informations sur les services, reportez-vous au
chapitre L’injection de dépendances.
3. Utiliser les paramètres de substitution
Jusqu’à présent, vos actions étaient des méthodes sans arguments. Sachez que
Symfony dispose d’une fonctionnalité de « paramètres magiques ».
Si un paramètre possède un nom ou un typage d’objet particulier, Symfony sera
capable d’injecter une certaine valeur (ou objet) lors de l’invocation de l’action. Ainsi,
le code de l’action se retrouve allégé.
a. Paramètres de substitution des routes
Nous avons déjà utilisé cette fonctionnalité précédemment : lorsqu’une route
possédait un paramètre de substitution, nous le placions en paramètre de l’action pour
le récupérer à la volée :
/**
* @Route("/hello/{name}")
*/
public function hello($name)
{
return new Response('Hello '.$name.'!');
}

La technique de l’exemple ci-dessus est un raccourci permettant d’extraire la valeur


du paramètre de substitution name depuis la requête.
b. Exemples
Imaginons une route avec un paramètre de substitution correspondant à un identifiant
d’article. Tous les exemples suivants produiraient le même résultat :
<?php

namespace App\Controller;

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

class DefaultController extends AbstractController


{
/**
* @Route("/article/{id}")
* @Template()
*/
public function index ()
{
$id = $this
->getRequestStack()
->getCurrentRequest()
->attributes
->get('id');
// ...
}
}

Récupération de l’identifiant depuis la requête, obtenue grâce au


service request_stack.
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Annotation\Template;

class DefaultController extends AbstractController


{
/**
* @Route("/article/{id}")
* @Template()
*/
public function index(Request $request)
{
$id = $request->attributes->get('id');
// ...
}
}

La requête est injectée automatiquement à l’invocation grâce au typage d’objet.


<?php

namespace App\Controller;

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

class DefaultController extends AbstractController


{
/**
* @Route("/article/{id}")
* @Template()
*/
public function index($id)
{
// ...
}
}

Le paramètre de substitution peut être injecté directement par Symfony.

4. Travailler avec les URL


Des URL peuvent être générées depuis un contrôleur grâce à la
méthode generateUrl().
public function index()
{
$url = $this->generateUrl('ma_route');
// ...
}

Optionnellement, cette méthode accepte un tableau de paramètres, représentant


les paramètres de substitution pris en charge par la route :
public function index()
{
$url = $this->generateUrl('hello', [
'name' => 'pierre',
'param_2' => 'valeur2',
]);
// ...
}

Des paramètres libres (non présents dans la définition de la route) peuvent être
ajoutés. Ils seront donc ajoutés en fin d’URL (par exemple /hello/pierre?
param_2=valeur2).
Vous devrez toujours utiliser la génération d’URL plutôt qu’écrire vos URL
manuellement. Ainsi, lorsque vous modifiez le path d’une de vos routes, vous ne
prenez pas le risque de casser certaines URL en dur.

5. Effectuer une redirection


Générer une URL est généralement utile pour effectuer une redirection. Ainsi, la
méthode generateUrl() peut facilement être couplée à une autre méthode : redirect().
public function index()
{
return $this->redirect($this->generateUrl('ma_route'));
}

Par défaut, le code HTTP de la redirection est 302, mais ce code est modifiable via le
second paramètre de la méthode redirect().
public function index()
{
return $this->redirect($this->generateUrl('ma_route'), 301);
}

6. La délégation de requête
Il existe une alternative à la redirection HTTP : la délégation de requête. Il s’agit
d’exécuter une seconde action directement depuis le contrôleur courant.
La principale différence avec la redirection HTTP est que la redirection interne est
invisible pour le client (navigateur).
Tandis que la redirection HTTP nécessite le renvoi d’une réponse spécifique (de code
30X), qui génère à son tour une seconde requête, la redirection interne est gérée par
Symfony ; la réponse renvoyée au client correspond à celle de la seconde action.
public function index()
{
$response = $this->forward(
'App\Controller\WelcomeController::hello',
[
'name' => 'bobby',
]
);
return $response;
}

La méthode forward() effectue une requête interne, et la réponse générée est


récupérée et renvoyée à son tour depuis l’action principale via l’instruction return.

7. La gestion des erreurs et des pages d’erreurs dans


les contôleurs
Quand des erreurs surviennent, il est important de ne pas seulement afficher un
message d’erreur à l’utilisateur, mais de respecter le protocole HTTP en renvoyant le
code d’erreur HTTP approprié.
Ainsi, pour une page inexistante, il convient de renvoyer une réponse ayant le
code 404. Si l’utilisateur n’est pas autorisé à accéder à une ressource, le
statut 403 doit être utilisé.
a. Le contrôleur
Ce code HTTP peut être ajouté à l’objet Symfony\Component\HttpFoundation\
Response (en tant que deuxième argument du constructeur ou via la
méthodesetStatusCode()).
Pour les codes HTTP correspondant à des erreurs (commençant par 4 ou 5), il existe
néanmoins un raccourci. Dans ces cas-là, en plus de renseigner le code d’erreur, il
faut bien souvent gérer le corps de la réponse (l’affichage) en y intégrant une page
d’erreur. Voici comment gérer ce type de réponses :
<?php

namespace App\Controller;

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

class DefaultController extends AbstractController


{
/**
* @Route("/article/{id}")
* @Template()
*/
public function index($id)
{
// Recherche de l'article en base de données avec
// l'identifiant $id
$article = // ...

// Si l'article n'a pas été trouvé, on lance une erreur 404


// par l'intermédiaire d'une exception
if (!$article) {
throw $this->createNotFoundException(
"Cet article n'existe pas."
);
}

// Sinon, affichage de l'article...


}
}

Pour renvoyer une page d’erreur, il faut retourner l’exception correspondante. Ici, la
méthode createNotFoundException() n’est qu’un raccourci retournant une instance
de Symfony\Component\HttpKernel\Exception\NotFoundHttpException. Le
code suivant serait équivalent :
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpKernel\Exception\HttpException;

class DefaultController extends AbstractController


{
/**
* @Route("/article/{id}")
* @Template()
*/
public function index($id)
{
// Recherche de l'article en base de données
$article = // ...

if (!$article) {
throw new HttpException(
404,
"Cet article n'existe pas."
);
}

// ...
}
}

Ce raccourci de contrôleur existe car le code d’erreur 404 est très utilisé dans une
application web, mais pour les autres codes d’erreurs, vous devez instancier et lancer
une Symfony\Component\HttpKernel\Exception\HttpException vous-même. Le
premier argument est le code de l’erreur, le second est un message d’erreur.
b. La vue
Lorsque vous lancez des exceptions, vous remarquez une page avec un
fantôme. N’ayez crainte, cette dernière n’est pas destinée aux utilisateurs finaux mais
affichée seulement lors du développement, car elle comporte de nombreuses
informations sur l’erreur, dont une trace des méthodes invoquées avant l’incident.
Une page d’erreur lors du développement.
Il est néanmoins essentiel pour vous de pouvoir outrepasser cette page pour accéder à
la page d’erreur de production.
La technique la plus simple consiste à utiliser l’application en environnement de
production ; pour cela, il faut modifier la valeur de la variable APP_ENV pour
qu’elle soit égale à prod. En cas d’erreur au rafraîchissement de la page, pensez à
vider le cache avec la commande php bin/console cache:clear -e prod.
Une fois l’environnement de production activé, vous accédez à la page d’erreur par
défaut de Symfony :

Ce type de page n’est probablement pas adapté à votre application mais il est
heureusement très simple à personnaliser.
Si vous n’êtes pas familier avec Twig et les templates, nous vous conseillons de lire le
chapitre Les templates avec Twig avant d’entamer la personnalisation de vos pages
d’erreurs.
Pour personnaliser vos pages d’erreurs, il vous faut tout d’abord installer
le TwigBridge dans le projet, à l’aide de la commande suivante :
composer require symfony/twig-pack

Ensuite, il est nécessaire de surcharger le template par défaut. Ce dernier étant


contenu au sein du bundle Twig (TwigBundle), vous devez créer le fichier
suivant : templates/bundles/TwigBundle/Exceptions/error.html.twig.
À titre d’exemple, voici ce qu’il pourrait contenir :
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>Une erreur est survenue.</title>
</head>
<body>
<h1>Erreur de type {{ status_code }}
({{ status_text }})</h1>
<p>
Une exception a été lancée depuis l'application, elle
comportait le message suivant : "{{ exception.message }}"
</p>
</body>
</html>

Une page d’erreur ressemble maintenant à ceci :


Pour créer un template spécifique à un code d’erreur, il est possible de suffixer le nom
du fichier par la valeur du code d’erreur, par exemple :
 templates/bundles/TwigBundle/Exception/error404.html.twig pour les
erreurs 404.
 templates/bundles/TwigBundle/Exception/error503.html.twig pour les
erreurs 503.
Il est également recommandé de créer des logs lorsqu’une erreur survient. Pour plus
d’informations, reportez-vous au chapitre Journalisation et surveillance avec Symfony
- Générer des journaux avec Monolog.
Précédent
Routage par nom de domaine
Suivant
Quiz
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control

 1. Apports dans une architecture applicative


 2. IoC et injection de dépendances

L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Le modèle de conception IoC :


Inversion Of Control
Le modèle de conception (ou Design Pattern) Inversion Of Control est un
modèle d’architecture partant du principe que le flot d’exécution des différentes
instructions n’est pas maîtrisé par l’application elle-même mais par un
élément logiciel fourni par un framework. Dans le cadre de Symfony, c’est le Service
Container qui apporte cette fonctionnalité.

1. Apports dans une architecture applicative


Dans une application traditionnelle, c’est-à-dire sans framework, les différents
composants applicatifs sont liés entre eux par le programmeur ; c’est lui qui doit
prendre à sa charge dans son code la communication entre les éléments du modèle, de
la vue et du contrôleur. Cela implique de fournir une quantité assez conséquente de
« code technique » en plus du code fonctionnel, nuisant à la productivité des
développeurs.
Les frameworks de développement actuels apportent tous plus ou moins un conteneur
logiciel qui va se charger de l’inversion de contrôle. Il faut voir l’inversion de
contrôle comme un mécanisme où le flot d’exécution est déjà tout tracé (bien que
configurable) et où les composants applicatifs vont venir se brancher aux
emplacements appropriés.

Le principal avantage de l’apport d’un conteneur est donc le contrôle du flot


d’exécution ; ainsi le développeur peut se concentrer sur l’implémentation des
fonctionnalités des composants sans avoir besoin de se soucier de la communication
entre les composants. De plus, l’exploitation des fonctionnalités fournies par le
framework est facilitée car elles sont apportées par le conteneur.

2. IoC et injection de dépendances


L’inversion de contrôle va permettre de bénéficier d’une fonctionnalité essentielle :
l’injection de dépendances. Dans la mesure où le conteneur du framework contrôle le
flot d’exécution et l’accès aux fonctionnalités pour les composants, il va être
responsable de :
 Mettre les composants en relation. Quand un composant a besoin d’un autre, c’est
le conteneur du framework qui lui fournit ;
 Fournir les fonctionnalités aux composants.
Précédent
Quiz
Suivant
L’injection de dépendances
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances

 1. Principes de base
 2. Les différentes techniques d’injection de dépendances
 a. L’injection de dépendances par le constructeur
 b. L’injection de dépendances par setter (mutateur)
 c. L’injection de dépendances par propriété
 3. Les avantages

Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

L’injection de dépendances
1. Principes de base
L’idée qui sous-tend l’injection de dépendances est simple : au lieu de laisser une
classe aller chercher elle-même les dépendances dont elle a besoin, ce processus est
externalisé.
La classe X, allégée de cette tâche, pourra se concentrer sur sa fonction première.
Découvrons ci-après les différents types d’injection.

2. Les différentes techniques d’injection de


dépendances
a. L’injection de dépendances par le constructeur
Cette injection consiste à passer la dépendance lors de l’instanciation de la classe :
<?php

class X
{
private $db;

public function __construct(\PDO $db)


{
$this->db = $db;
}

public function foo()


{
$stmt = $this->db->query('SELECT...');
// ...
}

// ...
}

Ce type d’injection convient parfaitement aux dépendances obligatoires.


Comme vous pouvez le constater, le TypeHint (typage d’objet) est PDO. Cela permet
d’être sûr que l’objet passé en argument lors de l’instanciation est un objet PDO, ou
un objet héritant de PDO.
Utiliser un TypeHint s’apparente à passer un « contrat ». Ici, peu importe le contexte,
on sait qu’un objet de la classe X aura toujours à sa disposition un objet PDO ou
héritant de PDO sur lequel il pourra compter. C’est dans sa définition même, la classe
X ne peut être instanciée sans objet PDO. Cela n’aurait pas été le cas avec la
technique du Singleton, où la classe X dépendrait du contexte.
b. L’injection de dépendances par setter (mutateur)
L’injection via les setters peut être faite à n’importe quel moment de la vie de l’objet.
<?php

class X
{
private $db;

public function setDb(\PDO $db)


{
$this->db = $db;
}

public function foo()


{
if ($this->db) {
$stmt = $this->db->query('SELECT...');

//...
}
}

// ...
}

Ce type d’injection est plutôt conseillé pour les dépendances optionnelles, ou lorsque
l’on souhaite se donner la possibilité de changer l’implémentation d’une dépendance
obligatoire.
c. L’injection de dépendances par propriété
Ce type d’injection est moins recommandé, car il n’existe aucun moyen de
vérifier que le type de la dépendance injectée est correct. Nous vous la présentons tout
de même :
<?php

class X
{
public $db;

public function foo()


{
if ($this->db) {
$stmt = $this->db->query('SELECT...');

//...
}
}

// ...
}

3. Les avantages
Ces techniques d’injection de dépendances ont un principal avantage : la flexibilité.
La classe, ayant délégué cette tâche au contexte, gagne en portabilité. Elle peut
maintenant fonctionner sous une multitude de contextes, dans la mesure où ceux-ci
respectent le « contrat » stipulé au travers de son TypeHint.
Cela permet par exemple d’utiliser la classe X dans une autre application, avec en
dépendance une autre classe que MyPDO (mais respectant toujours ce contrat
« instance de PDO »), ou d’utiliser un « mock object » (objet bouchon) lors de ses
tests unitaires, pour les focaliser sur la logique métier de cette classe X. Plus
largement, avec l’injection de dépendances, les classes contiennent uniquement des
contraintes ou possibilités d’injection. Les injections en tant que telles sont faites à
l’exécution du code.
Heureusement, Symfony nous simplifie cette tâche. Nous allons pouvoir définir des
injections pour des objets de nos classes grâce à des fichiers de configuration et, à
l’exécution, nous pourrons accéder à ces objets prêts à l’emploi.
Précédent
Le modèle de conception IoC : Inversion Of Control
Suivant
Le Service Container
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container

 1. Les services
 2. Explications au travers d’un service X

Créer un service et configurer ses injections


Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Le Service Container
Nous avons évoqué cette entité lors du chapitre sur l’architecture du framework. On
sait que le Service Container contient des « services ».
Son rôle est d’absorber la complexité que nous venons d’identifier : définir la manière
d’instancier des classes passives, ne contenant que des règles et
possibilités d’injection. Cette définition est ce qu’on appelle un service.
Concrètement, le Service Container est une classe dont chacune des méthodes
retourne un service.

1. Les services
Un service est un objet PHP prêt à être utilisé et ayant une tâche générique et unique.
Il peut servir à « persister » des données en base, envoyer des e-mails ou écrire des
logs.
Un service est accessible au travers du Service Container et le framework Symfony
possède par défaut un certain nombre de services.

2. Explications au travers d’un service X


Penchons-nous maintenant sur la création d’un service X donné, basé sur notre classe
X (cf. au début de ce chapitre pour la définition de la classe).
Tous les services étant définis dans le Service Container et accessibles uniquement au
travers de celui-ci, la création de ce service sera faite dans la classe représentant le
Service Container :
<?php

class ServiceContainer
{
private $services = array();
public function getX()
{
if (isset($this->services['x'])) {
return $this->services['x'];
}

$pdo = new PDO('mysql:host=localhost', 'bilal', 'pass');

return $this->services['x'] = new X($pdo);


}
}

Le Service Container prend en charge toute la complexité, il est chargé


d’injecter l’objet PDO au service X.
À l’exécution, récupérer le service X est aussi simple que d’invoquer la
méthode getX du Service Container.
Comme vous pouvez le constater, le service X est créé à la volée lorsqu’il est
demandé la première fois, puis enregistré dans une propriété services. Lors des
prochaines invocations, ce même objet sera retourné directement. Ce comportement
est identique à celui du Singleton : il n’y a qu’une seule instanciation.
Le Service Container est une classe automatiquement générée par Symfony. Nous
allons maintenant voir comment le configurer.
Précédent
L’injection de dépendances
Suivant
Créer un service et configurer ses injections
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments
Tout déplier | Tout replier
Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container

 1. Les services
 2. Explications au travers d’un service X

Créer un service et configurer ses injections


Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Le Service Container
Nous avons évoqué cette entité lors du chapitre sur l’architecture du framework. On
sait que le Service Container contient des « services ».
Son rôle est d’absorber la complexité que nous venons d’identifier : définir la manière
d’instancier des classes passives, ne contenant que des règles et
possibilités d’injection. Cette définition est ce qu’on appelle un service.
Concrètement, le Service Container est une classe dont chacune des méthodes
retourne un service.
1. Les services
Un service est un objet PHP prêt à être utilisé et ayant une tâche générique et unique.
Il peut servir à « persister » des données en base, envoyer des e-mails ou écrire des
logs.
Un service est accessible au travers du Service Container et le framework Symfony
possède par défaut un certain nombre de services.

2. Explications au travers d’un service X


Penchons-nous maintenant sur la création d’un service X donné, basé sur notre classe
X (cf. au début de ce chapitre pour la définition de la classe).
Tous les services étant définis dans le Service Container et accessibles uniquement au
travers de celui-ci, la création de ce service sera faite dans la classe représentant le
Service Container :
<?php

class ServiceContainer
{
private $services = array();

public function getX()


{
if (isset($this->services['x'])) {
return $this->services['x'];
}

$pdo = new PDO('mysql:host=localhost', 'bilal', 'pass');

return $this->services['x'] = new X($pdo);


}
}

Le Service Container prend en charge toute la complexité, il est chargé


d’injecter l’objet PDO au service X.
À l’exécution, récupérer le service X est aussi simple que d’invoquer la
méthode getX du Service Container.
Comme vous pouvez le constater, le service X est créé à la volée lorsqu’il est
demandé la première fois, puis enregistré dans une propriété services. Lors des
prochaines invocations, ce même objet sera retourné directement. Ce comportement
est identique à celui du Singleton : il n’y a qu’une seule instanciation.
Le Service Container est une classe automatiquement générée par Symfony. Nous
allons maintenant voir comment le configurer.
Précédent
L’injection de dépendances
Suivant
Créer un service et configurer ses injections
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections

 1. Créer un service
 2. Les différents types d’injections dans un service Symfony
 a. Injection par constructeur
 b. Injection par méthode
 c. Injection par propriété
 3. Injection automatique avec l’autowiring
 4. Les services « lazy »

Le chargement automatique de services


Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Créer un service et configurer ses


injections
1. Créer un service
Le moyen le plus simple de créer un service est de le configuer via le
fichier config/services.yaml. Les services doivent être définis sous la clé services :
# config/services.yaml

services:
my_pdo:
class: PDO
arguments: ['mysql:host=localhost', 'bilal', 'pass']

Cette configuration crée un service my_pdo, qui est une instance de PDO (clé class).
Les arguments passés au constructeur sont définis sous la
clé arguments. Concrètement, avec cette configuration, le service my_pdo est créé
de cette manière :
<?php

$service = new PDO('mysql:host=localhost', 'bilal', 'pass');


2. Les différents types d’injections dans un service
Symfony
a. Injection par constructeur
Une fois le service my_pdo configuré, il peut être injecté dans un autre service, via le
constructeur, par exemple (cf. L’injection de dépendances par le constructeur,
précédemment dans ce chapitre) :
# config/services.yaml

services:
my_pdo:
class: PDO
arguments: ['mysql:host=localhost', 'bilal', 'pass']
x:
class: X
arguments: @my_pdo

Le service X est alors créé comme ceci :


<?php

$myPDO = $serviceContainer->get('my_pdo');

$service = new X($myPDO);

Ici, le Service Container récupère le service my_pdo, puis le passe en argument lors
de l’instanciation de X. Le Service Container injecte un service nommé my_pdo (et
non la chaîne de caractères @my_pdo) car @ est un caractère spécial signifiant que
ce qui suit est le nom d’un service.
b. Injection par méthode
On peut également demander au Service Container d’invoquer une méthode de l’objet
après qu’il ait été créé, pour, par exemple, injecter une dépendance (un autre service) :
# config/services.yaml

services:
my_pdo:
class: PDO
arguments: ['mysql:host=localhost', 'bilal', 'pass']
x:
class: X
calls:
- [setDb, [@my_pdo]]

Avec cette configuration, le Service Container crée le service comme ceci :


<?php

$service = new X();

$myPDO = $serviceContainer->get('my_pdo');
$service->setDb($myPDO);

Une fois la classe instanciée, la méthode setDb est invoquée avec, en argument, le
service my_pdo (cf. L’injection de dépendances par setter (mutateur), précédemment
dans ce chapitre).
En ajoutant un point d’interrogation entre l’arobase et le nom du service, on peut
également rendre l’injection optionnelle : la dépendance n’est pas injectée si le service
correspondant n’existe pas. Si le service my_pdo n’existait pas, avec @?my_pdo, la
méthode ne serait pas invoquée du tout, tandis qu’avec @my_pdo, une exception
serait lancée.
c. Injection par propriété
Comme nous l’avons vu (cf. L’injection de dépendances par propriété, précédemment
dans ce chapitre), il est également possible de faire une injection par propriété, même
s’il est préférable d’utiliser le constructeur ou un mutateur :
# config/services.yaml

services:
my_pdo:
class: PDO
arguments: ['mysql:host=localhost', 'bilal', 'pass']
x:
class: X
properties:
db: @my_pdo

La propriété publique db contient le service my_pdo.

3. Injection automatique avec l’autowiring


L’autowiring est une technique visant à faciliter l’injection de dépendances. Grâce à
cette dernière, Symfony peut injecter des dépendances en se basant sur le typehint de
vos classes. La définition des services s’en retrouve ainsi allégée.
Autowiring par constructeur
Reprenons notre précédent exemple sur l’injection par constructeur. Il est possible de
le remplacer par cette configuration :
# config/services.yaml

services:
my_pdo:
class: PDO
arguments: ['mysql:host=localhost', 'bilal', 'pass']
x:
class: X
autowire: true

Ici, nous utilisons la directive autowire plutôt que de spécifier les arguments
manuellement. Lors de la création du Service Container, Symfony analyse la
signature du constructeur de la classe X. Étant donné que cette dernière requiert un
argument de type PDO, un service pour cette classe est recherché et injecté : c’est le
service my_pdo.
Autowiring par setter (mutateur)
Pour utiliser l’autowiring avec des setters, il faut les passer explicitement :
# config/services.yaml

services:
my_pdo:
class: PDO
arguments: ['mysql:host=localhost', 'bilal', 'pass']
x:
class: X
autowire: [ 'setDb' ]

Pour cette configuration, en plus du constructeur, Symfony analyse la signature de la


méthode setDb pour y injecter la dépendance appropriée (my_pdo). Plutôt que de
lister toutes les méthodes manuellement, vous pouvez aussi utiliser un « wildcard » :
# config/services.yaml

services:
x:
class: X
autowire: [ 'set*' ]

Grâce à ce dernier, toutes les méthodes commençant par set seront analysées par le
système d’autowiring.

4. Les services « lazy »


Nous savons qu’un service n’est instancié que lorsque nous en avons besoin, c’est-à-
dire au moment où nous y accédons via le Service Container.
Cependant, qu’en est-il de ses dépendances ? Si nous récupérons un service X,
possédant des dépendances avec Y et Z, ces dernières sont instanciées et
injectées même si nous ne les utilisons pas, le service X n’a pas forcément besoin des
dépendances Y et Z pour les tâches que nous voulons effectuer. Si l’instanciation du
service Y ou Z implique une action lourde (par exemple, une connexion à une base de
données), elle sera effectuée inutilement (la dépendance n’étant pas utilisée).
Pour optimiser ce type de services, Symfony propose les « lazy services »
(services paresseux), qui sont instanciés seulement avant d’être utilisés.
Pour utiliser les lazy services, vous devez ajouter la recette Flex proxy-manager-
bridge avec la commande :
composer require proxy-manager-bridge

Dorénavant, l’option lazyest disponible lors de la définition d’un service :


# config/services.yaml

services:
my_pdo:
class: PDO
arguments: ['mysql:host=localhost', 'bilal', 'pa$S']
lazy: true

Ici, le service my_pdo ne sera instancié qu’au moment où l’application essaye


d’invoquer l’une de ses méthodes. Au niveau de son utilisation, tout est complètement
transparent, étant donné que vous pouvez utiliser ce service comme un service
classique.
Précédent
Le Service Container
Suivant
Le chargement automatique de services
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI
Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services

 1. La configuration
 2. Exemple d’utilisation

Créer des services réutilisables et distribuables


Quiz
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes
Le chargement automatique de
services
Le chargement automatique de services est une fonctionnalité apparue dans Symfony
avec la version 3.4, mais c’est Symfony 4 qui en démocratise l’usage.

1. La configuration
Le principe est assez simple : toutes les classes que vous créez (à quelques
exceptions près) sont enregistrées en tant que service, et donc injectables les unes
dans les autres. Vous pouvez aussi injecter des services Symfony dans celles-ci. Ce
comportement est rendu possible par la configuration par défaut fournie dans le
fichier config/services.yaml, dont voici le contenu avec ladite configuration mise en
gras :
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in
# your services.
autoconfigure: true # Automatically registers your services as
# commands, event subscribers, etc.

# makes classes in src/ available to be used as services


# this creates a service per class whose id is the fully-qualified
# class name
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
- '../src/Tests/'

# controllers are imported separately to make sure services can be


# injected as action arguments even if you don't extend any base
# controller class
App\Controller\:
resource: '../src/Controller/'
tags: ['controller.service_arguments']

Nous voyons que toute classe déclarée dans le répertoire src/ est déclarée en tant que
service dont l’identifiant est le FQCN (Fully-Qualified Class Name), le nom de classe
pleinement qualifié. Cependant certains sous-dossiers de src/ sont exclus car ils
contiennent des classes qui ne doivent pas être gérées par le Service Container ; c’est
le cas des entités Doctrine, par exemple.
Les dernières lignes de configuration de ce fichier servent quant à elles à
permettre l’injection de services dans les contrôleurs :
App\Controller\:
resource: '../src/Controller/'
tags: ['controller.service_arguments']

Les services seront donc injectables en paramètres des méthodes d’action des
contrôleurs.

2. Exemple d’utilisation
Prenons un service nommé ArticleService qui reçoit par injection le service Doctrine
de Symfony et qui est injecté en paramètre des actions d’un contrôleur
nommé ArticleController. L’organisation des composants pourrait être schématisée
comme ceci :

Voici comment les choses sont codées dans la classe ArticleService :


namespace App\Model;

use App\Entity\Article;
use Doctrine\ORM\EntityManagerInterface;

class ArticleService
{
// Attribut qui référence l'EntityManager de Doctrine
private $em;

// Constructeur qui va recevoir par injection l'EntityManager de


// Doctrine.
// Le type 'EntityManagerInterface' permet à Symfony de savoir quel
// objet injecter !
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}

public function ajouterArticle(Article $article)


{
...
}
...
}

Ici, le type EntityManagerInterface représente le service Doctrine à utiliser pour


manipuler les entités. Avec cette déclaration en tant que paramètre du constructeur,
l’injection se fait automatiquement ; il s’agit d’une injection par le constructeur.
Concernant la classe ArticleController, l’injection se fait sur les méthodes d’action :
namespace App\Controller;

use App\Entity\Article;
use App\Entity\Auteur;
use App\Form\ArticleType;
use App\Model\ArticleService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class ArticleController extends AbstractController


{
/**
* @Route("/article/tous", name="article_tous")
*/
public function tous(ArticleService $articleService): Response
{
...
}
...
}

L’injection du service ArticleService se fait en utilisant une injection par méthode.


Avec cette approche, le développement se fait assez naturellement et il n’est plus
nécessaire de se poser la question de savoir comment nous pouvons obtenir tel ou tel
objet de l’application ; c’est l’une des fonctionnalités les plus intéressantes de
Symfony.
Précédent
Créer un service et configurer ses injections
Suivant
Créer des services réutilisables et distribuables
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables

 1. Le concept de bundle
 a. Créer un bundle
 b. Arborescence du bundle
 2. La définition de services dans un bundle
 3. La configuration
 a. Définir une arborescence
 b. Les différentes étapes du traitement de la configuration
 c. Récupérer la configuration validée
 4. Les « Compiler Passes »
 a. Concept
 b. Les tags
 c. Le Compiler Pass

Quiz
Les templates avec Twig
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Créer des services réutilisables et


distribuables
1. Le concept de bundle
Avant Symfony 4, tout le code d’une application devait être organisé dans des
bundles. L’objectif de cette approche consistait à faciliter la réutilisation et la
distribution de fonctionnalités. Bien que ce système d’organisation ne soit plus
obligatoire, il n’en reste pas moins toujours utilisable si vous souhaitez développer
des fonctionnalités et les redistribuer.
a. Créer un bundle
Pour créer des services redistribuables, il est tout d’abord nécessaire de créer un
bundle et la structure de dossiers et fichiers allant avec. La notation des bundles suit
des conventions assez précises ; ainsi, si vous souhaitez développer un ensemble de
fonctionnalités d’administration, vous nommez le
bundle MonEntrepriseAdminBundle, conformément à ces conventions. Voici la
structure de base du bundle, constituée d’une classe dans l’arborescence de dossiers
indiquée en commentaire en tête du code :
// src/MonEntreprise/AdminBundle/MonEntrepriseAdminBundle.php
namespace App\MonEntreprise\AdminBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class MonEntrepriseAdminBundle extends Bundle


{

Pour activer ce bundle, il est ensuite nécessaire de le déclarer dans le


fichier config/bundles.php avec la syntaxe suivante :
return [
// ...
App\MonEntreprise\AdminBundle\MonEntrepriseAdminBundle::class
=> ['all' => true],
];

b. Arborescence du bundle
Une fois le bundle créé, il est nécessaire d’ajouter des répertoires pour en organiser le
code. Voici la structure type recommandée (bien que certains répertoires puissent
s’avérer inutiles en fonction des fonctionnalités que vous développez).
 Controller/ : contient les contrôleurs du bundle.
 DependencyInjection/ : contient des classes d’injection de dépendances ; nous y
reviendrons justement dans la suite de cette section.
 Resources/config/ : contient la configuration du bundle, par exemple un
fichier routing.yaml si le routage de ce bundle est déclaré au format YAML.
 Resources/views/ : contient les templates Twig.
 Tests/ : contient les classes de test du bundle.

2. La définition de services dans un bundle


Avant de continuer, il est primordial de garder en mémoire que le seul et unique but
du composant DependencyInjection est de créer des services au sein du Service
Container. Et ce n’est pas la multitude de possibilités offertes par ce composant, et
que nous allons évoquer plus loin, qui doit vous faire perdre de vue ce point : la
finalité est de mettre à disposition du développeur un objet container (ou Service
Container) contenant des services, et rien d’autre.
Pour créer notre Service Container, Symfony utilise un ContainerBuilder, dont le
rôle est de charger nos définitions de services depuis différentes sources : depuis la
section services dans config/services.yaml mais également depuis nos bundles,
comme nous allons le voir.
Une fois les définitions chargées dans le ContainerBuilder, un Container en est
extrait puis enregistré dans un fichier de cache, de manière à ce que ce
processus, souvent assez gourmand en ressources, ne soit pas répété à chaque requête.
Ce fichier est contenu dans votre répertoire de cache var/cache/dev (pour
l’environnement de développement, par exemple) et
nommé appDevDebugProjectContainer.php, par exemple (toujours selon votre
environnement et mode de debug).
Chacun des bundles peut posséder un fichier de configuration de services :
src/MonEntreprise/AdminBundle/Resources/config/services.[format]
Le format peut être yml, xml ou php.
Pour créer un service depuis un bundle, il suffit de le définir sous la clé services de ce
fichier, comme nous le faisons avec config/services.yaml :
# src/MonEntreprise/AdminBundle/Resources/config/services.yaml

services:
mon_service:
class: src/MonEntreprise/AdminBundle/MaClasse

3. La configuration
La spécificité d’une extension de container est sa portabilité, notamment grâce à un
système de configuration. La configuration permet de modifier dynamiquement les
services définis par un bundle, de façon beaucoup plus structurée et conventionnelle
qu’avec des paramètres de container seuls.
Reprenons le service my_pdo de notre exemple précédent pour mieux saisir l’intérêt
de la configuration :
# src/MonEntreprise/AdminBundle/Resources/config/services.yaml

services:
my_pdo:
class: PDO
arguments:
- '%dbhost%'
- '%dbuser%'
- '%dbpass%'

La classe PDO est instanciée avec trois arguments : le DSN (Data Source Name),
l’utilisateur et le mot de passe utilisés pour la connexion avec la base de données.
Tout d’abord, comme nous l’avons précédemment expliqué dans ce chapitre, il est
fortement déconseillé de placer des informations sensibles (comme un mot de passe)
au sein du fichier de configuration.
Les fichiers du répertoire config/packages ont été conçus dans cette optique. Voici, à
titre d’exemple, la structure du fichier exploité par le code précédent :
// config/packages/admin_bundle.yaml

admin_bundle:
dbhost: localhost
dbuser: dbusername
dbpass: dbpassword

Ici, en matière de portabilité, la définition du service my_pdo comporte quelques


limitations :
 En installant ce bundle, le développeur doit savoir qu’il doit renseigner des
paramètres de container. De plus, pour éviter les conflits avec d’autres
paramètres, les noms sont souvent préfixés par le nom du bundle (par
exemple mon_bundle.mon_parametre), ce qui les rend moins pratiques à
utiliser.
 Le support de plusieurs pilotes PDO s’avère très difficile. Ici, mysql est écrit en
dur. Même s’il était remplacé par un paramètre de container, il ne serait pas
possible de supporter les différents pilotes : avec le pilote sqlite,
l’option host n’aurait pas de sens, il faudrait placer le chemin vers le fichier de la
base de données. Il en est de même pour toutes les options spécifiques à un pilote
donné et que vous retrouvez dans le DSN.
Il serait possible d’utiliser un seul paramètre de container pour le DSN de PDO, mais
il serait assez rebutant à renseigner. Ici, la meilleure solution pour garantir la
flexibilité du service tout en conservant sa simplicité est d’utiliser la configuration.
Les bundles ont chacun la possibilité de définir des paramètres de configuration à
renseigner par le développeur, puis d’effecteur des traitements sur ces derniers.
a. Définir une arborescence
Cette configuration est définie dans le fichier Configuration.php du
dossier DependencyInjection de chaque bundle.
Le rôle de ce fichier est de créer une arborescence de valeurs à renseigner,
appelée TreeBuilder. Par défaut, l’arborescence est vide et aucun paramètre n’est
requis.
Pour ajouter des paramètres, il faut placer des « nœuds » :
namespace MonEntreprise\AdminBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

class Configuration implements ConfigurationInterface


{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('admin_bundle');

$rootNode
->children()
->scalarNode('dbhost')->end()
->scalarNode('dbuser')->end()
->scalarNode('dbpass')->end()
->end()
;

return $treeBuilder;
}
}

Ici, nous définissons plusieurs nœuds : dbhost, dbuser, etc. Ces paramètres sont
récupérés depuis la configuration de l’application
dans config/packages/admin_bundle.yaml.
Les valeurs définies dans le fichier sont récupérables depuis la classe de
l’extension du bundle (MonEntrepriseAdminExtension dans notre cas). Cette classe
est dans le dossier DependencyInjection du bundle :
namespace MonEntreprise\AdminBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;

class MonEntrepriseAdminExtension extends Extension


{
public function load(array $configs, ContainerBuilder
$container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration,
$configs);
$loader = new Loader\YamlFileLoader($container, new
FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yaml');
}
}

La méthode load() de l’extension est invoquée lors de la phase de compilation du


Service Container. Les arguments sont les valeurs pour la configuration du bundle et
le ContainerBuilder.
La configuration brute est ensuite traitée par Symfony
(méthode processConfiguration()). Une fois la configuration traitée, le contenu du
tableau retourné n’est en aucun cas hypothétique : il correspond obligatoirement aux
contraintes définies avec le TreeBuilder. Si tel n’était pas le cas, une exception aurait
été levée précédemment.
b. Les différentes étapes du traitement de la configuration
La configuration d’un bundle provient potentiellement de plusieurs fichiers. Nous
savons que la configuration est définie dans le fichier .env, mais il existe également
potentiellement .env.dev et .env.test. Ces derniers sont capables d’étendre la
configuration, selon un environnement donné.
Symfony utilise donc plusieurs fichiers de configuration qu’il normalise,
fusionne puis valide avant de délivrer la configuration finale au bundle.
Les types de nœuds
Pour l’instant, nous avons uniquement défini des nœuds de type scalaire
(scalarNode). Un nœud de type scalaire accepte les chaînes de caractères, les valeurs
numériques et les valeurs booléennes.
Pour restreindre un nœud à un type scalaire particulier, vous pouvez créer le nœud
correspondant :
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('admin_bundle');

$rootNode
->children()
->booleanNode('mon_booleen')->end()
->integerNode('mon_nombre')->end()
->arrayNode('mon_tableau')->end()
->variableNode('ma_variable')->end()
->end()
;

La différence entre les nœuds de type variable et scalar est que le nœud de
type variable accepte également les tableaux.
Valeurs requises et valeurs par défaut
Par défaut, aucun nœud n’est requis. Le fait de ne renseigner aucune configuration
pour un bundle n’entraînera pas d’erreur. Pour rendre un nœud obligatoire, il faut
invoquer la méthode isRequired() après sa déclaration :
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('admin_bundle');

$rootNode
->children()
->scalarNode('driver')->isRequired()->end()
->scalarNode('host')->defaultValue('localhost')->end()
->scalarNode('user')->end()
->scalarNode('pass')->end()
->scalarNode('path')->end()
->end()
;

Ici, le nœud driver est obligatoire, tandis que le nœud host vaut par défaut localhost.
Validation
La méthode isRequired() vue précédemment a pour conséquence de créer une règle
de validation : si aucune valeur n’est saisie, une exception sera lancée.
Cependant, si vous souhaitez utiliser des règles de validation plus complexes, vous
pouvez les définir manuellement. Une section de validation peut être ouverte avec la
méthode validate() :
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('admin_bundle');

$rootNode
->children()
->scalarNode('driver')
->isRequired()
->validate()
->ifNotInArray(array('mysql', 'sqlite'))
->thenInvalid('Pilote %s non supporté.')
->end()
->end()
->end()
;

L’exemple ci-dessus affichera un message d’erreur si le pilote n’est


pas mysql ou sqlite.
Pour les sections de validation, la condition doit être un appel à l’une des
méthodes suivantes :
 ifTrue(\Closure $fonction)
 ifString()
 ifNull()
 ifArray()
 ifInArray(array $tableau)
 ifNotInArray(array $tableau)
 always()
L’action à effectuer est quant à elle définie via l’une de ces méthodes :
 then(\Closure $fonction)
 thenEmptyArray()
 thenInvalid($message)
 thenUnset()
Second exemple, pour le nœud path, la règle de validation suivante affiche un
message d’erreur si la valeur ne correspond pas à un fichier existant :
$rootNode
->children()
->scalarNode('driver')
->isRequired()
->validate()
->ifNotInArray(array('mysql', 'sqlite'))
->thenInvalid('Pilote %s non supporté.')
->end()
->end()
->scalarNode('path')
->validate()
->ifTrue(function ($v) { return !is_file($v); })
->thenInvalid('Fichier de base de données non
trouvé.')
->end()
->end()
->end()
;

c. Récupérer la configuration validée


Nous venons de découvrir comment la configuration était traitée. Nous savons donc
que la méthode processConfiguration() de l’extension du bundle retourne les valeurs
de configuration normalisées et validées.
Vous pouvez ensuite utiliser ces dernières pour créer ou modifier des services à votre
guise. Continuons avec la précédente configuration, dont le but est de créer un service
PDO :

namespace MonEntreprise\AdminBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

class Configuration implements ConfigurationInterface


{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('admin');

$rootNode
->children()
->scalarNode('driver')
->isRequired()
->validate()
->ifNotInArray(array('mysql', 'sqlite'))
->thenInvalid('Seuls les pilotes MySQL et SQLite
sont pris en charge.')
->end()
->end()
->scalarNode('host')->defaultValue('localhost')->end()
->scalarNode('user')->end()
->scalarNode('pass')->end()
->scalarNode('path')->end()
->end()
->validate()
->ifTrue(function($v) {
if ('mysql' == $v['driver'] && !isset($v['host'],
$v['user'], $v['pass'])) {
return true;
}
if ('sqlite' == $v['driver'] && empty($v['path'])) {
return true;
}

return false;
})
->thenInvalid('La configuration de la connexion avec
la base de données est invalide.')
->end()
;

return $treeBuilder;
}
}

Le tableau de configuration final peut être récupéré pour modifier les arguments du
service my_pdo :
<?php

namespace MonEntreprise\AdminBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;

class MonEntrepriseAdminExtension extends Extension


{
public function load(array $configs, ContainerBuilder
$container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration,
$configs);

$loader = new Loader\YamlFileLoader(


$container,
new FileLocator(__DIR__.'/../Resources/config')
);
$loader->load('services.yaml');

$pdoDefinition = $container->findDefinition('my_pdo');

if ('mysql' == $config['driver']) {
$dsn = 'mysql:host='.$config['host'];
$pdoDefinition->setArguments(array(
$dsn,
$config['user'],
$config['pass']
));
} elseif ('sqlite' == $config['driver']) {
$dsn = 'sqlite:'.$config['path'];
$pdoDefinition->addArgument($dsn);
}
}
}

Dans ce cas-là, les arguments du service ne sont pas déclarés dans le


fichier services.yml du bundle :
services:
my_pdo:
class: PDO

L’extension du bundle, après avoir chargé le fichier de configuration des services du


bundle, récupère la définition du
service my_pdo (méthode findDefinition() du ContainerBuilder) pour renseigner
ses arguments :
 Dans le cas du pilote mysql, l’hôte, l’utilisateur et le mot de passe sont
renseignés.
 Dans le cas du pilote sqlite, le chemin vers le fichier de base de données est
renseigné.
Pour vous exercer, nous vous conseillons de partir de cet exemple pour le compléter
en ajoutant d’autres paramètres, comme le port à utiliser (pilote MySQL) ou un
paramètre indiquant s’il faut créer la base de données en mémoire (pilote SQLite).

4. Les « Compiler Passes »


Nous venons de voir qu’un bundle peut créer et modifier des services, en
s’appuyant notamment sur des fichiers de configuration. Cette technique est complète
et offre déjà de larges possibilités. Néanmoins, elle cloisonne les bundles en modules
autonomes et les interactions entre ces derniers se révèlent compliquées.
Il est difficile d’imaginer un bundle modifier un service défini par un autre bundle car
le Service Container est construit en s’appuyant sur les extensions de bundles dans un
certain ordre. Lors de l’exécution de l’extension de container d’un bundle A, les
services définis par le bundle B ne sont disponibles dans le ContainerBuilder que si ce
bundle B est déclaré avant le bundle A dans le Kernel. S’appuyer sur cette contrainte
serait peu fiable, et il serait impossible de gérer le cas de bundles ayant
réciproquement besoin de modifier la définition de leurs services.
C’est pour répondre à cette problématique, à savoir le partage de la définition de
services entre plusieurs bundles, que Symfony dispose de la fonctionnalité de
« Compiler Passes ». Ces derniers sont utilisés dans beaucoup de bundles de l’édition
standard du framework. Ils constituent donc un concept important de l’injection de
dépendances.
a. Concept
Un Compiler Pass est une création ou modification de services à retardement. Il n’est
pas exécuté au moment du chargement de l’extension de container d’un bundle, mais
en toute fin du processus de compilation du container, après l’exécution de toutes les
extensions des bundles de l’application.
Lors de cette étape, tous les bundles ont déjà défini leurs services et il ne reste plus
qu’à récupérer les services souhaités, puis à les modifier. Les services intervenant à ce
moment-là sont identifiables car ils ont été tagués au préalable.
L’exemple illustrant le mieux ce concept serait le répartiteur d’événements. Ainsi,
avant de continuer, nous vous conseillons de parcourir le chapitre de cet ouvrage
dédié à ce thème.
Un répartiteur d’événements s’appuie sur le patron de
conception Observer (observateur). Il comporte un objet qui envoie des événements
(le répartiteur, ou dispatcher), tandis que d’autres (les listeners) écoutent ces
événements.
Il serait hors contexte de recréer un système complet de répartiteur d’événements au
sein de ce chapitre, mais très intéressant de comprendre comment y intégrer le
composant d’injection de dépendances.
Imaginons que vous souhaitiez créer un bundle de gestion d’utilisateurs réutilisable
dans plusieurs de vos projets. Ce dernier serait plutôt abstrait, avec des points
d’ancrage envoyant des événements à certains moments (inscription d’un utilisateur,
connexion, etc.). L’application, quant à elle, aurait configuré des listeners pour y
ajouter toutes les actions spécifiques au projet (envoi d’un e-mail de confirmation
après inscription, etc.).
namespace Eni\UtilisateurBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\EventDispatcher\GenericEvent;

class InscriptionController extends AbstractController


{
/**
* @Route("/inscription")
*/
public function confirmation()
{
// inscription
$utilisateur = // ...

// en cas de succès, l'objet utilisateur est injecté dans


// un événement
$evenement = new GenericEvent($utilisateur);

// l'événement est ensuite "dispatché"


$this->get('event_dispatcher')->dispatch(
'eni.inscription_utilisateur',
$evenement
);

return new Response(


'<html><body>Inscription réussie.</body></html>
);
}
}

Point d’ancrage chargé de déclencher l’événement eni.inscription_utilisateur.


Les listeners de cet événement doivent ensuite être injectés dans le répartiteur
d’événements. Il est donc conseillé d’utiliser le composant « Injection de
dépendances » pour cette tâche. Voici un listener dont la tâche est d’envoyer un e-
mail à l’utilisateur de l’événement eni.inscription_utilisateur :
namespace Eni\DemoBundle\Listener;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\GenericEvent;

class InscriptionListener implements EventSubscriberInterface


{
public function envoiEmail(GenericEvent $evenement)
{
$utilisateur = $evenement->getSubject();

mail(
$utilisateur->getEmail(),
'Confirmation d\'inscription',
'Votre inscription sur notre site a bien été validée.'
);
}

public static function getSubscribedEvents()


{
return array(
'eni.inscription_utilisateur' => 'envoiEmail',
);
}
}

Cette classe appartient au bundle DemoBundle, tandis que le service de


répartiteur d’événements est défini par le framework (FrameworkBundle). Nous
devons utiliser un Compiler Pass pour injecter une instance
d’InscriptionListener dans le répartiteur.
b. Les tags
Dans un premier temps, le listener doit être configuré en tant que service :
# src/Eni/DemoBundle/Resources/config/services.yml

services:
my_event_subscriber:
class: Eni\DemoBundle\Listener\InscriptionListener
tags:
- { name: eni.event_subscriber }
La particularité de ce service est de comporter un tag. Un tag est un marqueur placé
sur un service. Ce marqueur peut ensuite être utilisé pour retrouver le service, lors de
la phase du Compiler Pass. Ici, le service est marqué comme
un eni.event_subscriber, c’est une valeur libre que nous avons choisie.
c. Le Compiler Pass
Par convention, il est conseillé de placer le Compiler Pass dans le dossier
DependencyInjection/Compiler :

// src/Eni/DemoBundle/DependencyInjection/Compiler/
RegisterListenersPass.php

namespace Eni\DemoBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;

class RegisterListenersPass implements CompilerPassInterface


{
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('event_dispatcher');
$tags = $container
->findTaggedServiceIds('eni.event_subscriber')
;

foreach ($tags as $id => $attributes) {


$definition->addMethodCall(
'addSubscriber',
array($container->getDefinition($id))
);
}
}
}

La première action du Compiler Pass est de récupérer la définition du


service event_dispatcher. Ensuite, il recherche les services dont le tag
est eni.event_subscriber. La valeur retournée est un tableau dont les clés sont les
identifiants des services, et les valeurs des tableaux contenant les attributs de chaque
tag. Dans notre cas, nous n’en utilisons aucun. Finalement, nous itérons sur les
différents tags pour injecter nos listeners dans le répartiteur d’événement.
Les attributs de tags sont utiles si vous souhaitez ajouter des informations
complémentaires aux tags :
# Ici, nous ajoutons un attribut « priority », qui pourra être
# utilisé pour définir la priorité du listener. Lorsque vous
# utilisez plusieurs listeners pour un même événement, cela permet
# de contrôler l'ordre dans lequel ils sont invoqués.

services:
my_event_subscriber:
class: Eni\DemoBundle\Listener\InscriptionListener
tags:
- { name: eni.event_subscriber, priority: 5 }

La dernière étape pour finaliser le Compiler Pass consiste à l’enregistrer. Cela se fait
depuis le fichier principal du bundle, à la racine de celui-ci :
<?php

namespace Eni\DemoBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Eni\DemoBundle\DependencyInjection\Compiler\RegisterListenersPass;

class EniDemoBundle extends Bundle


{
public function build(ContainerBuilder $container)
{
parent::build($container);

$container->addCompilerPass(new RegisterListenersPass());
}
}

Grâce à ce Compiler Pass, pour enregistrer un listener sur un événement donné, vous
n’aurez qu’à ajouter un tag lors de la définition du service.
Précédent
Le chargement automatique de services
Suivant
Quiz
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts

 1. Le concept de Templating
 2. Templating et modèle MVC

Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Présentation et concepts
1. Le concept de Templating
La notion de template (ou modèle) fait référence au principe d’élaboration de modèles
statiques de pages HTML dans lesquels viendront s’insérer des données dynamiques
résultant de l’exécution du code PHP.
Le templating est l’approche générale de découpage entre ces données statiques et
dynamiques, toujours dans l’optique de séparer les responsabilités : les traitements
applicatifs d’un côté, la présentation des données issues de ces traitements de l’autre.

2. Templating et modèle MVC


Dans la mise en œuvre du modèle MVC (cf. Architecture du framework - Le modèle
de conception MVC), les templates endossent la responsabilité de l’affichage des
données, c’est-à-dire : la vue. Ou plus précisément « les vues » dans la mesure où
chaque action implémentée dans les contrôleurs a la responsabilité de prévoir un
affichage spécifique en invoquant le template associé.
Chaque template est donc responsable d’une génération de contenu HTML à
destination du navigateur web. Ce contenu HTML est produit en combinant le code
statique, utilisé pour la mise en forme générale des pages, avec le code dynamique
permettant d’intégrer les données, bien que ce dernier soit simplement limité à
l’expression de variables contenant ces données. En effet, il est hors de question de
trouver du code PHP exécutant une requête SQL dans une vue !
Précédent
Quiz
Suivant
Twig
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI
Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig

 1. Présentation
 2. Pourquoi un nouveau langage ?
 3. Mise en pratique
 4. Remarques sur l’utilisation
 5. La notation des templates
 6. Extension du système de templates
 7. L’annotation @Template

Les gabarits de pages (layouts) et les blocks


Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Twig
1. Présentation
Twig est un moteur de templates, une librairie permettant de gérer la couche
« présentation », pour les applications utilisant le modèle de conception MVC.
Globalement, le principe est d’extraire tout ce qui est relatif à la vue dans des fichiers
dédiés, appelés « templates », qui représentent pour la plupart du HTML.
Nous insistons ici sur le terme « représenter ». En effet, les templates ne contiennent
pas forcément le code HTML tel qu’il sera retourné par l’application, ils contiennent
bien souvent du code permettant de générer ce code HTML. Ce code utilise un
langage spécifique au moteur de template et ce langage est communément appelé, par
extension, du « Twig ».
Twig est un projet qui voit le jour en 2008. Son auteur Armin Ronacher s’inspire très
largement du framework Jinja, un moteur de template pour Python. Aujourd’hui, le
projet Twig est maintenu par les équipes de développement de Symfony dont il est le
moteur de template par défaut. Le site officiel de Twig est accessible à
l’adresse https://twig.symfony.com.

2. Pourquoi un nouveau langage ?


Une première question, tout à fait légitime, peut vous venir à l’esprit suite à la
description que nous venons d’effectuer : pourquoi utiliser Twig et non PHP ?
On pourrait très bien imaginer une utilisation correcte du modèle de conception MVC,
avec extraction de la couche Vue au sein de fichiers dédiés (templates) et utilisant le
langage PHP.
Sachez que Symfony permet cela (même si c’est Twig qui est configuré par
défaut). Néanmoins, nous n’aborderons pas cette solution au cours de ce chapitre ;
nous nous concentrerons uniquement sur Twig, et ce pour plusieurs raisons :
 Twig est rapide. Bien qu’il utilise son propre langage, et par conséquent son
propre moteur de parsing (car, au final, c’est du code PHP qui doit être
généré). Le code PHP généré par chaque template est mis en cache. Ce
processus n’a donc pas lieu à chaque requête. L’impact sur les performances, par
rapport à du PHP pur, est donc minime.
 Twig est sécurisé. Les données affichées sont immunisées par défaut contre les
éventuelles attaques malveillantes, comme le cross-site scripting (XSS) par
exemple.
 Twig est adapté aux templates. Ce langage a été spécialement conçu dans cette
optique et nous verrons tout au long de ce chapitre qu’il nous offre une multitude
de raccourcis (qui sont impossibles en PHP) nous permettant de garder nos
templates lisibles et simples. Si vous avez déjà eu l’occasion d’utiliser le
framework Django (pour le langage Python), vous ne serez pas dépaysé : la
syntaxe de Twig est très semblable à celle utilisée dans les templates sous
Django.
 Twig est largement utilisé car il est le moteur de template par défaut de Symfony.
La plupart des projets l’utilisent et beaucoup de bundles open source supportent
uniquement Twig, ce qui est également le cas des tutoriels ou articles qu’on peut
trouver sur Internet. Ils sont très souvent orientés sur ce moteur de template et
non PHP.

3. Mise en pratique
Voyons comment créer concrètement cette couche Vue au sein de notre
application. Nous connaissons pour l’instant le processus entre une requête et une
réponse (cf. Architecture du framework et Routage et contrôleur). Nous savons que le
contrôleur est chargé de retourner une réponse HTTP, sous la forme d’un objet de
type Symfony\Component\HttpFoundation\Response :
namespace App\Controller;

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

class DefaultController extends AbstractController


{
/**
* @Route("/")
*/
public function hello()
{
return new Response('Hello world!');
}
}
Ici, l’application retourne une réponse ayant comme contenu Hello world! (la vue),
qui est directement générée depuis le contrôleur. Dans ce cas précis, cela n’est pas
particulièrement choquant, mais imaginez une page web, avec ses nombreuses
balises... le code du contrôleur deviendrait rapidement illisible.
Déléguons la génération du contenu de la réponse à Twig.
Contrôleur
namespace App\Controller;

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

class DefaultController extends AbstractController


{
/**
* @Route("/")
*/
public function hello()
{
return $this->render(
'default/hello.html.twig'
);
}
}

Template
{# templates/default/hello.html.twig #}
Hello world!

{# templates/default/hello.html.twig #} n’est pas obligatoire car pour Twig, c’est un


commentaire. Ici, nous l’utilisons uniquement dans le but d’aider le lecteur, en
indiquant le chemin vers le template.

4. Remarques sur l’utilisation


Nous avons vu au cours des chapitres précédents (Architecture du framework et
L’injection de dépendances) que, pour Symfony, tous les outils/fonctionnalités étaient
regroupés dans des « services ». Twig n’échappe pas à cette règle.
Effectivement, nous invoquons la méthode render de la classe Symfony\Bundle\
FrameworkBundle\Controller\AbstractController. En allant voir son contenu, on
remarque qu’elle fait bel et bien appel à un service (dont l’identifiant est
« templating »).
Cela rejoint nos explications précédentes (cf. Architecture du framework -
Architecture de Symfony, se référer notamment au schéma descriptif) ; la vue est tout
simplement un service, sur lequel nous pouvons nous appuyer pour générer des
réponses, au même titre que n’importe quel autre service. Ce n’est pas un service
spécial ou particulier.

5. La notation des templates


De la même manière que le composant Router utilise une notation particulière pour
référencer une action, chaque action utilisant un template doit le référencer selon une
certaine convention.
Les templates sont enregistrés dans le répertoire templates/ de l’application. Ce
répertoire contient généralement un sous-répertoire pour chaque contrôleur contenant
les templates spécifiques à ce contrôleur. L’emplacement de ce répertoire est défini
dans la configuration de Twig qui se trouve dans le
fichier config/packages/twig.yaml ; en voici le contenu par défaut :
twig:
default_path: '%kernel.project_dir%/templates'
debug: '%kernel.debug%'
strict_variables: '%kernel.debug%'
exception_controller: null

Nous pouvons notamment y constater la définition de la variable default_path qui


pointe vers ce répertoire templates/.
%kernel.project_dir% est une variable représentant le répertoire racine du projet.
Elle est utilisée dans de nombreux fichiers de configuration Symfony.
La notation pour les templates est la suivante : chemin/vers/template ou le chemin
est exprimé de manière relative à partir du répertoire templates/. Se présentent alors
deux possibilités de localisation des templates :
 Les templates directement localisés dans le répertoire templates/, qui seront des
templates généraux pour l’application ou bien des gabarits de page (cette notion
est évoquée un peu plus loin dans ce chapitre). En utilisant par
exemple base.html.twig dans un contrôleur, on référence le
fichier templates/base.html.twig.
 Les templates associés à une action de contrôleur qui sont, eux, rangés dans un
dossier portant le nom du contrôleur. Ainsi, conventionnellement,
l’action afficher() du contrôleur AdminController sera associée au
template présent dans templates/admin/afficher.html.twig, son appel dans le
contrôleur se faisant avec le nom admin/afficher.html.twig.

6. Extension du système de templates


En plus du répertoire conventionnel templates/, il est possible d’ajouter d’autres
dossiers contenant des templates afin de mieux organiser son code. Pour cela il faut
intervenir dans la configuration de Twig stockée dans le
fichier config/packages/twig.yaml. La propriété paths de ce fichier permet de
référencer plusieurs répertoires supplémentaires et de les associer à un espace de
noms afin d’éviter tout conflit de nommage. Prenons l’exemple de la configuration
suivante ajoutée au fichier config/packages/twig.yaml :
twig:
# ...
paths:
'admin/templates': 'admin'
'backend/templates': 'back'

Les répertoires admin/templates et backend/templates, tous deux situés à la racine


du projet, sont ajoutés au système de localisation des templates par Symfony et
possèdent respectivement les espaces de noms admin et back. Les fichiers de ces
répertoires seront identifiés par la
syntaxe @EspaceDeNom/nom_du_fichier.html.twig.
Avec cet ajout, pour référencer le fichier index.html.twig situé
dans admin/templates, on utilisera @admin/index.html.twig.

7. L’annotation @Template
Le bundle SensioFrameworkExtraBundle intégré par défaut, offre un raccourci
intéressant pour l’affichage de vos templates. Plutôt que d’invoquer la
méthode render() du contrôleur comme nous venons de le voir, vous pouvez tout
simplement apposer une annotation sur votre action :
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;

class DefaultController extends AbstractController


{
/**
* @Route("/")
* @Template
*/
public function hello()
{
return array();
}
}

Ici, comme nous utilisons l’annotation @Template, Symfony « devine » le template à


utiliser en fonction de l’action et du contrôleur : default/hello.html.twig.
Ensuite, le tableau vide retourné en fin d’action signifie qu’aucune variable n’est
envoyée au template. Si vous souhaitez en envoyer, elles doivent tout simplement être
ajoutées au tableau :
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;

class DefaultController extends AbstractController


{
/**
* @Route("/")
* @Template
*/
public function helloAction()
{
return array(
'ma_variable' => 'ma_valeur',
'mon_autre_variable' => 'mon_autre_valeur',
);
}
}

Cette approche, bien que toujours utilisable, tend à être de moins en moins utilisée à
cause de l’aspect un peu trop « magique » de ce comportement. Nous vous
conseillons, de fait, de toujours utiliser la méthode render(), permettant de mieux
contrôler quel template sera rendu en fin d’action.
Précédent
Présentation et concepts
Suivant
Les gabarits de pages (layouts) et les blocks
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks

 1. La composition de pages
 2. Définition des gabarits
 3. Les blocks

Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Les gabarits de pages (layouts) et


les blocks
1. La composition de pages
En naviguant sur un site web classique, nous pouvons nous rendre compte que les
pages sont très ressemblantes et que seul le contenu principal change, tandis que
d’autres parties sont statiques (menu, pied de page, etc.).
Si les templates représentant ces pages étaient isolés, une modification sur un contenu
commun au site nécessiterait l’édition de tous les templates, ce qui n’est pas
envisageable car cela va à l’encontre du principe du « Don’t Repeat Yourself » (« Ne
pas se répéter »), véritable leitmotiv du développeur.
Il est donc fortement intéressant de factoriser le code redondant de ses templates, de
manière à ne pas se répéter, au travers de layouts.

2. Définition des gabarits


Un layout est un gabarit de mise en page, qui est utilisé comme base lors de la
création de pages. C’est en quelque sorte un patron de conception, un moule.
Par défaut, le framework inclut un layout plutôt générique. Il est situé dans le
dossier templates/ et nommé base.html.twig :
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title>
{# Run `composer require symfony/webpack-encore-bundle`
and uncomment the following Encore helpers to start
Symfony UX #}
{% block stylesheets %}
{#{{ encore_entry_link_tags('app') }}#}
{% endblock %}
{% block javascripts %}
{#{{ encore_entry_script_tags('app') }}#}
{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>

On y reconnaît une page HTML5 basique mais certains mots-clés nous sont
inconnus : ils appartiennent au langage Twig.
Ces instructions de langage Twig sont des blocks. Ils sont définis via la syntaxe
suivante :
{% block nom_du_block %}
Contenu optionnel du bloc
{% endblock %}

nom_du_block est l’intitulé du block, nous vous conseillons vivement de choisir des
noms descriptifs. Dans notre layout par défaut, c’est déjà le cas.
Essayons d’interpréter le template templates/base.html.twig :
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;

class DefaultController extends AbstractController


{
/**
* @Route("/")
*/
public function index()
{
return $this->render('base.html.twig');
}
}

Nous obtenons le code HTML suivant :


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Welcome!</title>
</head>
<body>
</body>
</html>

Le code obtenu est en réalité légèrement différent, il contient la barre d’outils avec les
informations utiles lors du développement. Cette barre est générée automatiquement
par Symfony sur les pages HTML. Par souci de simplicité, nous omettons celle-ci du
code HTML.
Nous remarquons, en comparant le code HTML généré à celui du template, que les
blocks ont disparu.

3. Les blocks
Les blocks sont des zones, des marqueurs, placés sur les templates et dont le contenu
est optionnel (ils peuvent tout à fait être vides).
title, stylesheets, body et javascripts correspondent respectivement aux zones pour
le titre, les feuilles de style CSS, le contenu principal ainsi que les scripts JavaScript.
Les noms des blocks peuvent être choisis librement par le développeur : il n’y a pas
de comportement particulier en fonction de l’intitulé d’un block.
Les blocks sont particulièrement utiles lors de l’héritage de templates et nous pouvons
constater que le template base.html.twig est minimal.
Il peut servir de base pour d’autres templates :
Contrôleur
namespace App\Controller;

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

class DefaultController extends AbstractController


{
/**
* @Route("/")
*/
public function index()
{
return $this->render(
'default/index.html.twig'
);
}
}

Template
{# default/index.html.twig #}

{% extends 'base.html.twig' %}

{% block body %}
Hi there!
{% endblock %}

Le code HTML généré sera le suivant :


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Welcome!</title>
</head>
<body>
Hi there!
</body>
</html>

Nous remarquons que ce contenu généré est assez éloigné du contenu du


template Twig, alors que s’est-il passé ?
Nous avons utilisé l’héritage de templates : l’instruction {% extends
’base.html.twig’ %} indique que notre template est un enfant de base.html.twig.
En conséquence, le contenu généré par ce template enfant sera le même que son
parent, à la seule différence que les blocks peuvent être surchargés. Ici, nous
redéfinissons le block body. Ce contenu est donc injecté à l’endroit où ce block est
défini dans le template parent.
Pouvoir uniquement remplacer le contenu des blocks parents serait trop
restrictif. Twig nous permet donc également de compléter ce contenu :
Template
{# default/index.html.twig #}

{% extends 'base.html.twig' %}

{% block title %}
{{ parent() }} Page d'index
{% endblock %}

{% block body %}
Hi there!
{% endblock %}

Voici le code HTML généré :


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Welcome! Page d'index</title>
</head>
<body>
Hi there!
</body>
</html>

Grâce à l’instruction {{ parent() }}, nous demandons à réafficher le contenu du block


du template parent et nous y ajoutons ensuite un texte complémentaire.
Ce texte complémentaire peut également précéder l’instruction.
Cette technique est très puissante car elle permet par exemple d’ajouter un script
JavaScript spécifique à une page très facilement, tandis que le block JavaScript du
fichier base.html.twig inclurait le script global au site :
{# default/index.html.twig #}

{% extends 'base.html.twig' %}
{% block javascripts %}
{{ parent() }}

<script src="http://..."></script>
{% endblock %}

...
Précédent
Twig
Suivant
Le langage Twig
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig

 1. Les différents types d’instructions


 2. Manipulation des variables
 a. Utilisation de variables dans les templates
 b. Utilisation des variables de type tableau ou objet
 3. Structures de contrôle et tags
 a. Les conditions
 b. Les boucles
 4. Les balises Twig (tags)
 a. Créer et modifier des variables
 b. Twig et l’échappement
 5. Inclure des templates

Les filtres et les fonctions


La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Le langage Twig
1. Les différents types d’instructions
Malgré les connaissances basiques que nous avons sur Twig pour l’instant, nous
pouvons déjà dégager des exemples précédents deux types de notations :
 {% ... %} : cette instruction exécute ou définit quelque chose. Elle est liée à la
logique et c’est au travers de celle-ci que nous étendons des templates
({% extends … %}) ou définissons des blocks ({% block … %}).
 {{ ... }} : cette instruction affiche quelque chose, le contenu du template parent par
exemple (via {{ parent() }}).
Sachez qu’il existe une troisième instruction, qui nous permet de placer des
commentaires au sein du template :
{# Ceci est un commentaire Twig #}

Les commentaires Twig, contrairement aux commentaires HTML, seront effacés lors
de la génération du code, donc non visibles dans le code source de la page.

2. Manipulation des variables


a. Utilisation de variables dans les templates
Des variables peuvent être envoyées aux templates depuis le contrôleur, de manière à
générer des pages dynamiques. Pour cela, elles doivent être passées dans un tableau,
en tant que deuxième paramètre de la méthode render() du contrôleur :
namespace App\Controller;

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

class DefaultController extends AbstractController


{
/**
* @Route("/")
*/
public function index()
{
return $this->render(
'default/index.html.twig',
[
'titre' => 'Bienvenue à tous',
'bonjours' => ['Hello', 'Bonjour', 'Hallo'],
]
);
}
}

Ici, nous envoyons deux variables au template, titre et bonjours, contenant


respectivement le titre de la page ainsi que le mot « bonjour » en différentes langues.
Le template peut ensuite récupérer ces variables :
{# default/index.html.twig #}

{% extends 'base.html.twig' %}

{% block title %}
{{ titre }}
{% endblock %}

{% block body %}
{% for bonjour in bonjours % }
{{ bonjour }}<br />
{% endfor %}
{% endblock %}

Le code généré :
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Bienvenue à tous</title>
</head>
<body>
Hello<br />
Bonjour<br />
Hallo<br />
</body>
</html>

Nous découvrons deux nouvelles instructions au travers de cet exemple :


 {{ titre }} : affiche la variable titre, qui a été envoyée depuis le contrôleur.
 {% for bonjours in bonjours %} : itère sur le tableau bonjours. C’est
l’équivalent d’un foreach en PHP. Nous reviendrons plus loin dans ce
chapitre sur les boucles.
Notons que la première instruction, liée à l’affichage, est délimitée par deux
accolades, tandis que la seconde, étant une action logique, est entourée d’une accolade
et du signe %. Cela rejoint nos précédentes explications sur les différents types
d’instructions.
b. Utilisation des variables de type tableau ou objet
Quand les variables envoyées aux templates ne sont pas de simples chaînes de
caractères, mais des tableaux ou objets, le développeur a souvent besoin d’accéder à
des propriétés ou valeurs sous-jacentes.
Twig contient un délimiteur très utile pour cette tâche, c’est le point :
{{ variable.valeur }}

Ici, lorsque variable est un objet, Twig renvoit la valeur, par ordre de priorité, de :
 $variable[’valeur’], si l’objet implémente l’interface ArrayAccess.
 la propriété valeur de l’objet : $variable->valeur.
 la méthode $variable->valeur().
 la méthode $variable->getValeur().
 la méthode $variable->isValeur().
 null le cas échéant.
Si variable est un tableau, Twig renvoie le contenu de la clé valeur, ou null (si la
clé valeur n’existe pas).

3. Structures de contrôle et tags


Les structures de contrôle ont pour but de gérer la logique du programme ; nous ne
serons pas dépaysés car nous utilisons déjà ces concepts avec PHP (avec les boucles,
conditions, etc.).
a. Les conditions
Twig autorise les instructions de type if [/ elseif] / else :
{% if variable.valeur == 'Bonjour' %}
Bienvenue!
{% elseif variable.valeur == 'Hallo' %}
Willkommen!
{% else %}
Welcome!
{% endif %}

Le code équivalent en PHP serait :


<?php

if ($variable['valeur'] == 'Bonjour') {
echo 'Bienvenue!';
} elseif ($variable['valeur'] == 'Hallo') {
echo 'Willkommen!';
} else {
echo 'Welcome!';
}

Ici, $variable[’valeur’] n’est pas forcément ce qui est généré car Twig utilise les
règles d’accès aux tableaux/objets que nous avons définies précédemment.
b. Les boucles
Les boucles permettent d’itérer sur des tableaux (ou objets implémentant
l’interface Traversable) :
{% for valeur in tableau %}
{{ valeur }}<br />
{% endfor %}

Le code ci-dessus a pour équivalent en PHP :


<?php

foreach ($tableau as $valeur) {


echo $valeur . '<br />';
}

En PHP, l’instruction foreach peut également récupérer la clé de chaque élément du


tableau :
<?php

foreach ($tableau as $cle => $valeur) {


echo $cle . ' : ' . $valeur . '<br />';
}

L’instruction for de Twig permet aussi de récupérer les clés, ce qui s’avère
particulièrement intéressant pour les tableaux associatifs :
{% for cle, valeur in tableau %}
{{ cle }} : {{ valeur }}<br />
{% endfor %}

Un raccourci très appréciable avec Twig est la possibilité d’afficher un texte alternatif
lors d’une tentative d’itération sur un tableau vide :
{% for valeur in tableau %}
{{ valeur }}<br />
{% else %}
Le tableau est vide.<br />
{% endfor %}

En PHP, ce type de syntaxe n’existe pas ; l’équivalent serait plus fastidieux :


<?php

if ($tableau) {
foreach ($tableau as $valeur) {
echo $valeur . '<br />';
}
} else {
echo 'Le tableau est vide.<br />';
}

À l’intérieur de chaque boucle, une variable spéciale est accessible ; son nom est loop.
Elle contient de précieuses informations quant au déroulement des itérations :
« Quelle est la longueur totale du tableau ? » ou « À quelle occurrence sommes-
nous ? » sont autant de questions auxquelles elle pourra répondre.
Attributs de la variable loop :
Twig
loop.index Le numéro de l’itération courante. dispose
d’autres
loop.revindex Le nombre d’itérations à effectuer pour atteindre la fin de
la boucle.

loop.first Un booléen qui vaut « true » à la première itération.

loop.last Un booléen qui vaut « true » à la dernière itération.

loop.length Le nombre d’éléments à parcourir.


fonctionnalités relatives aux boucles plutôt intéressantes, comme la possibilité de
filtrer à la volée chaque élément :
{% for produit in produits if produit.disponible %}
{{ produit.nom }}<br />
{% endfor %}

Ce code correspondrait en PHP à :


<?php

foreach ($produits as $produit) {


if (!$produit->isDisponible()) { // ou ->getDisponible(),
['disponible'], etc.
continue;
}
echo $produit->getNom() . '<br />';
}

Certains attributs de la variable loop seront inaccessibles en cas de boucle à condition


(loop.length, loop.last ou loop.revindex). En effet, Twig ne peut savoir à l’avance le
nombre d’éléments qui seront itérés, puisque le filtre est appliqué à la volée.
Enfin, nous noterons que l’instruction for peut aussi itérer un nombre de fois
prédéfini, sans qu’il n’y ait de tableau :
{% for i in 0..10 %}
{{ i }}<br />
{% endfor %}

En PHP :
<?php

foreach (range(0, 10) as $i) {


echo $i . '<br />';
}

Cela fonctionne également avec les lettres : {% for i in a..z %}

4. Les balises Twig (tags)


Les balises Twig, souvent nommées « tags », sont toutes les instructions comprises au
sein du délimiteur {% ... %}.
a. Créer et modifier des variables
Nous avons vu que les variables disponibles sont envoyées depuis le contrôleur.
C’est effectivement ce qu’il se passe la plupart du temps. Néanmoins, des
variables peuvent aussi être créées au niveau des templates, grâce au tag set.
{% set variable = 'valeur' %}

{% set tableau = [1, 2] %}

{% set tableau_associatif = {'cle': 'valeur'} %}

{% set variable1, variable2 = 'valeur1', 'valeur2' %}

Pour les affectations plus complexes, on utilise ce tag avec des balises ouvrantes et
fermantes :
{% set variable %}
{% for ... %}
{# ... #}
{% endfor %}

{# ... #}
{% endset %}

Cette deuxième technique s’avère relativement puissante quand elle est combinée à
d’autres tags, les valeurs générées pouvant ainsi devenir dynamiques (boucles,
conditions, etc.).
b. Twig et l’échappement
Par défaut, et par mesure de sécurité, Twig est plutôt agressif quant à sa
politique d’échappement : il considère toutes les variables comme potentiellement
dangereuses.
En conséquence, les variables contenant du code HTML (même maîtrisé par le
développeur) seront mal affichées, car les caractères spécifiques au HTML seront
échappés, empêchant le navigateur de les interpréter.
{% set titre = '<h1>Bac à sable</h1>' %}

{{ titre }}

Pour le template ci-dessus, le navigateur affichera les balises, car le code source serait
le suivant :
&lt;h1&gt;Bac à sable&lt;/h1&gt;

Il existe trois façons de modifier ce comportement.


Le tag autoescape
La première consiste à utiliser un tag permettant de modifier la logique
d’échappement. C’est le tag {% autoescape %} qui permet de désactiver
l’échappement de cette manière :
{% autoescape false %}
{% set titre = '<h1>Bac à sable</h1>' %}

{{ titre }}
{% endautoescape %}

En plus de désactiver l’échappement, ce tag est capable de modifier la logique


d’échappement.
Cette logique n’est bien évidemment pas la même selon le langage environnant : les
pages web contiennent souvent du HTML, mais nous pouvons retrouver des scripts
JavaScript ou des styles CSS placés dans la page HTML :
<style type="text/css">
.couleur {
color: {{ color }};
}
/* ... */
</style>

<script>
var nom = "{{ nom }}";
// ...
</script>

Si ces morceaux de code JavaScript ou de CSS utilisent des variables dont le contenu
n’est pas maîtrisé par le développeur (provenant d’un utilisateur), il conviendra de les
échapper, de manière à éviter les injections de code.
Dans le cas ci-dessus, pour le code JavaScript, si la variable nom contient ";
alert("aaa"); var a = ", une alerte apparaîtra à l’écran, alors que le développeur
souhaite simplement affecter une valeur à une variable.
Voici comment modifier la stratégie d’échappement :
{% autoescape 'html' %}
Les variables de ce block seront échappées selon la stratégie
HTML.
C'est le comportement par défaut.
{% endautoescape %}

{% autoescape 'css' %}
Les variables de ce block seront échappées selon la stratégie
CSS.
{% endautoescape %}

{% autoescape 'js' %}
Les variables de ce block seront échappées selon la stratégie
JavaScript.
{% endautoescape %}

Les stratégies d’échappement prises en charge par Twig sont :


 html
 js
 css
 html_attr (pour les attributs HTML)
 url (pour les paramètres utilisés dans des URL)
Globalement, on parle d’échappement pour les variables, mais les cas couverts sont
légèrement plus nombreux. Pour plus d’informations sur tous les cas où celui-ci
intervient, reportez-vous à l’adresse
suivante : https://twig.symfony.com/doc/api.html#escaper-extension
Échappement unitaire
Une seconde solution est l’échappement au niveau de la variable. Cette technique est
plus atomique que le tag.
Pour cela, nous utilisons un filtre (nous reviendrons en détail sur les filtres plus loin
dans ce chapitre).
{{ variable|escape('html') }}

Ici, la variable est échappée selon la stratégie HTML.


Ce filtre est décrit de manière détaillée dans la section Les filtres et les fonctions de ce
chapitre.
Modifier la stratégie globale (utilisateurs avancés)
La stratégie d’échappement par défaut peut être modifiée au travers de la
configuration générale de Twig :
# config/packages/twig.yaml

twig:
default_path: '%kernel.project_dir%/templates'
debug: %kernel.debug%
strict_variables: %kernel.debug%
autoescape: false

La clé en question est autoescape. Ici, via false, nous désactivons l’échappement.
Désactiver l’échappement par défaut implique une parfaite maîtrise des
variables utilisées dans les templates. Les variables non sûres devront être échappées
manuellement à l’affichage (au travers du tag autoescape ou du filtre escape). Cette
technique est déconseillée et nous l’évoquons à titre purement informatif.

5. Inclure des templates


Certaines parties de templates, considérées comme des modèles pouvant se
répéter dans plusieurs pages, peuvent être factorisées.
Il suffit d’extraire le code en question dans son propre template, puis de
l’inclure depuis les templates le nécessitant.
{% extends 'base.html.twig' %}
{% block body %}
Contenu de la page.
...
{{ include('publicite.html.twig')}}
{% endblock %}

Le template inclus aura accès aux variables définies dans le template incluant ; des
variables supplémentaires peuvent être passées lors de l’inclusion :
{{ include('publicite.html.twig', {'theme': 'cosmétiques'})
}}

Enfin, le template inclus peut se voir interdire l’accès aux variables du contexte
extérieur via le paramètre with_context :
{{ include('publicite.html.twig', with_context = false) }}

{# ou se voir restreindre l'accès aux variables passées


explicitement #}
{{ include('publicite.html.twig', {'theme': 'cosmétiques'},
with_context = false) }}
Précédent
Les gabarits de pages (layouts) et les blocks
Suivant
Les filtres et les fonctions
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions

 1. Présentation des filtres


 a. Utilisation et syntaxe
 2. Les principaux filtres Twig
 a. Chaînes de caractères
 b. Échappement
 c. L’encodage
 3. Les fonctions
 a. Twig et le routage
 b. Débogage avec la fonction dump

La gestion des ressources statiques (images, feuilles de style, scripts JS…)


Quiz
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Les filtres et les fonctions


Les tags et structures de contrôles sont plutôt des instructions relatives à la logique du
template. Nous allons maintenant découvrir deux types d’instructions de plus bas
niveau : les filtres et les fonctions.

1. Présentation des filtres


Les filtres ont pour objectif de transformer des données en appliquant une règle qui
leur est propre et les caractérise. Ces règles sont particulièrement appropriées pour les
vues et pour l’affichage.
Échapper ou changer la casse de chaînes de caractères, travailler avec les objets
DateTime ou les encodages du texte affiché, sont autant de fonctionnalités qui sont
mises à notre disposition au travers des filtres.
a. Utilisation et syntaxe
Les filtres sont le plus souvent utilisés sur des variables, mais, par souci de
compréhension, nous allons les utiliser directement sur des chaînes de caractères lors
des différents exemples.
Voici la syntaxe d’un filtre fictif nommé mon_filtre :
{# sur une variable #}
{{ variable|mon_filtre }}

{# sur une chaîne de caractères #}


{{ 'Bonjour'|mon_filtre }}

Certains filtres pourront accepter des paramètres :


{{ variable|mon_filtre('parametre1', 'parametre2') }}

Enfin, plusieurs filtres peuvent être exécutés à la suite :


{{ variable|filtre_1|filtre_2|filtre_3 }}

2. Les principaux filtres Twig


a. Chaînes de caractères
Twig dispose de filtres relatifs à la gestion de casse (majuscules, minuscules).
lower
Ce filtre permet de passer tous les caractères d’une chaîne en minuscules.
{{ 'Bonjour'|lower }}

{# affiche 'bonjour' #}

upper
Ce filtre est semblable à lower, mais renvoie des majuscules.
{{ 'Bonjour'|upper }}

{# affiche 'BONJOUR' #}

title
Ce filtre retourne une casse adaptée aux titres.
{{ 'le gilet bleu'|title }}

{# affiche 'Le Gilet Bleu' #}

capitalize
Ce filtre met en majuscule le premier caractère de la chaîne.
{{ 'le gilet bleu'|title }}

{# affiche 'Le gilet bleu' #}

b. Échappement
Nous avons déjà pris connaissance de la manière dont Twig gérait l’échappement
mais nous allons revenir ici sur le filtre escape précédemment évoqué, ainsi que sur
un nouveau filtre, raw.
escape
Le filtre escape a un alias : e. Les deux notations ci-dessous sont donc équivalentes :
{{ variable|escape('html') }}
{{ variable|e('html') }}

{# ou #}
{{ variable|e }}

Il est optionnel de spécifier la stratégie HTML car c’est la stratégie par défaut.
raw
Dans un contexte où l’échappement est activé, pour ne pas échapper une
variable spécifique, il faut utiliser le filtre raw :
{% autoescape %}
{{ variable|raw }} {# cette variable ne sera pas échappée #}
{% endautoescape %}

Si plusieurs filtres sont enchaînés, le filtre raw doit toujours être placé en dernier.
striptags
Une alternative à l’échappement est la suppression pure et simple des balises HTML.
{{ '<h1>Titre</h1>'|striptags }}

{# affiche 'Titre' #}

c. L’encodage
convert_encoding
De manière générale, il est recommandé d’utiliser l’encodage UTF-8 avec Symfony.
Il est configuré par défaut au niveau du Kernel ainsi que pour différents services :
Twig ou Doctrine (pour les bases de données) par exemple.
Si vous n’avez pas le contrôle de l’encodage sur une quelconque variable affichée
(par exemple dans le cas de données issues d’un flux RSS récupéré depuis une source
extérieure), il est possible d’appliquer un filtre qui se chargera de gérer l’encodage.
{{ caracteres_non_utf8|convert_encoding('UTF-8', 'ISO-8859-1') }}

Le premier paramètre du filtre est l’encodage souhaité après application du filtre, le


second est le caractère d’encodage d’entrée.
json_encode
Le filtre json_encode permet de transformer un tableau en une chaîne de
caractères JSON.
tableau|json_encode

En plus d’une utilisation traditionnelle en tant que format d’échange de données (API,
Ajax, etc.), ce filtre peut être utilisé pour générer des objets littéraux JavaScript.
On pourrait imaginer un plug-in jQuery dont les options seraient dynamiques (en
fonction des préférences de l’utilisateur) :
$('selecteur').plugin(
{{ options|json_encode|raw }}
);
{# le code généré pourrait être #}

$('selecteur').plugin(
{"color":"blue","title":"Messages de Pierre"}
);

Cette technique a toutefois une limite, elle supporte uniquement les configurations
simples (valeurs scalaires, tableaux, objet littéral imbriqué) autorisées par le format
JSON : les fonctions anonymes (ou Closures) ne sont pas transformables au travers de
ce filtre.
url_encode
Le filtre url_encode permet d’encoder des variables qui seront utilisées en tant que
parties d’URL.
Ce filtre accepte une chaîne de caractères ou un tableau.
{{ "mot-clé"|url_encode }}
{# génère ' mot-cl%C3%A9' #}

{{ {'variable1': 'valeur1', 'variable2': 'valeur2'}|url_encode }}


{# génère 'variable1=valeur1&variable2=valeur2 ' #}

Vous n’aurez pas besoin de ce filtre pour générer les URL de votre application car
celui-ci est uniquement utile pour gérer les URL externes. Pour les URL
internes, reportez-vous à la section ci-après (Twig et le routage).

3. Les fonctions
Avec Twig, les fonctions sont semblables à ce que l’on entend par ce terme en PHP.
On pourrait définir une fonction par un morceau de code effectuant une certaine tâche
et caractérisé par un nom. C’est par ce nom que le développeur peut invoquer cette
fonction, de manière illimitée. Les fonctions acceptent souvent des paramètres,
correspondant à des cibles sur lesquelles le traitement doit être effectué, ou des
informations complémentaires quant au déroulement souhaité. Enfin, avec Twig, elles
retournent ou affichent toujours quelque chose.
a. Twig et le routage
Les routes sont très utilisées dans les templates et nous savons comment générer des
URL depuis le contrôleur (cf. Routage et contrôleur - Le contrôleur). Mais il est
fréquent de vouloir en générer depuis les vues, notamment pour les liens de ses pages
HTML.
Il existe deux fonctions relatives à la génération de routes : path et url.
path
Cette première fonction génère une URL relative. En général, il est préférable de
générer ce type d’URL.
<a href="{{ path('ma_route') }}">Visiter ma page.</a>

{# Exemple de code généré :

<a href="/ma-route">Visiter ma page.</a>

#}

Les paramètres de substitution doivent être placés en tant que second argument, dans
un tableau associatif :
<a href="{{ path('hello', { 'name': 'world' }) }}">
Visiter ma page.
</a>

{# Code généré :
<a href="/hello/world">
Visiter ma page.
</a>

#}

url
Cette fonction s’utilise de la même manière que path. La seule différence réside dans
la valeur retournée : c’est une URL absolue.
<a href="{{ url('ma_route') }}">Visiter ma page.</a>

<a href="{{ url('hello', { 'name': 'world' }) }}">


Visiter ma page.
</a>

{# Code généré :

<a href="http://localhost/sf/ma-route">Visiter ma page.</a>

<a href="http://localhost/sf/hello/world">
Visiter ma page.
</a>

#}

Un cas concret nécessitant des URL absolues est la génération du contenu d’un e-mail
avec Twig.
b. Débogage avec la fonction dump
En PHP, une fonction très pratique pour connaître le contenu d’une variable
est var_dump() car, à la différence de l’instruction echo, var_dump() peut afficher le
contenu de variables de types complexes tels que des tableaux ou des objets.
Avec Twig, nous utiliserons la fonction dump :
{{ dump(ma_variable) }}
{# équivalent de 'var_dump($ma_variable);' #}
Précédent
Le langage Twig
Suivant
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)

 1. Les ressources statiques dans une application Symfony


 2. Le cas des ressources statiques externes
 3. Référencer les ressources publiques depuis un template
 4. Cas pratique avec le framework CSS Bootstrap 4

Quiz
Accéder aux bases de données avec Doctrine
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

La gestion des ressources statiques


(images, feuilles de style, scripts
JS…)
1. Les ressources statiques dans une application
Symfony
Un élément important de la couche Vue est l’ensemble des ressources statiques dont
celle-ci a besoin. En effet, la quasi-totalité des sites web utilisent des feuilles de style,
des scripts JavaScript et/ou des images. Comme nous l’avons expliqué précédemment
(cf. Mise en place d’un projet Symfony - Structure de l’application), toutes ces
ressources doivent se trouver dans le répertoire public/ du projet : c’est le seul
répertoire accessible par un client navigateur web.
Lorsqu’il s’agit de mettre à disposition des ressources statiques pour les utiliser dans
les templates, c’est dans ce répertoire qu’il faut travailler. On pourra, par exemple,
créer des sous-répertoires afin d’y stocker les images, les CSS et les scripts
JavaScript.
2. Le cas des ressources statiques externes
Lorsque vous installez des composants supplémentaires dans votre application, par
exemple avec une recette Flex, il peut y avoir au sein de cette extension des
ressources statiques qui doivent être rendues disponibles dans le répertoire public/.
Si ces ressources sont placées dans des packages, mais que le serveur web doit y
accéder via public/, il faut forcément un mécanisme de synchronisation entre ces
emplacements.
Il existe une commande permettant de mettre à jour le contenu du dossier public/ en
important les ressources :
php bin/console assets:install public

Ce processus peut s’avérer fastidieux car lorsque vous travaillez sur un fichier
JavaScript, par exemple, il vous faut lancer cette commande après chaque
modification de manière à pouvoir tester la page avec la dernière version du script.
Une alternative permet de contourner cela : l’utilisation de liens symboliques
(symlinks). Appliqué à un répertoire, un lien symbolique est un dossier « miroir »
pointant vers ce répertoire. En quelque sorte, on pourrait dire que c’est une copie de
dossier synchronisée automatiquement.
php bin/console assets:install public --symlink

La commande ci-dessus crée des liens symboliques et non de simples copies de


dossiers. Les modifications apportées aux ressources publiques de vos bundles sont
alors synchronisées automatiquement avec le dossier public/.
La technique des liens symboliques a quelques contraintes :
 Le serveur web doit être configuré de manière à supporter les liens symboliques.
Sous Apache, il s’agit de l’option FollowSymlinks de la directive Options.
 Sous Windows, par défaut, la création de liens symboliques nécessite le
privilège Administrateur. De plus, les liens symboliques ne sont pas
supportés pour les versions antérieures à Windows Vista/Windows Server 2008.
Il est cependant important de noter que, dans la plupart des cas, cette commande est
exécutée automatiquement par la recette Flex.

3. Référencer les ressources publiques depuis un


template
Maintenant que nous savons comment les ressources publiques sont gérées par
Symfony, nous allons découvrir de quelle manière nous pouvons les référencer depuis
nos templates.
La fonction asset de Twig doit être utilisée pour générer les chemins vers les
ressources publiques :
<img src="{{ asset('img/logo.png') }}" />
{# référence public/img/logo.png #}

<link href="{{ asset('css/main.css') }}" rel="stylesheet"


type="text/css" />
{# référence public/css/main.css #}

<script
src="{{ asset('bundles/monbundle/js/mon_script.js') }}"></script>
{# référence public/bundles/monbundle/js/mon_script.js #}

4. Cas pratique avec le framework CSS Bootstrap 4


Le framework Bootstrap 4 fait partie des frameworks CSS parmi les plus
populaires. Il fournit des styles CSS et des comportements JavaScript par
défaut extrêmement intéressants pour les applications web modernes. Il est assez
simple de l’intégrer dans une application Symfony.
Tout d’abord, il s’agit de télécharger une distribution Bootstrap 4 disponible à
l’adresse https://getbootstrap.com/docs/4.0/getting-started/download/.
Une fois l’archive ZIP de Bootstrap téléchargée, vous pouvez extraire les
dossiers css/ et js/ qui sont contenus à l’intérieur vers le
dossier public/bootstrap/ que vous aurez créé en amont dans votre projet.
Il ne reste plus alors qu’à référencer la feuille de style et le script JavaScript
principaux de Bootstrap dans le gabarit de votre application, le
fichier templates/base.html.twig.
En voici le contenu modifié :
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>
{% block title %}
Bienvenue sur MonJournal !
{% endblock %}
</title>
{% block stylesheets %}
<link rel="stylesheet"
href="{{ asset('bootstrap/css/bootstrap.min.css') }}"
type="text/css" />
{% endblock %}
</head>
<body>
{% block body %}
<h1>Bienvenue sur MonJournal !</h1>
{% endblock %}
{% block javascripts %}
<script src="{{ asset('bootstrap/js/bootstrap.min.js') }}"
type="text/javascript" />
{% endblock %}
</body>
</html>

Les pages de votre application devraient être décorées du style par défaut de
Bootstrap 4. Il vous suffit ensuite d’explorer les styles CSS présents dans Bootstrap
pour enjoliver vos éléments HTML.
Précédent
Les filtres et les fonctions
Suivant
Quiz
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts

 1. Les principes de l’ORM


 2. Architecture de Doctrine
 a. DBAL
 b. Entité
 c. ORM
 3. La notion d’entité

Installer et configurer Doctrine


Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Présentation et concepts
1. Les principes de l’ORM
Doctrine est une librairie permettant de gérer les interactions entre une application et
une (ou plusieurs) base de données. Bien que ce ne soit pas la seule manière de
communiquer avec une base de données (il existe d’autres librairies, comme Propel,
l’extension PHP PDO, etc.), Symfony a clairement fait de Doctrine son option
favorite. En effet, cette dernière est préconfigurée par défaut dans Symfony depuis la
version 2 du framework.
L’ORM (Object-To-Relational Mapping), ou mapping objet relationnel, est un
concept permettant d’associer des classes du modèle objet de PHP avec des tables de
bases de données. Une fois cette association réalisée (le « mapping »), la manipulation
des classes et des objets agit directement sur les données stockées en base, permettant
ainsi au programmeur de simplifier les opérations basiques de gestion de données.

2. Architecture de Doctrine
Avant d’entrer dans le vif du sujet, il convient de définir certains concepts
nécessaires à la compréhension du fonctionnement de Doctrine.
Doctrine se compose de plusieurs couches : DBAL, ORM et Entité.

a. DBAL
La couche DBAL (Database Abstraction Layer) est la couche de plus bas niveau. Elle
ne comporte aucune logique applicative et son rôle est d’envoyer des requêtes vers
une base de données et de récupérer les résultats. Elle est comparable à l’extension
PHP PDO et pourrait être définie comme étant une version plus avancée de cette
extension.
PHP PDO est d’ailleurs utilisé en interne par la couche DBAL.
b. Entité
Les entités sont les classes d’une application ayant été configurées de manière à ce
que Doctrine puisse établir une correspondance entre ces dernières et des tables en
base de données.
La principale fonctionnalité apportée par Doctrine (par rapport à une utilisation
directe de l’extension PHP PDO par exemple) en plus de pouvoir récupérer des objets
au lieu de tableaux en résultats de requêtes (car PDO est également capable de le
faire), est d’établir une véritable relation entre ces objets et les tables en base de
données.
L’entité est donc le reflet applicatif d’une table de la base de données, ses
propriétés étant en quelque sorte équivalentes à des colonnes.
Ainsi, en utilisant Doctrine, vous
pouvez récupérer, ajouter, modifier et supprimer des données en base sans avoir à
écrire une seule ligne de code SQL, toutes ces actions pouvant être effectuées au
travers des objets Entité.
c. ORM
La couche ORM (Object-relational mapping) est la librairie placée au cœur du
système ; elle est l’intermédiaire entre l’application et la couche DBAL. Son rôle est
de convertir les données tabulaires reçues depuis le DBAL en entités, mais aussi de
transformer les interactions avec les différents objets mis à disposition du développeur
(que nous évoquerons plus loin) en requêtes SQL à transmettre au DBAL.
En clair, la couche ORM établit une correspondance entre la base de données
relationnelle et la programmation orientée objet.

3. La notion d’entité
Dans Doctrine, et de manière générale dans les frameworks d’ORM, la notion d’entité
se rapporte aux classes qui représentent des données stockées dans une table de base
de données. À part de rares exceptions, chaque table sera identifiée une classe et
chaque colonne de la table aura une représentation sous la forme d’un attribut dans la
classe.
De ce fait, une classe d’entité est essentiellement une classe de structure de données
métier. Les informations y sont représentées par des attributs privés, pour respecter les
principes d’encapsulation du modèle objet, et des méthodes d’accès
(getXXX() et setXXX()) y sont définies pour permettre l’accès à ces attributs.
Cette structure est importante car Doctrine va automatiquement appeler ces méthodes
d’accès afin de valoriser et de lire les données sur chaque entité au moment des
interactions avec la base. Autre point important : le constructeur. Il doit pouvoir être
instancié sans paramètres.
Il est important de noter ici que les entités ne sont pas considérées comme des
services Symfony. En effet leur cycle de vie étant géré par Doctrine, elles ne peuvent
être prises en charge par le Service Container.
Précédent
Quiz
Suivant
Installer et configurer Doctrine
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine

 1. Mise en place de Doctrine


 2. Relation avec PDO
 3. Configuration

Définition des entités et de leur mapping


Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Installer et configurer Doctrine


1. Mise en place de Doctrine
Si vous avez créé votre application en utilisant le squelette d’application web à l’aide
de l’une des commandes suivantes :
composer create-project symfony/website-skeleton :4.4.* monjournal

ou
symfony new mon_projet --version=4.4 --full

alors Doctrine est déjà présent par défaut dans votre application comme en atteste le
fichier composer.json situé à la racine du projet :
{
"type": "project",
"license": "proprietary",
"require": {
"php": ">=7.1.3",
...
"doctrine/doctrine-bundle": "^2.3",
...
},
...
}

Dans le cas contraire, il est nécessaire d’installer le pack Symfony ORM via la
commande :
composer require symfony/orm-pack

2. Relation avec PDO


Comme nous l’avons vu précédemment, la couche DBAL de Doctrine est
directement responsable de l’interaction avec la base de données. Pour ce faire,
Doctrine utilise la librairie PDO (PHP Data Object), disponible dans le langage PHP
depuis la version 5.3 de ce dernier. PDO permet d’utiliser un code d’accès aux
données identique quel que soit la base de données sous-jacente et se sert pour cela
d’un pilote d’accès aux données.
La disponibilité d’un pilote PDO pour votre base de données est donc le prérequis
essentiel à l’utilisation de Doctrine. PHP est fourni en standard avec des pilotes pour
les bases de données MySQL (ou MariaDB), PostgreSQL et SQLite ; pour toutes les
autres bases de données du marché, il est nécessaire de se référer au site de l’éditeur
afin de télécharger et d’installer le pilote PDO approprié.
Pour les pilotes fournis avec PHP, leur activation se fait au travers du fichier php.ini,
dont voici un exemple avec les pilotes MySQL et SQLite actifs :
;;;;;;;;;;;;;;;;;;;;;;
; Dynamic Extensions ;
;;;;;;;;;;;;;;;;;;;;;;
...
;extension=pdo_firebird
extension=pdo_mysql
;extension=pdo_oci
;extension=pdo_odbc
;extension=pdo_pgsql
extension=pdo_sqlite
...

La configuration du pilote PDO de votre base de données doit être réalisée avant
d’aller plus loin dans la démarche.

3. Configuration
La configuration principale de Doctrine est définie
dans config/packages/doctrine.yaml :
doctrine:
dbal:
url: '%env(resolve:DATABASE_URL)%'

orm:
auto_generate_proxy_classes: true
naming_strategy:
doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true
mappings:
App:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App

On y trouve deux sections, dbal et orm. La section orm permet de définir les
mécanismes de localisation des entités, comme le répertoire de base (src/Entity) et
l’espace de nom appliqué par défaut (App\Entity) alors que la section dbal fait
référence à la variable d’environnement DATABASE_URL.
La variable DATABASE_URL est définie dans les fichiers d’environnement de votre
projet Symfony, au minimum dans le fichier .env situé à la racine de votre projet.
Cette variable contient la chaîne de connexion PDO permettant à la couche DBAL de
Doctrine de se connecter à la base de données sous-jacente.
###> doctrine/doctrine-bundle ###
# Format described at https://www.doctrine-project.org/projects/doc
# trine-dbal/en/latest/reference/configuration.html#connecting-using-a-
# url
# IMPORTANT: You MUST configure your server version, either here or in
# config/packages/doctrine.yaml
#
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
# DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/
db_name?serverVersion=5.7"
# DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/
db_name?serverVersion=13&charset=utf8"
DATABASE_URL="mysql://monjournal:monjournal@127.0.0.1:3306/
monjournal?serverVersion=5.7"
###< doctrine/doctrine-bundle ###

Le format de configuration dépend du pilote PDO utilisé et il sera toujours


nécessaire d’indiquer la version du serveur de base de données utilisé (comme indiqué
dans les commentaires du fichier).
Pour plus d’information sur la syntaxe de l’URL de connexion en fonction de la base
de données, nous vous invitons à consulter la documentation de Doctrine à
l’adresse : https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/
reference/configuration.html#connecting-using-a-url
Précédent
Présentation et concepts
Suivant
Définition des entités et de leur mapping
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping

 1. Règles de conception des entités


 2. Les syntaxes pour le mapping des entités
 3. Le mapping d’entités simples
 a. Définir une entité avec @ORM\Entity
 b. Gérer les colonnes de la table avec @ORM\Column
 c. @ORM\Table
 d. Les clés primaires
 e. Configurer les index
 4. Le mapping des relations (clés étrangères)
 a. @ORM\OneToOne
 b. @ORM\ManyToOne
 c. @ORM\ManyToMany
 d. Relations bidirectionnelles
 5. Les outils de la console
 a. Repérer les erreurs de mapping
 b. Générer le schéma des données à partir des entités
 c. Générer les entités à partir du schéma des données

Manipulation des entités avec l’EntityManager


Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes
Définition des entités et de leur
mapping
La première étape consiste à créer des entités. Comme nous l’avons vu
précédemment, une entité est une classe « spéciale », dans le sens où Doctrine est
capable de l’associer à une table en base de données. Ce concept pouvant sembler flou
au premier abord, illustrons-le au travers d’un exemple.

1. Règles de conception des entités


Imaginons que vous souhaitiez répertorier des livres dans une base de
données, chaque livre ayant un id, un titre et une date de parution.
Ici, l’entité est une simple classe, contenant les différentes informations dans des
propriétés :
<?php

namespace App\Entity;

class Livre
{
private $id;

private $titre;

private $dateParution;

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

/**
* @param string $titre
* @return Livre
*/
public function setTitre($titre)
{
$this->titre = $titre;

return $this;
}

/**
* @return string
*/
public function getTitre()
{
return $this->titre;
}

/**
* @param \DateTime $dateParution
* @return Livre
*/
public function setDateParution($dateParution)
{
$this->dateParution = $dateParution;

return $this;
}

/**
* @return \DateTime
*/
public function getDateParution()
{
return $this->dateParution;
}
}
Les propriétés sont privées, et des accesseurs et des mutateurs doivent donc être créés.
Les propriétés de vos entités ne doivent jamais être publiques, mais privées ou
protégées.

2. Les syntaxes pour le mapping des entités


Pour l’instant, notre entité est une classe PHP banale. Elle n’est en aucun cas
configurée de manière à être utilisée au travers de Doctrine.
C’est ici que le mapping intervient, car ce dernier consiste à configurer des
informations (telles que le nom de la table, les colonnes et leur type, etc.) pour une
entité donnée.
Tout comme c’est le cas pour le routage, il existe plusieurs formats de configuration
pour les entités : le YAML, le XML, le PHP et les annotations.
Au cours de ce chapitre, nous nous concentrons sur les annotations car c’est le format
le plus abordable ; son utilisation est intuitive et les configurations sont souvent plus
rapides qu’avec les autres formats.
Si vous souhaitez utiliser un autre format, n’hésitez pas à parcourir la documentation
officielle (https://symfony.com/doc/4.4/doctrine.html) en parallèle de la lecture de ce
chapitre.
Configurons notre entité avec les annotations Doctrine :
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
* @ORM\Table(name="livre")
*/
class Livre
{
/**
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* @ORM\Column(name="titre", type="string", length=255)
*/
private $titre;

/**
* @ORM\Column(name="date_parution", type="date",
* nullable=true)
*/
private $dateParution;

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

/**
* @param string $titre
* @return Livre
*/
public function setTitre($titre)
{
$this->titre = $titre;

return $this;
}

/**
* @return string
*/
public function getTitre()
{
return $this->titre;
}

/**
* @param \DateTime $dateParution
* @return Livre
*/
public function setDateParution($dateParution)
{
$this->dateParution = $dateParution;

return $this;
}

/**
* @return \DateTime
*/
public function getDateParution()
{
return $this->dateParution;
}
}

Nous détaillons les différentes annotations utilisées plus loin dans ce chapitre. La
dernière étape consiste à mettre à jour sa base de données en lançant la commande
suivante :
php bin/console doctrine:schema:update --force

La commande ci-dessus synchronise le schéma de votre base de données en fonction


des différentes entités de votre application. Ici, elle crée une table livre avec les
colonnes id, titre et date_parution, respectivement de types numérique, chaîne de
caractères et date (soit INT, VARCHAR et DATE, si vous utilisez une base de
données MySQL).
Il est possible de ne pas modifier la base de données mais d’afficher les requêtes à
effectuer pour la mettre à jour, avec l’option --dump-sql :
$ php bin/console doctrine:schema:update --dump-sql
CREATE TABLE livre (id INT AUTO_INCREMENT NOT NULL, titre
VARCHAR(255) NOT NULL, date_parution DATE NULL,
PRIMARY KEY(id)) DEFAULT CHARACTER SET
utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB

Il est important de comprendre que les annotations ont un impact sur la structure de
votre base de données. Bien évidemment, cette dernière ne se mettra pas à jour
automatiquement à la sauvegarde du fichier contenant l’entité.
Veillez donc à exécuter la commande doctrine:schema:update après chaque
modification du mapping de vos entités, de manière à synchroniser votre base de
données.
Votre application est maintenant prête à interagir avec la table livre.
Mais nous n’allons pas nous y atteler tout de suite ; commençons par décrire les
principales directives disponibles pour le mapping de vos entités.

3. Le mapping d’entités simples


a. Définir une entité avec @ORM\Entity
Mise au-dessus d’une classe, cette annotation la désigne comme étant une entité. Sans
celle-ci, Doctrine serait incapable de la détecter.
Elle doit être placée de cette manière :
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
*/
class MonEntite
{
// ...
}

Les annotations Doctrine sont contenues au sein de l’espace de noms Doctrine\ORM\


Mapping. Par convention, nous utilisons l’alias ORM.
Cette annotation accepte deux paramètres : repositoryClass et readOnly.
Le premier permet de définir un repository personnalisé (nous y reviendrons plus
tard) tandis que readOnly indique que l’entité n’est pas concernée par les mises à
jour. C’est une optimisation pour les tables de référence, dont les valeurs sont fixes.
Avec le mapping de l’exemple ci-dessous, vous ne pourrez pas modifier
d’entité MonEntite, mais vous pourrez tout de même en ajouter ou en supprimer.
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity(readOnly=true)
*/
class MonEntite
{
// ...
}

b. Gérer les colonnes de la table avec @ORM\Column


Cette annotation est primordiale car elle configure les colonnes de la table associée à
l’entité.
Chaque propriété de votre entité peut être précédée d’une annotation @ORM\
Column. Si c’est le cas, cette propriété devient « liée » à une colonne en base de
données. Reprenons l’annotation @ORM\Column utilisée dans notre exemple
d’introduction :
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
* @ORM\Table(name="livre")
*/
class Livre
{
/**
* @ORM\Column(name="titre", type="string", length=255)
*/
private $titre;

// ...

Nous remarquons qu’elle possède plusieurs propriétés, dont name et type.


name
La propriété d’annotation name correspond au nom de colonne à utiliser en base de
données. Il est assez commode d’utiliser le même nom que la propriété de classe
(ici $titre). C’est également le comportement par défaut de Doctrine : si cette
propriété d’annotation n’est pas renseignée, il utilise le nom de la propriété de classe
sans le $. Le code ci-dessus est donc équivalent à :
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
* @ORM\Table(name="livre")
*/
class Livre
{
/**
* @ORM\Column(type="string", length=255)
*/
private $titre;

// ...

type
Cette propriété définit le type de données de la colonne. Les valeurs acceptées ne sont
pas les types concrètement utilisés en base de données (VARCHAR, INT, etc.) car ce
concept de « type » n’englobe pas seulement le type de la colonne en base de
données, mais également le type de la valeur contenue dans la propriété de l’objet.
Doctrine effectue une conversion entre ces deux représentations. Dans l’exemple
précédent, ce n’est pas flagrant car les deux représentations du type string sont les
mêmes.
Examinons un autre type pour mettre en avant cette nuance :
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
*/
class Article
{
/**
* @ORM\Column(type="date")
*/
private $date;

// ...

Ici, la colonne date sera de type DATE, tandis que la valeur attendue dans la
propriété $date sera un objet PHP de type DateTime. Lorsque vous récupérez une
entité, la propriété $date contient donc un objet DateTime. De la même manière,
lorsque vous travaillez avec cette propriété, en vue de créer ou de modifier une ligne
en base de données, il faut injecter des objets DateTime (et non des chaînes de
caractères comme ’2017-05-10’).
Les différents types disponibles
sont : string, integer, smallint, bigint, boolean, decimal, date, time, datetime, text
, object, array et float.
Leurs noms sont assez explicites mais n’hésitez pas à vous reporter à la
documentation officielle pour plus de
précisions : https://www.doctrine-project.org/projects/doctrine-orm/en/latest/
reference/basic-mapping.html#doctrine-mapping-types
length
Cette propriété spécifie la longueur de la colonne en base de données. Elle n’est
applicable que si le type est string et elle vaut par défaut 255.
/**
* @ORM\Column(type="string", length=50)
*/
private $titre;

unique
Cette propriété, mise à true, ajoute un index unique sur la colonne :
/**
* @ORM\Column(type="string", unique=true)
*/
private $titre;

Si vous souhaitez définir une clé unique sur plusieurs colonnes, reportez-vous à la
propriété uniqueConstraints de l’annotation @ORM\Table, qui est détaillée plus loin.
nullable
Indique si la valeur NULL est autorisée pour la colonne. Par défaut, elle ne l’est pas.
/**
* @ORM\Column(type="string", nullable=true)
*/
private $nom;

precision et scale
Ces deux propriétés s’appliquent aux colonnes de type float. precision définit le
nombre total de chiffres, que ce soit avant ou après la virgule, et scale définit le
nombre de chiffres après la virgule.
/**
* @ORM\Column(type="float", precision=6, scale=2)
*/
private $valeur;

Ici, la propriété valeur contiendra des nombres décimaux de la forme


suivante : 3005,31.
c. @ORM\Table
Cette annotation permet de définir des options liées à la table en base de données : son
nom et ses index (à l’exception de la clé primaire).
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
* @ORM\Table(name="membre",
* uniqueConstraints={
* @UniqueConstraint(name="pseudo_uniq", columns={"pseudo"})
* },
* indexes={
* @Index(name="email_idx", columns={"email"}),
* @Index(name="nom_idx", columns={"nom"})
* }
* )
*/
class Utilisateur
{
// ...
}

L’exemple ci-dessus est une entité dont la table en base de données est
nommée membre ; les colonnes email et nom sont des index et la
colonne pseudo est un index unique.
Les index sont placés sous forme de tableaux d’annotations au sein de la
propriété indexes. L’annotation utilisée pour chaque index est @Index (détaillée plus
loin).
Quant aux index uniques, ils sont placés dans la propriété uniqueConstraints et
utilisent l’annotation @UniqueConstraint (détaillée plus loin également).
Changer le moteur de table
Avec MySQL, Doctrine utilise par défaut InnoDB comme moteur pour les tables. Si
vous souhaitez en utiliser un autre pour une table donnée, vous pouvez le spécifier au
travers d’un attribut options, sous la clé engine :
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
* @ORM\Table(name="membre", options={"engine": "MyISAM"})
*/
class Utilisateur
{
// ...
}

Sous MySQL, nous vous déconseillons d’abandonner le moteur InnoDB au profit de


MyISAM. En effet, InnoDB est plus sûr pour vos données car il garantit l’intégrité
référentielle et sait gérer les transactions. Si vous voulez changer de moteur pour
pouvoir utiliser les index de type FULLTEXT, sachez qu’InnoDB les prend en
charge à partir de la version 5.6 de MySQL.
d. Les clés primaires
Une clé primaire se définit avec l’annotation @ORM\Id, qui doit être placée au-
dessus d’une propriété, voire de plusieurs propriétés (dans le cas d’une clé primaire
multicolonne).
@ORM\Id peut être utilisé conjointement avec @ORM\GeneratedValue, qui
configure la politique de génération de valeurs pour cette colonne.
En pratique, le type de clé primaire le plus utilisé est la clé sur une colonne, dont la
valeur est générée automatiquement par la base de données :
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
*/
class Livre
{
/**
* @var integer
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;

// ...

Ici, une technique de génération par identité est utilisée (IDENTITY) ; pour MySQL
cela signifie l’usage de la propriété AUTO_INCREMENT. Pour les bases de données
Oracle et PostgreSQL, il faut utiliser la valeur SEQUENCE.
Si l’annotation @ORM\GeneratedValue est omise, vous devrez gérer la clé primaire
au niveau applicatif, en vous assurant qu’elle est unique pour chaque nouvelle entité
créée.
e. Configurer les index
@ORM\Index
Cette annotation est utilisée dans la propriété indexes de l’annotation @ORM\Table.
Elle possède deux propriétés : name et columns. La première spécifie le nom de
l’index, tandis que columns contient la ou les colonnes pour cet index.
L’exemple ci-dessous configure un index multicolonne (sur les
colonnes col_1 et col_2 de la table) :
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
* @ORM\Table(name="ma_table",
* indexes={
* @Index(name="multi_idx", columns={"col_1", "col_2"})
* }
* )
*/
class MonEntite
{
// ...
}

@ORM\UniqueConstraint
Tout comme @ORM\Index, @ORM\UniqueConstraint comporte les
propriétés name et column et est utilisée au sein de l’annotation @ORM\Table.
namespace Eni\DemoBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
* @ORM\Table(name="membre",
* uniqueConstraints={
* @UniqueConstraint(name="uniq", columns={"nom", "prenom"})
* }
* )
*/
class Utilisateur
{
// ...
}

Ici, une clé unique multicolonne est configurée sur nom et prenom.

4. Le mapping des relations (clés étrangères)


Les clés étrangères sont indispensables à toute base de données relationnelle. Elles
permettent de créer des relations entre les tables, tout en garantissant une intégrité
référentielle.
Pour chaque relation que vous souhaitez créer, vous devez tout d’abord réfléchir au
nombre d’occurrences possibles pour chacune des deux entités y participant. Ce
paramètre est une notion importante de la relation, et s’appelle la « cardinalité ».
Vous êtes peut-être familier avec ce terme si vous avec déjà utilisé la
méthode Merise pour un projet informatique, au travers de MCD (modèles
conceptuels de données). Ces derniers schématisent les entités et leurs relations, en
affectant une cardinalité pour chacune (1-n, n-n, etc.).
L’annotation à utiliser pour une relation dépend directement de sa cardinalité.
Doctrine propose les annotations suivantes :
 @ORM\OneToOne : une entité A est associée à une entité B (1-1).
 @ORM\ManyToOne : plusieurs entités A sont associées à une entité B (n-1).
 @ORM\ManyToMany : plusieurs entités A sont associées à plusieurs entités B
(n-n).
a. @ORM\OneToOne
Cette annotation est utilisée pour une relation de type 1-1.
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
*/
class Biographie
{
// ...

/**
* @ORM\OneToOne(
* targetEntity="App\Entity\Auteur"
* )
*/
private $auteur;

// ...
}

Ici, l’entité Biographie a une relation avec une entité Auteur et cette relation est de
type 1-1. Une clé étrangère est donc placée sur la table de l’entité Biographie, sur une
nouvelle colonne auteur_id, qui référence la clé primaire id de la table correspondant
à l’entité Auteur.
Personnaliser les colonnes de la relation
Pour l’exemple précédent, les deux colonnes auteur_id et id sont les colonnes par
défaut attendues par Doctrine dans le cadre de relations. Si vous souhaitez
personnaliser ces noms, vous devez utiliser une annotation spéciale : @ORM\
JoinColumn.
Cette dernière permet de configurer les colonnes utilisées pour la relation. Elle
possède les propriétés suivantes :
 name : nom à utiliser pour la colonne contenant l’identifiant de clé étrangère.
 referencedColumnName : nom de la clé primaire de la table référencée.
 unique : si cette propriété est placée à true, un index unique est créé pour la
colonne contenant l’identifiant de clé étrangère.
 nullable : par défaut, la relation est optionnelle, mais si cette propriété est mise
à false, elle devient obligatoire (la colonne contenant la clé étrangère n’accepte
pas la valeur NULL).
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* Biographie
*
* @ORM\Entity
*/
class Biographie
{
// ...

/**
* @ORM\OneToOne(
* targetEntity="App\Entity\Auteur"
* )
* @ORM\JoinColumn(
* name="mon_auteur",
* referencedColumnName="sa_cle_primaire",
* unique=true
* )
*/
private $auteur;

// ...

b. @ORM\ManyToOne
Cette annotation est utilisée pour configurer les relations de type n-1.
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
*/
class Critique
{
// ...

/**
* @ORM\ManyToOne(targetEntity="Livre")
*/
private $livre;

Dans l’exemple ci-dessus, l’entité Critique est en relation avec l’entité Livre.
Plusieurs critiques sont reliées à un livre. De manière plus naturelle, on peut dire
qu’un livre possède plusieurs critiques.
Notons au passage que le nom de classe dans targetEntity n’est pas obligatoirement
un FQCN (Fully Qualified Class Name), si les deux entités appartiennent au même
espace de noms.
c. @ORM\ManyToMany
L’annotation @ORM\ManyToMany est capable de configurer les relations de
type n-n.
Ce type de relation implique une particularité, car, lors d’une relation entre deux
tables A et B, avoir une clé étrangère sur la table A limiterait le nombre d’occurrences
de B à 1. Il faut donc passer par une table de jointure.
Imaginons qu’un Livre puisse avoir plusieurs Thèmes. De la même manière,
un Thème pourrait regrouper plusieurs Livres. Nous serions ici dans le cas d’une
relation de type n-n, qui serait configurable comme suit :
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
*/
class Livre
{
// ...

/**
* @ORM\ManyToMany(targetEntity="Theme")
*/
private $themes;

En mettant à jour votre base de données (doctrine:schema:update), vous vous


apercevez qu’une table de jointure a été créée. Elle s’appelle livre_theme et possède
deux colonnes : livre_id et theme_id.
Personnaliser la table de la relation
Ces valeurs par défaut sont personnalisables, grâce à @ORM\JoinTable, qui est
capable de contrôler finement la définition de la table de jointure.
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
*/
class Livre
{
// ...

/**
* @ORM\ManyToMany(targetEntity="Theme")
* @ORM\JoinTable(name="themes_des_livres",
* joinColumns={
* @ORM\JoinColumn(
* name="id_du_livre",
* referencedColumnName="id"
* )
* },
* inverseJoinColumns={
* @ORM\JoinColumn(
* name="theme_id",
* referencedColumnName="id"
* )
* }
* )
*/
private $themes;

La propriété name contient le nom à utiliser pour la table de jointure. Les


propriétés joinColumns et inverseJoinColumns contiennent une
annotation @ORM\JoinColumn. joinColumns correspond à la colonne de l’entité
courante (ici Livre) tandis que inverseJoinColumns est celle qui référence l’autre
entité (Theme).
À moins que vous n’ayez la contrainte de partir d’une base de données existante dont
la structure doit rester telle quelle, nous vous conseillons de ne pas utiliser les
annotations @JoinColumn et @JoinTable pour modifier les noms des colonnes
participant aux jointures, mais de vous appuyer sur les noms par défaut générés par
Doctrine. Néanmoins, nous verrons plus loin que ces annotations sont utiles pour
d’autres tâches, comme la gestion des opérations en cascade.
d. Relations bidirectionnelles
Les deux entités intervenant pour chaque relation n’ont pas strictement le même rôle :
il y a un côté propriétaire et un côté inverse. Par côté propriétaire, nous entendons le
côté de la relation possédant la clé étrangère. En procédant par élimination, le côté
inverse est donc l’autre entité.
L’annotation d’association doit toujours être placée du côté propriétaire. Il peut être
parfois délicat à détecter et voici quelques règles à respecter en cas de doute :
 La relation est de type n-1, le côté propriétaire est alors le côté n.
 La relation est de type 1-1, le côté propriétaire est alors celui qui est inséré en
second en base de données. À titre d’exemple, pour une relation entre
un Utilisateur et une Adresse, nous aurions plutôt tendance à placer la clé
étrangère sur la table Adresse, car on crée une adresse pour un utilisateur et non
l’inverse.
 La relation est de type n-n, il n’y a alors pas de côté propriétaire car aucune clé
étrangère n’est placée sur les tables correspondant aux entités, mais sur une table
de jointure. Dans ce cas-là, vous pouvez placer l’annotation d’association sur
l’un ou l’autre des côtés.
Une fois l’annotation d’association mise sur la bonne entité (côté propriétaire), une
limitation se fait sentir : « comment récupérer l’entité (ou les entités) du côté
propriétaire depuis le côté inverse ? ». Depuis l’entité Adresse précédemment
évoquée, la méthode getUtilisateur() permettrait de récupérer
l’entité Utilisateur correspondante. Mais depuis une entité Utilisateur, il n’y a
aucune propriété faisant référence à son Adresse. Or, d’un point de vue applicatif,
récupérer une Adresse pour un Utilisateur semble être un besoin plus naturel.
Pour contourner cette limitation, Doctrine propose les relations bidirectionnelles. Ces
dernières consistent à créer une propriété sur l’entité du côté inverse, qui référence
l’entité du côté propriétaire.
Voici comment créer une relation bidirectionnelle entre des entités Livre et Auteur :
<?php
// src/Entity\Livre.php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;


/**
* Livre
*
* @ORM\Entity
*/
class Livre
{
// ...

/**
* @ORM\ManyToOne(
* targetEntity="App\Entity\Auteur",
* inversedBy="livres"
* )
*/
private $auteur;

// ...

/**
* Get auteur
*
* @return App\Entity\Auteur
*/
public function getAuteur()
{
return $this->auteur;
}
// ...
}
<?php
// src/Entity\Auteur.php

namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;

/**
* Auteur
*
* @ORM\Table()
* @ORM\Entity
*/
class Auteur
{
// ...

/**
* @ORM\OneToMany(
* targetEntity="App\Entity\Livre",
* mappedBy="auteur"
* )
*/
private $livres;

// ...

/**
* Get livres
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getLivres()
{
return $this->livres;
}

// ...
}

Ici, selon les règles que nous avons définies précédemment, le côté propriétaire doit
être Livre : c’est le côté n de la relation n-1 (ou ManyToOne). Dans
l’association @ORM\ManyToOne qu’il contient, la propriété utilisée pour cette
relation dans l’entité du côté inverse (Auteur) doit être spécifiée au sein du
paramètre inversedBy. Ici, nous utilisons livres. Il faut donc créer une
propriété $livres dans l’entité Auteur.
L’annotation d’association utilisée dans Auteur est @ORM\OneToMany (1-n). Elle
est la relation inverse de @ORM\ManyToOne (n-1), et possède quant à elle le
paramètre mappedBy, qui référence la propriété de relation de l’entité du côté
propriétaire (Livre), à savoir auteur.
Ainsi configurée, cette relation est bidirectionnelle ; l’Auteur est récupérable depuis
le Livre, et vice versa.
Pour les relations @ORM\OneToOne (1-1) et @ORM\ManyToMany (n-
n), l’inversion de la relation n’affecte pas sa cardinalité, l’annotation du côté inverse
reste donc la même.
Relation bidirectionnelle avec l’annotation @ORM\OneToOne
Voici un second exemple de relation bidirectionnelle. Les entités y participant
sont Biographie et Auteur, la relation est de type 1-1 et le côté propriétaire
est Biographie.
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
*/
class Biographie
{
// ...

/**
* @ORM\OneToOne(
* targetEntity="App\Entity\Auteur",
* inversedBy="biographie"
* )
*/
private $auteur;

// ...
}
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
*/
class Auteur
{
// ...

/**
* @ORM\OneToOne(
* targetEntity="App\Entity\Biographie",
* mappedBy="auteur"
* )
*/
private $biographie;

// ...
}

L’annotation du côté inverse n’a strictement aucun impact sur la structure de votre
base de données, elle intervient seulement au niveau applicatif.

5. Les outils de la console


La console Symfony dispose d’un grand nombre de commandes dédiées à Doctrine ;
elles sont toutes disponibles avec le préfixe doctrine:.
a. Repérer les erreurs de mapping
Un mapping mal configuré pourrait très facilement compromettre la suite des
opérations en provoquant des erreurs difficiles à déboguer. Une fois le mapping de
votre application mis en place, il est très important de tester sa validité.
Pour cela, vous devez effectuer la commande suivante :
php bin/console doctrine:schema:validate

Voici le résultat si aucune erreur n’est détectée :


[Mapping] OK - The mapping files are correct.
[Database] OK - The database schema is in sync with the mapping
files.

Cette commande effectue deux vérifications. Dans un premier temps, votre mapping
est passé au peigne fin. Ici, il est correctement défini. Si tel n’est pas le cas, repérez
l’entité mentionnée dans le message d’erreur et vérifiez son mapping. Ensuite, la
structure de votre base de données est analysée. Si elle ne correspond pas à celle
attendue en fonction du mapping de vos entités, une erreur sera affichée. Pour régler
le problème, vous devrez tout simplement lancer la
commande doctrine:schema:update.
b. Générer le schéma des données à partir des entités
Précédemment dans ce chapitre, nous avons vu qu’il était possible de créer le schéma
de base de données (les tables) à partir du mapping des entités ; pour cela la
commande doctrine:schema:update est utilisée.
php bin/console doctrine:schema:update --dump-sql

Cette commande permet de voir les requêtes SQL qui seront générées pour mettre à
jour la structure de données.
php bin/console doctrine:schema:update --force
Cette commande permet quant à elle de mettre à jour effectivement les tables, ce qui
peut engendrer des pertes de données.
c. Générer les entités à partir du schéma des données
Une autre option de génération consiste à produire les entités à partir d’un schéma de
données existant. Cette approche est courante dans la mesure où de nombreuses
applications s’appuient sur des structures de données existantes. Avec un schéma très
complexe constitué de plusieurs dizaines de tables, le gain de temps est considérable
car toutes les entités seront automatiquement générées.
Pour générer les entités à partir d’un schéma de données, il faut procéder en deux
étapes :
 Générer le mapping des entités ;
 Générer les classes d’entités.
Générer le mapping
On utilise la commande doctrine:mapping:import en précisant :
 l’espace de noms des entités ;
 le format de configuration ;
 le répertoire dans lequel le code doit être généré.
À titre d’exemple :
php bin/console doctrine:mapping:import App\Entity annotation --path=src/

permet de générer le mapping pour les entités de l’application en utilisant le


format annotation (on peut utiliser les formats XML, YAML, ou PHP).
Dans le cas où le format de mapping choisi est annotation, une structure de code de
base est générée afin de pouvoir y positionner les annotations de mapping. Pour les
autres formats, c’est un fichier de configuration au format indiqué qui sera produit.
Il est important de bien regarder le mapping généré et potentiellement de l’ajuster : la
console n’est pas nécessairement capable de tenir compte de toutes les subtilités dont
vous avez besoin dans votre application. Par exemple, la console est incapable de
générer des associations bidirectionnelles entre les entités.
Générer les classes d’entités
Une fois le mapping produit, ajusté et validé, il est possible de générer les entités. La
console propose la commande make:entity pour ce faire.
Cette commande permet de créer interactivement une nouvelle entité. Dans notre cas,
il s’agit de produire ou mettre à jour une entité à partir d’un mapping, on y ajoutera
donc l’option --regenerate.
php bin/console make:entity Article --regenerate

Dans le cas où le mapping a été généré via des annotations, cette commande se
contente simplement d’ajouter les getters et les setters à la classe d’entités.
Précédent
Installer et configurer Doctrine
Suivant
Manipulation des entités avec l’EntityManager
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager

 1. Le rôle de l’EntityManager
 2. Insertion de données
 3. Modification de données
 4. Suppression de données
 5. Autres opérations de l’EntityManager
 a. refresh()
 b. detach()
 6. Les opérations en cascade

Récupérer des entités


Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Manipulation des entités avec


l’EntityManager
1. Le rôle de l’EntityManager
L’EntityManager est l’objet permettant d’effectuer les opérations liées à
l’altération des données, à savoir les requêtes de
type INSERT, UPDATE et DELETE.
Au niveau de votre application, même si vous manipulez des entités, qui
correspondent aux données présentes en base, il ne suffit pas de modifier la
valeur d’une propriété (au travers d’un mutateur) pour que Doctrine fasse la
synchronisation en base de données. Vous devez gérer ces opérations avec
l’EntityManager.

2. Insertion de données
Pour insérer une ligne dans une table donnée, il faut tout d’abord instancier l’entité
correspondant à cette table :
use App\Entity\Livre;

$livre = new Livre();

Ensuite, les données à insérer doivent être injectées au travers des mutateurs :
use App\Entity\Livre;

$livre = new Livre();


$livre->setTitre('La ferme des animaux');
$livre->setDateParution(new \DateTime('1945-08-17'));

Enfin, il ne reste plus qu’à demander à Doctrine d’envoyer la requête d’insertion vers
la base de données :
use App\Entity\Livre;

$livre = new Livre();


$livre->setTitre('La ferme des animaux');
$livre->setDateParution(new \DateTime('1945-08-17'));

$em = $container->get('doctrine')->getManager();
$em->persist($livre);
$em->flush();

Revenons sur cette dernière étape plus en détail. Dans un premier temps, nous
récupérons l’EntityManager de Doctrine. Ce dernier peut être récupéré en
invoquant la méthode getManager() du service doctrine.
Dans la majorité des situations, le code de récupération de l’EntityManager se trouve
au sein d’un service de manipulation de données créé dans votre application.
L’avantage de cette approche est que l’EntityManager Doctrine pourra y être injecté.
Voici un exemple d’un tel service :
namespace App\Model;

use App\Entity\Livre;
use Doctrine\ORM\EntityManagerInterface;

class ArticleService
{
private $em;

public function __construct(EntityManagerInterface $em)


{
$this->em = $em;
}

public function ajouterLivre(Livre $livre) {


$this->em->persist($article);
$this->em->flush();
}
...
}

Le type EntityManagerInterface est le type du service EntityManager de Doctrine, ici


il est injecté par le constructeur et mémorisé dans l’attribut $em.
Comme vous pouvez le constater, l’EntityManager doit être invoqué à deux reprises
pour une insertion, avec les méthodes persist() et flush(). La première méthode
indique à Doctrine que l’entité passée en argument a subi des modifications, et la
seconde lui demande de répercuter ces modifications en base de données.
Ce point peut paraître assez déroutant pour certaines personnes, surtout si elles ont
une expérience avec Doctrine1, par exemple, où il suffisait d’invoquer une méthode
sur l’entité ($entite->save()) pour un résultat équivalent. Mais les deux méthodes
distinctes proposées par Doctrine2 ont leurs avantages : les entités n’ont pas besoin
d’hériter d’une classe quelconque, et surtout, ce découpage permet l’introduction des
transactions.
Ainsi, si vous effectuez des modifications sur plusieurs tables, et que l’opération
venait à échouer en cours d’exécution, vous pourriez vous retrouver avec des données
potentiellement corrompues. Avec Doctrine2, ce n’est pas le cas : toutes les
requêtes INSERT, UPDATE et DELETE sont englobées dans des transactions. En
cas de problème, la base de données est rétablie à son état initial et le développeur,
quant à lui, peut être alerté de la situation s’il a mis en place des fichiers de
journalisation (cf. chapitre Journalisation et surveillance avec Symfony - Générer
des journaux avec Monolog).
Dans la suite de cette section, les exemples de code partiront du principe que
l’EntityManager est injecté via le constructeur et disponible au travers d’un
attribut nommé $em.

3. Modification de données
La modification de données est quasiment identique à l’insertion. La seule
différence est que l’entité cible n’est pas instanciée manuellement, mais récupérée au
travers de Doctrine. Elle correspond donc déjà à une ligne en base de données.
Dans un second temps, les modifications sont appliquées sur l’entité, avant
l’invocation des méthodes persist() et flush() de l’EntityManager :
public function modifierTitreLivre($idLivre)
{
$repository = $this->em->getRepository('App:Livre');

$livre = $repository->find($idLivre);
$livre->setTitre('Nouveau titre');

$this->em->persist($livre);
$this->em->flush();
}

Avec l’exemple ci-dessus, une invocation de la méthode modifierTitreLivre() avec


la valeur 2 en paramètre modifiera le titre du livre dont l’identifiant en base de
données est 2 pour la valeur Nouveau titre.
Cet exemple introduit également le concept de repository, sur lequel nous
reviendrons plus loin. Ici, nous utilisons la méthode find() de ce dernier, qui
permet de récupérer une entrée grâce à la valeur de sa clé primaire.

4. Suppression de données
La suppression de données implique tout d’abord, comme c’est le cas pour une
modification, de récupérer l’entité. Sur cette entité, la méthode remove() de
l’EntityManager doit être appliquée :
public function supprimerLivre($idLivre)
{
$repository = $this->em->getRepository(App:Livre');

$livre = $repository->find($idLivre);

$this->em->remove($livre);

$this->em->flush();
}

5. Autres opérations de l’EntityManager


Les principales actions que vous effectuez avec l’EntityManager sont l’insertion, la
modification et la suppression. Cependant, il en existe d’autres :
a. refresh()
Cette méthode permet de mettre à jour les données d’une entité en allant les récupérer
en base de données. Il se peut que les données d’une entité ne soient pas à jour. Cela
arrive si, par exemple, la base de données peut subir des modifications par un autre
biais que celui de Doctrine.
En pratique, vous pourriez avoir besoin de refresh() lors de vos tests fonctionnels, par
exemple pour comparer les données d’une entité avant et après l’exécution d’une
tâche quelconque.
b. detach()
La méthode detach() fait oublier à Doctrine une entité après qu’elle a subi
un persist() ou un remove(), mais avant le flush() :
$livre = new Livre();
$livre->setTitre('La ferme des animaux');
$livre->setDateParution(new \DateTime('1945-08-17'));

$this->em->persist($livre);

// détache l'entité
$this->em->detach($livre);

$this->em->flush();

Ici, le livre ne sera pas persisté en base de données.

6. Les opérations en cascade


Nous venons de voir comment insérer, modifier et supprimer des entités.
Cependant, vous vous rendrez rapidement compte que, dans certains cas, une
suppression peut échouer. Si vous essayez de supprimer une entité ayant au moins une
relation où elle agit en tant que côté inverse, il est fort probable qu’une erreur soit
retournée par votre base de données.
La possibilité d’effectuer des opérations en cascade permet de contourner ce
problème. Les opérations en cascade sont une fonctionnalité inhérente aux bases de
données relationnelles. Il existe deux manières de les implémenter : au niveau
applicatif ou au niveau de la base de données.
Nous vous recommandons de déléguer cette tâche à votre base de données dans la
mesure du possible, et d’utiliser Doctrine en tant que « roue de secours », pour les
situations où les opérations en cascade ne seraient pas supportées nativement par votre
base de données (par exemple, pour MySQL, une table MyISAM).
Gestion au niveau de la base de données
Pour une relation entre des entités Auteur et Livre, voici comment configurer les
opérations en cascade au niveau de la base de données :
<?php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
*/
class Livre
{
// ...

/**
* @ORM\ManyToOne(
* targetEntity="Auteur",
* onDelete="CASCADE",
* onUpdate="CASCADE",
* )
*/
private $auteur;

Ici, les clauses ON DELETE CASCADE et ON UPDATE CASCADE seront


ajoutées lors de la création de la clé étrangère. Ainsi, lors de la suppression
d’un Auteur, tous ses livres seront supprimés. Lors de la modification de la valeur
d’une clé primaire pour un Auteur (ce qui, au passage, est fortement déconseillé), les
valeurs seront mises à jour pour chaque livre.
Les opérations en cascade gérées par la base de données sont définies sur la propriété
de l’entité du côté propriétaire de la relation.
Gestion des opérations en cascade par Doctrine
Les opérations en cascade gérées par Doctrine ne se limitent pas à la suppression,
mais comprennent aussi l’ajout. Ainsi, par défaut, pour insérer des Livres pour
un Auteur, il faudrait utiliser ce morceau de code :
<?php

$auteur = new Auteur();

foreach (array('Titre 1', 'Titre 2') as $titre) {


$livre = new Livre();
$livre->setTitre($titre);
$auteur->addLivre($livre);
$this->em->persist($livre);
}

$this->em->persist($auteur);
$this->em->flush();

Comme vous pouvez le constater, il faut veiller à invoquer la méthode persist() de


l’EntityManager sur chaque entité Livre. Pour éviter cette « contrainte », vous pouvez
configurer des opérations en cascade :
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* Auteur
*
* @ORM\Table()
* @ORM\Entity
*/
class Auteur
{
// ...

/**
* @ORM\OneToMany(
* targetEntity="Eni\DemoBundle\Entity\Livre",
* mappedBy="auteur",
* cascade={"persist", "remove"}
* )
*/
private $livres;

// ...
}
Avec Doctrine, les opérations en cascade sont définies sur la propriété du côté inverse
de la relation.
Désormais, le code suivant est fonctionnel :
$auteur = new Auteur();

foreach (array('Titre 1', 'Titre 2') as $titre) {


$livre = new Livre();
$livre->setTitre($titre);
$auteur->addLivre($livre);
}

$this->em->persist($auteur);
$this->em->flush();

Ici, l’intérêt n’est pas flagrant, cependant, pour les opérations de suppression, la
longueur du code se voit fortement réduite :
$auteur = $repository->find($id);

$this->em->remove($auteur);
$this->em->flush();

Au lieu de :
$auteur = $repository->find($id);

foreach ($auteur->getLivres() as $livre) {


$this->em->remove($livre);
}
$this->em->remove($auteur);
$this->em->flush();

Les valeurs acceptées par le


paramètre cascade sont persist, remove, detach et all. all est l’équivalent
de persist, remove et detach réunis.
Il existe également la valeur merge, correspondant à l’action merge de
l’EntityManager, que nous avons omis d’évoquer volontairement car cette action est
rarement nécessaire. Pour plus d’informations, reportez-vous à la documentation
officielle.
Précédent
Définition des entités et de leur mapping
Suivant
Récupérer des entités
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités

 1. Le repository
 a. Un rôle de centralisateur
 b. Les méthodes de base du repository
 c. Les méthodes personnalisées du repository
 2. Le DQL
 a. SELECT
 b. FROM
 c. JOIN et LEFT JOIN
 d. WHERE
 e. ORDER BY
 f. Les limites
 g. Les limites et la pagination
 3. Le QueryBuilder

Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Récupérer des entités


1. Le repository
a. Un rôle de centralisateur
Le repository est l’endroit où sont regroupées les requêtes relatives à une entité. La
majorité est liée à la récupération de données (SELECT), mais le repository peut
aussi tout à fait contenir des opérations de création, modification ou suppression.
Chaque entité possède son propre repository. Il peut être matérialisé par une classe
définie par le développeur, si la propriété repositoryClass de
l’annotation @Entity est renseignée, ou, le cas échéant, par une classe interne à
Doctrine.
Nous avons utilisé un repository lors d’un exemple précédent (modification d’une
entité). Nous l’utilisions pour récupérer une entité en fonction de la valeur de sa clé
primaire :
$repository = $this->em->getRepository(App:Livre');
$livre = $repository->find(1);

Décomposons le code ci-dessus en plusieurs parties distinctes.


La récupération du repository
Pour récupérer le repository d’une entité donnée, vous devez invoquer la
méthode getRepository() de l’EntityManager. En paramètre, celle-ci prend le
FQCN (Fully Qualified Class Name) de l’entité dont le repository est souhaité.
Comme vous avez pu le constater, App:Livre n’est pas un nom de classe, c’est un
raccourci spécifique à Symfony qui correspond à App\Entity\Livre. Les deux sont
tout à fait valides et peuvent être utilisés, le premier ayant l’avantage d’être plus court.
La récupération d’une entité
Dans un second temps, nous récupérons l’entité dont la clé primaire vaut 1, en
invoquant la méthode find() sur le repository.
b. Les méthodes de base du repository
La méthode find() que nous venons d’utiliser fait partie des quelques
méthodes définies par défaut dans un repository. Ces méthodes de base permettent la
récupération d’une ou plusieurs entités en fonction de la valeur d’une ou plusieurs
colonnes, et elles ont la particularité de toutes commencer par find.
findAll()
Cette méthode retourne toutes les entités persistées en base de données. Le résultat est
donc un tableau d’entités.
find()
La méthode find() récupère une entité en fonction de la valeur de sa clé primaire :
// récupère l'entité dont l'identifiant est 15
$entite = $repository->find(15);

Si votre entité a la particularité de comporter une clé primaire sur plusieurs


colonnes, vous devez utiliser la méthode find() de la manière suivante :
$entite = $repository->find(array(
'cle_primaire_1' => $valeurClePrimaire1,
'cle_primaire_2' => $valeurClePrimaire2,
));

findOneBy()
Cette méthode retourne une entité en fonction de critères de filtrage donnés. Les
critères sont passés en argument sous la forme d’un tableau :
$entite = $repository->findOneBy(array(
'nom' => 'Larry',
'pays' => 'USA',
));

Le code ci-dessus récupère l’entité dont le nom vaut Larry et le pays vaut USA.
Si vous n’avez qu’un seul critère de filtrage, vous pouvez utiliser le raccourci
suivant :
$entite = $repository->findOneByNom('Larry');

Il est l’équivalent à la ligne suivante :


$entite = $repository->findOneBy(array('nom' => 'Larry'));

findBy()
La méthode findBy() est semblable à findOneBy(), à la différence qu’elle
récupère plusieurs entités tandis que findOneBy() n’en retourne qu’une seule.
findBy() accepte également plus de paramètres que findOneBy(), à savoir un ordre
de tri et une limite :
$entites = $repository->findByNom(
'Larry',
array('age' => 'DESC'),
10,
0
);

Imaginons que le repository de l’exemple ci-dessus corresponde à une entité dont la


table est Liste. La requête SQL utilisée par Doctrine pour récupérer l’entité pourrait
être la suivante :
SELECT *
FROM Liste
WHERE nom = 'Larry'
ORDER BY age DESC
LIMIT 0, 10

c. Les méthodes personnalisées du repository


De nos jours, la complexité des requêtes exigée par les applications web ne pourrait
être comblée par les méthodes de base du repository. Les jointures ou la gestion des
différents opérateurs de comparaison ne sont possibles qu’au travers de méthodes
personnalisées.
Au sein de ces méthodes personnalisées, nous utiliserons le langage DQL, un langage
spécifique à Doctrine sur lequel nous reviendrons plus loin. Tout d’abord, affairons-
nous à la création d’un repository :
namespace App\Entity;

use Doctrine\ORM\EntityRepository;
class LivreRepository extends EntityRepository
{
public function maMethode()
{
// ...
}

// ...
}

Pour créer un repository personnalisé, il faut étendre la classe de base Doctrine\


ORM\EntityRepository. Ensuite, il doit être référencé au niveau de l’entité, au
travers du paramètre repositoryClass de l’annotation @ORM\Entity :
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* Livre
*
* @ORM\Entity(
* repositoryClass="App\Entity\LivreRepository"
* )
*/
class Livre
{
// ...
}

2. Le DQL
Les questions qui se posent maintenant sont : « Que doivent contenir les
méthodes personnalisées d’un repository ? Comment effectuer des requêtes
complexes, non supportées par les méthodes de base du repository ? »
Comme nous l’avons rapidement évoqué, il faut utiliser un langage spécifique à
Doctrine : le DQL.
Le DQL est le langage utilisé pour créer des requêtes avec Doctrine ; il remplace le
traditionnel SQL. Bien évidemment, le SQL est toujours utilisé en interne, car votre
base de données ne peut communiquer qu’avec ce langage, mais vous ne l’écrivez pas
vous-même : les requêtes DQL sont transformées en requêtes SQL par Doctrine.
L’intérêt d’écrire ses requêtes en DQL plutôt qu’en SQL est multiple.
 L’abstraction : avec le DQL, nous n’utilisons pas des noms de tables et de
colonnes, mais des noms d’entités et de propriétés. Cela permet de se détacher de
la structure concrète de la base de données. Le mapping des entités est utilisé de
pair avec le langage DQL.
 La simplicité d’utilisation : cette abstraction permet au langage DQL d’être plus
concis que le SQL. À titre d’exemple, pour effectuer une jointure, il n’y a pas
besoin de spécifier les colonnes y participant car Doctrine les connaît déjà (grâce
au mapping des entités).
L’exemple suivant est une méthode personnalisée du repository retournant toutes les
entités Livre :
namespace App\Entity;

use Doctrine\ORM\EntityRepository;

class LivreRepository extends EntityRepository


{
public function getAll()
{
return $this->getEntityManager()
->createQuery(
'SELECT l FROM App:Livre l'
)
->getResult()
;
}
}

Le résultat de cette méthode getAll() est strictement identique à celui de la méthode


de base findAll(). Elle retourne toutes les entités Livre présentes en base de données.
Avant de décomposer les différentes étapes de l’exemple ci-dessus, concentrons-nous
sur le DQL :
SELECT l FROM App:Livre l

Comme vous pouvez le constater, ce langage est semblable au SQL. Il sera donc,
malgré ses quelques différences, assez facile à prendre en main.
a. SELECT
La commande SELECT permet de spécifier les entités à récupérer. Les entités à
sélectionner sont séparées par des virgules et référencées par leur alias, qui est défini
juste après la classe de l’entité.
Pour la requête DQL précédente, l’entité App:Livre possède l’alias l.
En SQL, il est possible de sélectionner des colonnes particulières. Avec
Doctrine, l’équivalent serait de récupérer des entités dont toutes les propriétés ne sont
pas renseignées. Cela est possible, mais vivement déconseillé, car
potentiellement source de problèmes. Cependant, cette technique a du sens si elle est
alliée à l’hydratation, qui consiste à récupérer des tableaux, par exemple, et non des
entités. Nous évoquerons l’hydratation plus loin dans ce chapitre.
b. FROM
La clause FROM est semblable à celle du langage SQL, à la différence que la classe
de l’entité est utilisée à la place du nom de la table, et que l’alias est obligatoire.
Le nom de l’entité spécifiée est primordial : si App:Livre est utilisé, le résultat sera
un tableau d’entités Livre. Peu importe la requête ou les jointures que vous
effectuerez, le résultat sera toujours des entités Livre. C’est l’entité principale de la
requête.
La classe de l’entité peut utiliser le nom court ou un FQCN. Les requêtes DQL
suivantes sont donc strictement équivalentes :
SELECT l FROM App:Livre l
SELECT l FROM App\Entity\Livre l

Depuis les repository, vous avez également la possibilité d’utiliser la


méthode getEntityName() pour récupérer la classe de l’entité du repository courant :
namespace App\Entity;

use Doctrine\ORM\EntityRepository;

class LivreRepository extends EntityRepository


{
public function getAll()
{
$query = $this->getEntityManager()->createQuery(
'SELECT l FROM '.$this->getEntityName().' l'
);

return $query->getResult();
}
}
c. JOIN et LEFT JOIN
En DQL, les jointures s’effectuent avec les clauses JOIN et LEFTJOIN.
Contrairement au SQL, la clause RIGHT JOIN n’existe pas, pour une raison
intrinsèquement liée aux résultats attendus pour une requête. Comme nous venons de
l’expliquer avec la clause FROM, l’entité y étant spécifiée est l’entité principale de la
requête ; les entités en relation avec Livre sont accessibles au travers de cette entité
uniquement (Livre::getCritique(), Livre::getAuteur(), etc.) Une clause RIGHT
JOIN impliquerait le renvoi potentiel d’entités sans relation avec Livre, ce qui
n’aurait aucun sens. D’ailleurs, dorénavant, nous cesserons de systématiquement
comparer le DQL avec le SQL car il est important que vous compreniez que le DQL
est utilisé pour interroger la couche modèle de votre application, et non la base de
données directement.
Pour découvrir les jointures, créons une méthode dans un LivreRepository retournant
les Livres et leur Auteur :
namespace App\Entity;

use Doctrine\ORM\EntityRepository;

class LivreRepository extends EntityRepository


{
public function getLivresEtAuteur()
{
$query = $this->getEntityManager()->createQuery(
'SELECT l , a
FROM App:Livre l
JOIN l.auteur a'
);

return $query->getResult();
}
}

Cette méthode serait invoquée comme suit depuis un service dans lequel
l’EntityManager serait injecté :
public function ListeDesLivres()
{
$livres = $this->em
->getRepository('App:Livre')
->getLivresEtAuteur()
;

return $livres;
}

Au niveau d’une vue Twig à laquelle nous transmettrions ce tableau via un contrôleur,
nous pourrions imaginer un affichage de chaque livre et de son auteur dans une liste :

<ul>
{% for livre in livres %}
<li>
"{{ livre.titre }}"
par {{ livre.auteur.prenom }}
{{ livre.auteur.nom }}
</li>
{% endfor %}
</ul>

Le résultat est un tableau d’entités Livre. Nous itérons dessus et ensuite, pour chaque
entrée, livre.auteur permet de récupérer l’auteur d’un article (Twig invoque la
méthode getAuteur(), cf. Les Templates avec Twig - Les gabarits de pages (layout) et
les blocks). La propriété $auteur de la classe Livre contient quant à elle
l’entité Auteur en relation avec Livre, car elle est mappée de cette façon :
/**
* @ORM\ManyToOne(
* targetEntity="App\Entity\Auteur",
* inversedBy="livres"
* )
*/
private $auteur;

Les jointures sont donc importantes si vous souhaitez travailler avec les entités en
relation avec l’entité principale de la requête. Cependant, Doctrine est capable de
retrouver ces entités même si la jointure n’est pas faite explicitement lors de la
requête. Bien que cette fonctionnalité soit intéressante, elle pourrait réduire fortement
les performances d’une application si on ne sait pas la maîtriser.
Pour illustrer ce problème, reprenons notre dernière requête DQL avec une jointure :
SELECT l , a
FROM App:Livre l
JOIN l.auteur a

Si vous supprimez la jointure, pour la transformer en :


SELECT l
FROM App:Livre l

vous serez toujours capable de récupérer l’auteur de chaque livre en invoquant la


méthode getAuteur().
Cette technique s’appelle le « lazy loading » : lorsque vous invoquez la
méthode getAuteur() alors que la jointure n’a pas été faite lors de la requête, Doctrine
effectue une seconde requête pour récupérer l’auteur du livre. Ces requêtes faites
automatiquement peuvent rapidement devenir nombreuses, si vous êtes dans une
boucle, par exemple.
Le profiler Symfony vous permet de voir le nombre de requêtes SQL exécutées par la
couche DBAL de Doctrine :

Les clauses LEFT JOIN sont utilisées pour les jointures optionnelles : si l’entité en
relation n’est pas présente pour une entité, la propriété de la relation vaudra NULL,
tandis qu’avec une clause JOIN, les entités principales n’ayant pas la relation ne
seront pas retournées.
d. WHERE
La clause WHERE filtre les entités à récupérer grâce à des opérateurs de
comparaison.
Voici comment récupérer un Livre par son titre :
namespace App\Entity;

use Doctrine\ORM\EntityRepository;

class LivreRepository extends EntityRepository


{
public function getParTitre($titre)
{
$query = $this->getEntityManager()->createQuery(
'SELECT l
FROM App:Livre l
WHERE l.titre = :titre'
);
$query->setParameter('titre', $titre);

return $query->getOneOrNullResult();
}
}

Cette méthode du repository prend en paramètre le nom du titre à rechercher ; nous


utilisons ensuite un paramètre :titre dans la requête DQL. Ce dernier est placé dans la
requête à l’endroit souhaité et sa valeur est ensuite injectée avec la
méthode setParameter().
Cette technique est semblable aux requêtes préparées de PDO.
Nous invoquons finalement la méthode getOneOrNullResult() car nous souhaitons
un seul résultat et que, si aucune entité n’est trouvée, Doctrine renvoie la
valeur NULL.
Attention : si la requête retourne plus d’une entité, la
méthode getOneOrNullResult() génère une exception.
Les paramètres de requête
Les paramètres de requêtes sont nominatifs ou numérotés.
Les paramètres nominatifs commencent par « : » :
namespace App\Entity;

use Doctrine\ORM\EntityRepository;

class LivreRepository extends EntityRepository


{
public function getParTitre($titre)
{
$query = $this->getEntityManager()->createQuery(
'SELECT l
FROM App:Livre l
WHERE l.titre = :titre'
);

$query->setParameter('titre', $titre);

return $query->getOneOrNullResult();
}
}

Les paramètres numérotés commencent par un point d’interrogation et sont suivis


d’un numéro :
namespace App\Entity;

use Doctrine\ORM\EntityRepository;

class LivreRepository extends EntityRepository


{
public function getParTitre()
{

$query = $this->getEntityManager()->createQuery(
'SELECT l
FROM App:Livre l
WHERE l.titre LIKE ?1 AND l.dateParution < ?2'
);

$query->setParameters(array(
1 => '%mot-clé%',
2 => new \DateTime('2005-05-10'),
));

return $query->getResult();
}
}

Si votre requête comporte plusieurs paramètres, vous pouvez utiliser la


méthode setParameters(), qui accepte un tableau de paramètres, les clés étant les
noms ou numéros des paramètres.
Expressions
Les expressions supportées par Doctrine sont semblables à celles du
SQL : BETWEEN, IN(), EXISTS, IS NULL, etc.
$query = $this->getEntityManager()->createQuery(
'SELECT e FROM App:Entite e
WHERE e.valeur BETWEEN ?1 AND ?2'
);
$query->setParameter(1, 15);
$query->setParameter(2, 20);

$entites = $query->getResult();

récupère les entités App:Entite dont la propriété valeur vaut entre 15 et 20.
$query = $this->getEntityManager()->createQuery(
'SELECT e FROM App:Entite e
WHERE e.valeur IN (5, 6, ?1)'
);

$query->setParameter(1, 10);

$entites = $query->getResult();

récupère les entités App:Entite dont la propriété valeur vaut 5, 6 ou 10.


namespace App\Entity;

use Doctrine\ORM\EntityRepository;

class LivreRepository extends EntityRepository


{
public function getLivresSansAuteur()
{
$query = $this->getEntityManager()->createQuery(
'SELECT l
FROM App:Livre l
LEFT JOIN l.auteur a
WHERE a.id IS NULL'
);

return $query->getResult();
}
}

récupère les entités Livre n’ayant pas d’auteur.


Sous-requêtes
Les sous-requêtes sont également prises en charge. L’exemple suivant récupère les
entités Utilisateur ayant au moins une Adresse définie :
$query = $this->getEntityManager()->createQuery(
'SELECT u FROM App:Utilisateur u
WHERE EXISTS (
SELECT a.id FROM App:Adresse a
WHERE a.user = u.id
)'
);

$utilisateurs = $query->getResult();

D’un point de vue fonctionnel, la requête ci-dessus est équivalente à :


$query = $this->getEntityManager()->createQuery(
'SELECT u FROM App:Utilisateur u
JOIN u.adresses'
);

$utilisateurs = $query->getResult();

e. ORDER BY
La clause ORDER BY trie les entités retournées selon un ou plusieurs critères, son
utilisation est la même qu’en SQL :
SELECT e
FROM App:Entite e
ORDER BY e.propriete_1 DESC, e.propriete_2 ASC

ASC étant l’ordre de tri par défaut, il peut être omis.


f. Les limites
En DQL, aucune clause ne permet de définir de limites sur les résultats obtenus.
Il faut utiliser l’objet Doctrine\ORM\Query directement (c’est l’objet retourné par la
méthode createQuery() de l’EntityManager).
Ses méthodes relatives aux limites sont setFirstResult() et setMaxResults() :
class LivreRepository extends EntityRepository
{
public function getVingtLivres()
{
$query = $this->getEntityManager()->createQuery(
'SELECT l FROM App:Livre l
);

$query->setFirstResult(5);
$query->setMaxResults(20);

return $query->getResult();
}
}

La méthode Query::setMaxResults() spécifie le nombre d’entités à récupérer. La


requête peut néanmoins en renvoyer moins que ce nombre, c’est un
nombre maximum. Dans l’exemple ci-dessus, la requête retournera 20 entités Livre,
sauf si la table en base de données en contient moins.
La méthode Query::setFirstResult() indique le décalage du premier résultat. Par
défaut, il vaut 0, il est donc inutile d’invoquer cette méthode en passant 0. Dans
l’exemple ci-dessus, ce décalage est à 5, la première entité récupérée est donc la
cinquième.
g. Les limites et la pagination
Une application typique des limites intervient lors de la pagination de résultats de
requêtes. La pagination consiste à répartir une trop longue liste sur plusieurs pages
accessibles au travers de liens disposés au-dessus et/ou en dessous de celle-ci.
Pagination sur les résultats d’une recherche Google
Voici comment cette dernière peut être gérée avec les limites de l’objet Query :
namespace App\Entity;

use Doctrine\ORM\EntityRepository;

class LivreRepository extends EntityRepository


{
public function getPage($page, $nbParPage)
{
$query = $this->getEntityManager()->createQuery(
'SELECT l
FROM App:Livre l
ORDER BY l.id'
);

$query->setFirstResult(($page - 1) * $nbParPage);
$query->setMaxResults($nbParPage);

return $query->getResult();
}

//...
}

Ici, la méthode getPage() retourne les entités à afficher pour une page donnée ; le
numéro de la page doit être passé en tant que premier argument ($page), le second est
le nombre d’entités souhaitées pour chaque page ($nbParPage).
Cependant, pour les calculer, il nous manque une information cruciale : le nombre
total d’entités. En effet, Le nombre de pages disponibles correspond au nombre total
d’entités divisé par le nombre d’entités à afficher par page, et si le résultat est un
nombre décimal, il doit être arrondi à l’entier supérieur.
Il nous faut donc tout d’abord être capables de calculer le nombre total d’entités. Pour
ce faire, nous créons une méthode au sein du repository :
namespace App\Entity;

use Doctrine\ORM\EntityRepository;
class LivreRepository extends EntityRepository
{
public function getNombredeLivres()
{
$query = $this->getEntityManager()->createQuery(
'SELECT Count(l.id) FROM App:Livre l'
);

return $query->getSingleScalarResult();
}

// ...
}

Cette requête DQL compte le nombre d’entités persistées en base de données. Vous
remarquerez que la méthode invoquée pour récupérer le résultat
est getSingleScalarResult() et non getResult(). Cette méthode transforme le résultat
(on parle d’hydratation). Ici, il est hydraté en valeur unique et scalaire : un nombre
entier.
Pagination avancée
La pagination que nous venons de créer est fonctionnelle et nous a permis de mieux
comprendre le fonctionnement des limites. Cependant, vous remarquerez rapidement
que ses fonctionnalités sont assez réduites, notamment au niveau de l’affichage. En
pratique, les systèmes de pagination ont pour la plupart besoin d’intégrer des liens
« suivant » et « dernier », ou de ne pas afficher tous les numéros de page disponibles,
mais seulement ceux des pages proches.

3. Le QueryBuilder
Une difficulté fréquemment rencontrée avec l’extension PHP PDO est la gestion des
requêtes dynamiques.
Le contenu d’une requête peut parfois varier lors de l’exécution de l’application.
Généralement, ce sont les valeurs des paramètres qui changent. PDO disposant de
requêtes préparées et de marqueurs de paramètres, il est aisé d’utiliser des paramètres
de différentes valeurs sans avoir à modifier le contenu de la requête.
Mais qu’en est-il de la structure même de la requête ? Dans certains cas, vous aurez
besoin de rajouter dynamiquement une clause WHERE ou une jointure.
Prenons l’exemple d’une méthode de repository chargée de récupérer des utilisateurs.
Optionnellement, cette dernière pourrait accepter une partie de pseudonyme à
rechercher :
namespace App\Entity;
use Doctrine\ORM\EntityRepository;

class UtilisateurRepository extends EntityRepository


{
public function rechercher($pseudo = null)
{
$dql = 'SELECT u FROM App:Utilisateur u';

if ($pseudo) {
$dql .= ' WHERE u.pseudo LIKE :pseudo';
}

$query = $this->getEntityManager()->createQuery($dql);

if ($pseudo) {
$query->setParameter('pseudo', '%'.$pseudo.'%');
}

return $query->getResult();
}
}

L’exemple ci-dessus est fonctionnel, mais très peu lisible : la chaîne de caractères
correspondant au DQL doit être modifiée manuellement, en veillant à ne pas oublier
les espaces.
Pour ces situations, il est plutôt conseillé d’utiliser un QueryBuilder. Le
QueryBuilder est capable de générer des objets Query au travers d’une interface
orientée objet :
namespace App\Entity;

use Doctrine\ORM\EntityRepository;

class UtilisateurRepository extends EntityRepository


{
public function rechercher($pseudo = null)
{
$qb = $this->createQueryBuilder('u');
if ($pseudo) {
$qb
->andWhere('u.pseudo LIKE :pseudo')
->setParameter('pseudo', '%'.$pseudo.'%')
;
}

return $qb->getQuery()->getResult();
}
}

Nous remarquons que le code est beaucoup plus concis et lisible. La chaîne de
caractères de la requête n’ayant pas à être générée manuellement, les erreurs de
syntaxe sont évitées.
Pour plus d’informations sur le QueryBuilder, veuillez-vous reporter à la
documentation officielle
: https://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-
builder.html
Précédent
Manipulation des entités avec l’EntityManager
Suivant
Fonctionnalités avancées
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées

 1. Les extensions Doctrine


 a. Installation
 b. Utilisation d’un slug sur une entité

Quiz
La gestion des événements applicatifs
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Fonctionnalités avancées
1. Les extensions Doctrine
Les extensions de Doctrine sont une fonctionnalité incontournable. Très
pratiques, elles permettent d’ajouter certains comportements à vos entités, comme par
exemple :
 un slug : une propriété générée automatiquement et qui contient un identifiant
lisible pour votre entité, parfaitement adapté aux URL (nous illustrerons cette
extension par un exemple),
 les traductions : le contenu de vos entités peut être traduit en plusieurs langues,
 l’ajout de propriétés created et updated : des dates mises à jour
automatiquement lors de la création et de la dernière modification de l’entité.
Cette liste est non exhaustive, il existe bien d’autres extensions.
a. Installation
Ces extensions ne sont pas installées par défaut. Le
bundle DoctrineExtensionsBundle les met à votre disposition.
Pour l’installer, il suffit d’utiliser la recette Flex associée et donc d’exécuter la
commande :
composer require antishov/doctrine-extensions-bundle

b. Utilisation d’un slug sur une entité


Le bundle est prêt à être utilisé. Configurons l’extension sluggable (pour la
génération de slugs) sur notre entité Livre.
Le slug est une propriété spéciale qui nous permettra d’utiliser des URL adaptées au
référencement, avec du texte plutôt qu’un identifiant.
En premier lieu, il faut activer l’extension au niveau de la configuration du bundle :
# config/packages/stof_doctrine_extensions.yaml

stof_doctrine_extensions:
orm:
default:
sluggable: true

Nous avons maintenant la possibilité d’accéder à l’annotation spéciale Slug dans


notre entité :
<?php

namespace Eni\DatabaseBundle\Entity;

use Doctrine\ORM\Mapping as ORM;


use Gedmo\Mapping\Annotation as Gedmo;

/**
* Livre
*
* @ORM\Entity
*/
class Livre
{
// ...

/**
* @var string
*
* @ORM\Column(name="titre", type="string", length=250)
*/
private $titre;

// ...

/**
* @Gedmo\Slug(fields={"titre"})
* @ORM\Column(type="string", length=255, unique=true)
*/
private $slug;

Il ne reste plus qu’à mettre à jour la base de données avec la


commande doctrine:schema:update --force et l’extension sera activée sur notre
entité.
Dorénavant, pour chaque entité Livre que vous persisterez, la
propriété slug contiendra une version du titre adaptée aux URL. Par exemple, pour le
titre « La ferme des animaux », le slug sera « la-ferme-des-animaux ».
Problème courant
Un problème courant intervient s’il y a déjà des lignes en base de données pour
l’entité à laquelle vous voulez ajouter un slug.
Dans ce cas-là, la commande doctrine:schema:update --force a sûrement affiché
une erreur lors de la tentative de création de la clé unique sur la colonne slug.
La colonne slug a pourtant bien été ajoutée car l’erreur n’est survenue que plus tard,
et avec cette commande, la modification de la structure de la base de données n’est
pas transactionnelle.
Vous devez donc exécuter le code suivant pour générer les slugs de vos entités
existantes (depuis un service par exemple) :
$livres = $this->em
->getRepository('EniDemoBundle:Livre')
->findAll()
;

foreach ($livres as $livre) {


$livre->setSlug(null);
$this->em->persist($livre);
}

$this->em->flush();

Enfin, relancez la commande doctrine:schema:update --force pour créer la clé


unique.
Précédent
Récupérer des entités
Suivant
Quiz
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs

 1. Le propagation des événements


 2. L’écoute des événements

Les événements du Kernel


Les événements de la console
Quiz
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes
Concepts et écoute d’événement
applicatifs
Le répartiteur d’événements (Event Dispatcher en anglais) est l’un des composants les
plus simples du framework. Il est pourtant largement utilisé au sein de celui-ci et est
indispensable, c’est un véritable rouage de Symfony.
Le répartiteur d’événements implémente le modèle de conception Observer. Le rôle
de ce design pattern est d’autoriser diverses parties à venir observer le déroulement de
l’exécution d’un traitement, au travers de points d’ancrage. En fonction des éléments
observés sur un point d’ancrage donné, ces diverses parties pourront décider de
déclencher certaines actions, voire interagir avec le code observé, et tout cela autour
d’un système d’événements.
Savoir utiliser le répartiteur d’événements à bon escient contribue à fortement
améliorer la qualité de son code ainsi que sa maintenabilité.

1. Le propagation des événements


La description étant assez théorique pour l’instant, nous vous proposons de découvrir
ce modèle de conception au travers d’un exemple.
Imaginons un blogueur qui, à chaque nouvel article posté par l’un de ses
collaborateurs, souhaite le persister en base de données, envoyer un e-mail de
remerciement à l’auteur et poster un tweet.
Le but n’étant pas de développer l’exemple en entier, nous allons présupposer que
trois services sont déjà configurés et permettent de réaliser ces
tâches : pdo, mailer et twitter.
Voici ce à quoi pourrait ressembler le contrôleur :
namespace App\Controller;

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

/**
* @Route("/blog")
*/
class DefaultController extends AbstractController
{
/**
* @Route("/publier")
*/
public function publier(Request $request)
{
if ('POST' === $request->getMethod()) {
$article = $request->request->get('article');

// Enregistrement en base
$this->get('pdo')
->prepare('
INSERT INTO article (auteur, titre, contenu)
VALUES(:auteur, :titre, :contenu)
')->execute(array(
'auteur' => $article['auteur'],
'titre' => $article['titre'],
'contenu' => $article['contenu'],
));

// Envoi de l'e-mail
$message = \Swift_Message::newInstance()
->setSubject('Validation de ton post')
->setFrom('webmaster@example.com')
->setTo($article['auteur'])
->setBody('Merci pour le post !');

$this->get('mailer')->send($message);

// Création et envoi du tweet


$tweet = 'Nouvel article sur le blog : '
. $article['titre']
;
$this->get('twitter')->post(
'statuses/update',
array('status' => $tweet)
);
}
return $this->render('default/publier.html.twig');
}
}

Nous remarquons que le code du contrôleur est assez long ; cela ne devrait pas être le
cas, c’est ce qu’on appelle un « fat controller ». Une bonne pratique est de garder ses
contrôleurs légers, comme nous l’avons vu précédemment (cf. chapitre Routage et
contrôleur).
Nous pourrions créer un service métier permettant de gérer les articles, et y ajouter
une méthode dont le contenu serait l’exécution des trois actions (persistance en base
de données, envoi de l’e-mail, rédaction du tweet), mais nous allons plutôt réaliser
cela grâce au répartiteur d’événements :
namespace App\Controller;

use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\GenericEvent;
use Symfony\Component\Httpfoundation\Request;

class DefaultController extends AbstractController


{
/**
* @Route("/blog")
*/
public function blog(Request $request)
{
if ('POST' === $request->getMethod()) {
$article = $request->request->get('article');

$this->get('event_dispatcher')->dispatch(
'new_post', new GenericEvent($article)
);
}

return $this->render('default/blog.html.twig');
}
}

Le code de notre action est maintenant beaucoup plus léger, mais il n’est pas encore
fonctionnel. L’action ne fait que « dispatcher » un événement new_post représenté
par un objet de type GenericEvent.
L’objet GenericEvent est la représentation orientée objet de l’événement ; il contient
des informations sur l’événement qui a été déclenché (ici un article).

2. L’écoute des événements


L’action étant correctement configurée pour dispatcher l’événement une fois un
article posté, il ne reste plus qu’à configurer les « listeners » ; ceux-ci correspondent
aux classes qui écoutent l’événement new_post pour pouvoir déclencher les actions
appropriées, à savoir, dans notre cas : enregistrement en base, envoi d’un e-mail et
envoi d’un tweet.
Chaque action est contenue au sein d’un service ; voici le fichier de configuration au
format YAML permettant cela :
# Dans config/services.yaml
services:
pdo:
class: \PDO
arguments:
- 'mysql:host=localhost;dbname=test'
- 'root'
- ''

database_service:
class: App\Service\DatabaseService
arguments:
- '@pdo'
tags:
- { name: kernel.event_listener, event: new_post, method: onNewPost }

mailer_service:
class: App\Service\MailerService
arguments:
- '@mailer'
tags:
- { name: kernel.event_listener, event: new_post, priority: -5 }
twitter_service:
class: App\Service\TwitterService
arguments:
- '@twitter'
tags:
- { name: kernel.event_subscriber }

Ici, nous définissons trois


services : database_service, mailer_service et twitter_service. Ils sont tous trois
configurés de manière à réagir à l’événement new_post, et nous allons voir que leur
configuration en fait des services spéciaux : ce sont des listeners.
Un listener est caractérisé par trois paramètres :
 Le nom de l’événement qu’il écoute (ici new_post).
 La méthode du service à invoquer lorsque l’événement est dispatché.
 La priorité : si plusieurs listeners écoutent le même événement, ils seront
invoqués par ordre de priorité.
Il existe deux types de tags (cf. chapitre L’injection de dépendances) permettant de
configurer ces paramètres pour un service donné (le transformant donc
en listener) : kernel.event_listener et kernel_event_subscriber.
Quel que soit le tag utilisé, le principe reste le même, la différence étant que l’un
(kernel.event_listener) permet la définition des paramètres au niveau du tag tandis
que le second (kernel.event_subscriber) s’attend à ce que ces paramètres soient
définis au niveau du service. La configuration ci-dessus utilise les deux méthodes.
Le tag kernel.event_listener est composé de plusieurs attributs :
 event : le nom de l’événement à écouter.
 method (optionnel) : le nom de la méthode à invoquer pour l’événement donné.
Cet attribut est optionnel ; s’il n’est pas mentionné, le répartiteur d’événements
essayera d’invoquer par défaut la méthode onNomDeLevenement. La règle
utilisée est la concaténation de on et du nom de l’événement en CamelCase (en
français « casse de chameau ») ; par exemple, pour un événement event_a, si
l’attribut method n’est pas spécifié, la méthode onEventA sera invoquée.
 priority (optionnel) : il est parfois important de maîtriser l’ordre dans
lequel les listeners sont invoqués. Il est par exemple préférable de persister les
informations d’un utilisateur en base avant d’envoyer un mail de confirmation
d’inscription, et non l’inverse. Cette priorité doit être comprise entre -255 et 255,
sachant que plus la priorité est élevée pour un listener, plus il sera appelé tôt.
Le tag kernel.event_subscriber n’attend aucun attribut car les options doivent être
définies au niveau du service. Pour ce faire, la classe doit implémenter une certaine
interface :
namespace App\Service;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\GenericEvent;

class TwitterService implements EventSubscriberInterface


{
private $api;

public function __construct($api)


{
$this->api = $api;
}

public function onNewPost(GenericEvent $event)


{
$article = $event->getSubject();

// Création du tweet
$tweet = 'Nouvel article sur le blog : '
. $article['titre']
;
$this->api->post(
'statuses/update',
array('status' => $tweet)
);
}

public static function getSubscribedEvents()


{
return array(
'new_post' => array('onNewPost', -10),
);
}
}

La classe implémente EventSubscriberInterface. Cette interface requiert la


définition d’une méthode getSubscribedEvents() qui renvoie un tableau contenant
les événements auxquels ce listener est abonné.
Chaque entrée du tableau retourné correspond donc à un événement écouté. La clé est
le nom de cet événement (ici new_post), la valeur est soit le nom de la méthode à
invoquer (une chaîne de caractères), soit, comme c’est le cas ici, un tableau avec deux
arguments : le premier est le nom de la méthode à invoquer et le second, la priorité.
Les tags kernel.event_listener et kernel.event_subscriber sont deux
manières différentes d’effectuer la même tâche, les options étant exactement les
mêmes. Cependant, par souci de cohérence, il est préférable de faire un choix et de se
cantonner à l’utilisation d’un seul tag dans son code.
Un listener est souvent plus rapide à configurer qu’un subscriber. Par contre,
le subscriber peut être considéré comme plus robuste par certains puristes, car il
permet l’utilisation de constantes de classes pour identifier les événements en lieu et
place des chaînes de caractères, parfois qualifiées de « magic values » (en français, «
valeurs magiques »).
Précédent
Quiz
Suivant
Les événements du Kernel
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel

 1. Les différents type d’événements


 2. Applications

Les événements de la console


Quiz
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Les événements du Kernel


1. Les différents type d’événements
Le Kernel (noyau) de Symfony propage plusieurs événements tout au long du
traitement d’une requête :
 kernel.request : cet événement est dispatché au tout début du traitement de la
requête. Il peut être utilisé pour retourner une réponse directement sans passer par
la phase de routage, par le contrôleur, etc.
 kernel.controller : cet événement a lieu juste avant l’exécution du contrôleur.
 kernel.controller_arguments : cet événement a également lieu avant l’exécution
du contrôleur. Grâce à lui, vous pouvez modifier les arguments passés au
contrôleur.
 kernel.view : l’événement kernel.view est dispatché si le contrôleur ne
retourne pas une instance de réponse. Exemple d’utilisation : Symfony utilise cet
événement en interne, lors de l’utilisation de l’annotation @Template().
 kernel.response : cet événement permet de modifier l’objet réponse à envoyer au
client. Exemple d’utilisation : en environnement de développement, Symfony
utilise cet événement pour ajouter la barre de débogage en bas de page.
 kernel.finish_request : lorsque des sous-requêtes modifient l’état de votre
application, cet événement peut être utilisé pour réinitialiser certaines valeurs. En
pratique, c’est un événement qui a plutôt vocation à être utilisé en interne par le
framework que par les utilisateurs finaux.
 kernel.terminate : l’événement kernel.terminate est dispatché après le renvoi
de la réponse. Cet événement est assez intéressant au niveau des gains en
performances qu’il peut apporter, comme nous allons le voir plus loin.
 kernel.exception : cet événement est déclenché si l’application lance une
exception.
Une bonne manière d’en apprendre plus sur ces événements est de parcourir la classe
Symfony\Component\HttpKernel\HttpKernel. Cette classe est le « cœur » de
Symfony. Elle est invoquée directement depuis votre contrôleur frontal au travers de
votre Kernel.

2. Applications
Grâce à l’événement kernel.terminate, vous pouvez par exemple retarder l’exécution
de tâches lourdes après le renvoi de la réponse, et non pendant, de manière à ne pas
pénaliser les performances de votre site. Dans ce cas-là, les tâches lourdes ne peuvent
pas avoir d’impact sur le contenu de la réponse HTTP renvoyée au client, car, au
moment où elles sont exécutées, la réponse est déjà envoyée.
Par défaut, Symfony (plus précisément SwiftMailerBundle) utilise cet événement
pour, entre autres, envoyer des e-mails. Lorsque vous en envoyez un depuis un
contrôleur, Symfony garde le message dans une file d’attente, pour effectuer l’envoi
concret seulement lors de l’événement kernel.terminate. Reportez-vous à l’annexe
Envoyer des e-mails grâce à SwiftMailer pour plus d’informations.
Voici un contrôleur dont l’exécution serait assez longue :
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class DefaultController extends AbstractController
{
/**
* @Route("/")
*/
public function tacheLourde()
{
sleep(5); // tâche lourde

return new Response('Hello world!');


}
}

Ici, nous utilisons la fonction PHP sleep() pour simuler une action lourde. Lorsque
cette action sera invoquée, l’utilisateur attendra au minimum cinq secondes devant
son navigateur, ce qui pourrait être évité en utilisant une tâche asynchrone.
Pour cela, il faut tout d’abord extraire le code lourd au sein d’un listener :
namespace App\Listener;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;

class TacheLourdeListener implements EventSubscriberInterface


{
public function executer(PostResponseEvent $event)
{
sleep(5);
}

public static function getSubscribedEvents(


PostResponseEvent $event
) {
return array(
KernelEvents::TERMINATE => 'executer',
);
}
}

La classe a la particularité d’être un listener (EventSubscriberInterface). Elle peut


donc facilement être transformée en service qui sera invoqué lors de
l’événement kernel.terminate :
services:
my_event_subscriber:
class: App\Listener\TacheLourdeListener
tags:
- { name: kernel.event_subscriber }

Dorénavant, la réponse sera envoyée très rapidement à l’utilisateur, tandis que la tâche
lourde sera effectuée après coup.
Précédent
Concepts et écoute d’événement applicatifs
Suivant
Les événements de la console
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console

 1. Prérequis
 2. Les événements

Quiz
Les formulaires
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Les événements de la console


1. Prérequis
Une bonne compréhension de la console Symfony est nécessaire pour appréhender ce
chapitre. N’hésitez pas à parcourir les chapitres adéquats (Architecture du framework
- La console et Annexes - Créer une commande pour la console) avant de continuer.
2. Les événements
La console est capable de déclencher plusieurs événements, au nombre de trois.
Le premier, console.command, se produit avant l’exécution d’une commande. Si une
exception est lancée lors du déroulement de la commande,
l’événement console.exception intervient. L’événement console.terminate est finale
ment déclenché une fois la commande terminée.
Voici comment les différents événements de la console peuvent être intégrés au sein
d’un listener :
namespace App\Listener;

use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleExceptionEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;

class ConsoleListener implements EventSubscriberInterface


{
public function command(ConsoleCommandEvent $event)
{
$input = $event->getInput(); // objet input
$output = $event->getOutput(); // objet output

$commande = $event->getCommand();

$output->writeln(sprintf(
'La commande "%s" va être exécutée.',
$commande->getName()
));
}

public function exception(ConsoleExceptionEvent $event)


{
$exception = $event->getException();

$event->getOutput()->writeln(sprintf(
'Une exception a été lancée : "%s".',
$exception->getMessage()
));
}

public function terminate(ConsoleTerminateEvent $event)


{
$event->getOutput()->writeln('Commande terminée.');
}

public static function getSubscribedEvents()


{
return array(
ConsoleEvents::COMMAND => 'command',
ConsoleEvents::EXCEPTION => 'exception',
ConsoleEvents::TERMINATE => 'terminate',
);
}
}

Le listener peut être enregistré de la même manière que pour les événements du
Kernel :
# Fichier config/services.yaml

services:
my_event_subscriber:
class: Eni\DemoBundle\Listener\ConsoleListener
tags:
- { name: kernel.event_subscriber }

Dorénavant, pour toute commande que vous exécuterez, un message s’affichera lors
de chaque événement de la Console.
Précédent
Les événements du Kernel
Suivant
Quiz
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI
Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC

 1. Le modèle
 2. Le contrôleur
 3. La vue

Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Un composant MVC
Les formulaires sont des éléments indispensables aux sites web : c’est le principal
moyen par lequel les utilisateurs interagissent avec l’application.
Ils sont affichés dans des pages (couche Vue) et, une fois soumis, ils sont
généralement utilisés pour modifier des données (couche Modèle), tout ceci étant
orchestré par le contrôleur.
On retrouve donc les protagonistes de notre fameux modèle de conception MVC (cf.
Architecture du framework - Le modèle de conception MVC).
Cette particularité fait du composant « Form » de Symfony non pas le plus difficile à
prendre en main, mais l’un des plus complets. Aussi, pour mieux appréhender ce
chapitre, il convient de maîtriser le Contrôleur et Twig.

1. Le modèle
Dans leur utilisation la plus fréquente, les formulaires permettent d’interagir avec la
couche Modèle (bien que le composant puisse fonctionner avec des tableaux). Les
« cibles » des formulaires sont donc des objets.
Imaginons une classe représentant un client :
namespace App\Model;
class Client
{
private $nom;
private $dateDeNaissance;

public function setNom($nom)


{
$this->nom = $nom;

return $this;
}

public function getNom()


{
return $this->nom;
}

public function setDateDeNaissance($dateDeNaissance)


{
$this->dateDeNaissance = $dateDeNaissance;

return $this;
}

public function getDateDeNaissance()


{
return $this->dateDeNaissance;
}
}

C’est une classe PHP classique, mais elle pourrait très bien correspondre à une entité
Doctrine si jamais son mappage était défini.
Un des buts du composant « Form » sera de créer ou de modifier des instances de
cette classe, en fonction de données transmises par l’utilisateur, via un formulaire.

2. Le contrôleur
Le contrôleur, en tant que point de relais, est chargé de :
 créer ou récupérer l’objet de la couche Modèle,
 créer le formulaire et lui faire connaître cet objet de la couche Modèle, dont il
permettra la modification,
 passer le formulaire à la vue pour que celle-ci s’occupe de l’affichage des
différents champs,
 valider, une fois le formulaire soumis par le client, les données, de manière à
pouvoir afficher des messages d’erreur si besoin est (par exemple « Ce champ est
obligatoire », « Ce n’est pas une adresse e-mail valide », etc.).
Voici le code au sein d’une action (il n’est pas nécessaire que vous le compreniez en
détail pour le moment, nous reviendrons bien évidemment sur le sujet plus loin dans
ce chapitre) :
/**
* @Route("/")
*/
public function index(Request $request)
{
$client = new Client();

$form = $this->createFormBuilder($client)
->add('nom', TextType::class)
->add('date_de_naissance', BirthdayType::class)
->add('valider', SubmitType::class)
->getForm()
;

$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {


return new Response('Le formulaire est valide.');
}

return $this->render('default/index.html.twig',
[ 'form' => $form->createView() ]
);
}

3. La vue
À la fin de notre action, nous envoyons notre formulaire à la vue, au sein d’une
variable form. Le template a pour objectif de mettre en forme ce formulaire, en
affichant ses différents champs.
On notera que l’objet formulaire n’est pas envoyé tel quel, mais qu’il faut invoquer la
méthode $form->createView(), cette méthode transformant le formulaire en un objet
spécialement adapté aux templates.
Au niveau du template, l’affichage du formulaire est d’une simplicité déconcertante :
<html>
<body>
{{ form(form) }}
</body>
</html>

Il suffit d’invoquer la fonction Twig form(), avec en argument l’objet représentant le


formulaire dans les vues.

Le formulaire complet est ainsi généré. Le rendu sera évidemment personnalisable,


nous nous y attarderons plus loin dans ce chapitre.
Précédent
Quiz
Suivant
Fonctionnement du composant
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments
Tout déplier | Tout replier
Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant

 1. L’objet « Form »
 a. Soumission
 b. Validation
 c. Vue
 2. Les types
 3. Les options
 4. Les objets « Form » et « FormBuilder »
 a. Le FormBuilder
 b. Structure de l’objet Form
 5. Association avec l’objet de la couche Modèle
 6. Formulaires sans objet
 7. La représentation des valeurs
 a. Transformation des données
 b. Illustration avec le type date

Les types de champs de formulaire


Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Fonctionnement du composant
1. L’objet « Form »
L’objet Form est le principal élément utilisé au niveau du contrôleur. Dans l’exemple
ci-dessus, il est contenu dans la variable $form. Il représente l’ensemble des champs
du formulaire, sous forme hiérarchique.
C’est le point central autour duquel tout s’articule. L’objet représentant la
requête HTTP ou l’objet de la couche Modèle y est « injecté ». Les tâches de
traitement classiques (telles que la gestion de la soumission, la validation ou la
création de la vue du formulaire) se font également au travers de cet objet Form.
a. Soumission
L’objet Form comporte une méthode handleRequest(), prenant en paramètre
l’objet Request (la requête HTTP courante). Cette méthode est capable de
l’introspecter.
De cette introspection naîtra une conclusion, à savoir, une réponse à la
question suivante : est-ce que l’utilisateur, lors de la requête HTTP courante, est en
train de soumettre le formulaire, ou est-il seulement en train de l’afficher ?
S’il est en train de le soumettre (généralement cette action est reconnaissable par le
fait que la méthode de la requête HTTP courante est de type POST), les données
postées sont rattachées au formulaire et l’objet de la couche Modèle est également mis
à jour (dans la mesure où les données saisies par l’utilisateur sont valides, nous y
reviendrons plus tard).
Par défaut, invoquer la méthode handleRequest() serait équivalent à :
if ($request->isMethod('POST')) {
$form->submit($request);
}

La méthode submit() permet de rattacher manuellement des données à un formulaire.


b. Validation
La méthode isValid() permet de savoir si les valeurs soumises par l’utilisateur sont
correctes, en fonction d’un ensemble de règles (dites « contraintes ») définies par le
développeur.
Ces règles peuvent être définies à la volée, c’est-à-dire durant la création du
formulaire, ou directement sur l’objet de la couche Modèle.
La validation est évoquée en détail plus loin dans ce chapitre.
c. Vue
Enfin, un objet spécial, une « vue » du formulaire, peut être extrait de
l’objet Form grâce à sa méthode createView(). Ici, il ne serait pas intéressant
d’entrer dans le détail, sachez juste que les templates ont besoin de cet objet spécial et
non de l’objet principal Form pour travailler.

2. Les types
Les classes telles TextType ou SubmitType que nous avons utilisées précédemment
correspondent aux types des champs de notre formulaire. Nous les référençons avec
leur FQCN grâce au mot-clé ::class (disponible depuis PHP 5.5).

3. Les options
Lors de la configuration des différents champs du formulaire, un troisième
argument de la méthode add() peut être utilisé pour passer des options, au travers
d’un tableau.
Ces options sont utilisées pour personnaliser le champ : changer son label, sa valeur
par défaut ou son rendu sont autant de possibilités offertes par les options.
Illustrons ceci avec un exemple concret. Imaginons que le
champ date_de_naissance soit restreint aux jeunes de 7 à 77 ans :
$form = $this->createFormBuilder($client)
->add('nom', TextType::class)
->add('date_de_naissance', BirthdayType::class, array(
'years' => range(date('Y') - 77, date('Y') - 7)
))
->add('valider', SubmitType::class)
->getForm()
;

Nous utilisons pour ceci l’option years, qui accepte un tableau de valeurs
correspondant aux années disponibles pour le champ.
Il y a des options communes à tous les types, tandis que d’autres sont spécifiques à un
type de champ donné. Ici, par exemple, l’option years est spécifique aux champs
relatifs à la gestion des dates (elle n’aurait pas de sens pour un champ de
type textarea). La liste complète des options disponibles pour chaque type est
détaillée plus loin.

4. Les objets « Form » et « FormBuilder »


a. Le FormBuilder
Le « FormBuilder » est au « Form » ce que, sous Doctrine, le « QueryBuilder » est à
la « Query ». Son rôle est de configurer et de créer des objets Form.
Nous l’avons précédemment utilisé de la manière suivante :
$form = $this->createFormBuilder($client)
->add('nom', TextType::class)
->add('date_de_naissance', BirthdayType::class)
->add('valider', SubmitType::class)
->getForm()
;

Ici, nous utilisons la méthode héritée createFormBuilder(), celle-ci permet, comme


son nom l’indique, de créer une instance de FormBuilder.
Nous passons en paramètre de cette méthode l’objet de la couche Modèle que nous
voulons associer au formulaire.
Ensuite, nous invoquons à plusieurs reprises la méthode add(). Cette dernière ajoute
un « enfant » (ou « child » en anglais) au formulaire en cours de construction. Chaque
enfant a un nom (nom, date_de_naissance et valider) et un type
(TextType, BirthdayType et SubmitType).
Au final, l’objet Form est récupéré via la méthode getForm().
b. Structure de l’objet Form
L’objet Form est sous forme hiérarchique : il contient un élément principal (Client),
auquel nous avons ajouté des enfants (nom, date_de_naissance et valider)
manuellement, tandis que d’autres enfants ont été ajoutés automatiquement
(Jour, Mois et Année).
Voici un aperçu graphique de la représentation que se fait Symfony de notre
formulaire :
Chaque « nœud » est un formulaire (une sorte de sous-formulaire, mais en
interne, c’est la même classe Form qui est utilisée par le framework). Il a, comme
nous l’avons vu, un nom et un type. Une fois le formulaire soumis, la plupart des
nœuds auront également une valeur.
Il peut être composé (compound) ; les nœuds composés (grisés sur le schéma) ne
donnent généralement pas lieu à l’affichage d’un élément, mais agissent en tant que
« conteneurs ».
La méthode get() permet de récupérer les enfants d’un formulaire :
$enfant = $form->get('date_de_naissance');

// récupère le sous-formulaire 'date_de_naissance'

En répétant cette technique sur l’objet Form enfant, nous pouvons accéder à
n’importe quel nœud de l’arborescence :
$noeudJour = $form->get('date_de_naissance')->get('day');

5. Association avec l’objet de la couche Modèle


Les différentes valeurs saisies via les champs d’un formulaire ont pour la
plupart vocation à être injectées dans l’objet de la couche Modèle.
La manière dont le framework associe les données contenues dans la requête HTTP
de soumission du formulaire aux différentes propriétés de l’objet de la couche Modèle
s’appelle le « mapping ».
Pour cela, le framework utilise généralement les setters de cet objet. Voici un schéma
récapitulatif des setters invoqués pour le formulaire de la section précédente :
Sur ce schéma, la méthode de l’objet Client invoquée pour chaque « nœud » est
précisée après le nom de celui-ci. L’argument passé à cette méthode est la valeur
soumise par l’utilisateur pour le champ en question.
Des trois champs que nous avons configurés
(nom, date_de_naissance et valider), valider ne donnera lieu à aucune injection :
c’est un champ de type submit, donc son rôle est uniquement de valider l’envoi du
formulaire.
Le formulaire principal (Client) est le conteneur de nos différents champs, c’est le
nœud principal. D’un point de vue Modèle, il correspond à l’objet Client. La valeur
de ce nœud n’est donc pas soumise par l’utilisateur directement.
Enfin, les champs Jour, Mois et Année ont quant à eux bel et bien des valeurs, mais
ce sont des valeurs « partielles ». Elles ne sont donc pas injectées directement dans
l’objet de la couche Modèle, mais reconstituées au sein du nœud date_de_naissance,
dont la valeur sera injectée via la méthode setDateDeNaissance() de l’objet, au
travers d’un objet de type DateTime.
Tout ce processus est effectué automatiquement par le framework. Vous n’avez rien
à configurer pour cela. Il est tout de même important de le connaître car il sera
possible de modifier la manière dont les données sont injectées ou transformées. Nous
aborderons ces techniques plus loin dans le chapitre.

6. Formulaires sans objet


Un formulaire Symfony n’a pas obligatoirement besoin d’un objet de la couche
Modèle pour fonctionner. Si aucun objet ne lui est associé, il renvoie un tableau,
accessible via la méthode getData() de l’objet Form.
use Symfony\Component\HttpFoundation\Request;

public function indexAction(Request $request)


{
$form = $this->createFormBuilder() // aucun paramètre
->add('mon_texte', TextType::class)
->add('ma_date', DateType::class)
->add('valider', SubmitType::class)
->getForm()
;

$form->handleRequest($request);

if ($form->isSubmitted()) {
$donnees = $form->getData();
// Traitement du tableau contenant les données soumises...
}

return array('form' => $form->createView());


}

7. La représentation des valeurs


a. Transformation des données
En plus de savoir injecter les données des requêtes HTTP de soumission au sein de
l’objet associé au formulaire, le framework est capable de transformer ces données.
En effet, nous avons par exemple vu qu’avec un champ de type birthday, une fois le
formulaire soumis, le framework injecte un objet de type DateTime dans la propriété
de l’objet de la couche Modèle. L’objet DateTime étant spécifique à PHP, et
n’existant d’aucune manière au niveau du protocole HTTP, il y a forcément au
minimum une étape intermédiaire entre la récupération des données soumises et
l’injection de celles-ci. Cette étape est la transformation de la représentation de la
valeur.
Il existe trois types de représentation des données :
 View data (données de la vue) : c’est le format de données utilisé en entrée et
sortie de l’application. En entrée, cela se fait au travers de requêtes HTTP,
souvent dans le corps de celle-ci, dans des paramètres POST. En sortie, ces
données sont utilisées dans les templates, pour préremplir les champs par
exemple.
 Model data (données du modèle) : c’est la représentation des données du champ
pour la couche Modèle. Concrètement, cela correspond à la valeur de la propriété
de l’objet de la couche Modèle associée au champ en question.
 Norm data (données normalisées) : ce format de données est une sorte de
« format tampon » entre View data et Model data. En pratique, les
formats Model et Norm sont souvent similaires.
b. Illustration avec le type date
Le champ de type date est un exemple intéressant pour illustrer les différentes
représentations des données. En effet, il dispose de plusieurs options permettant de
contrôler ces représentations : il est possible de récupérer en Model data un objet de
type DateTime, un timestamp, un tableau ou tout simplement une chaîne de
caractères.
Pour gérer cela, nous utilisons l’option input. Elle peut prendre les valeurs
suivantes : string, datetime, array, timestamp.
use Symfony\Component\HttpFoundation\Request;

public function indexAction(Request $request)


{
$form = $this->createFormBuilder()
->add('ma_date', DateType::class, array(
'input' => 'timestamp'
))
->add('valider', SubmitType::class)
->getForm()
;

$form->handleRequest($request);

if ($form->isSubmitted()) {
$donnees = $form->get('ma_date')->getData();

// équivalent à (>= PHP 5.4) :


// $donnees = $form->getData()['ma_date'];
}

return array('form' => $form->createView());


}

L’instruction $form->get(’ma_date’) récupère le sous-formulaire ma_date. Ensuite,


la méthode getData() renvoie les données de la représentation Model data. Ici, c’est
un timestamp (entier numérique) qui est retourné, car nous l’avons spécifié
explicitement au travers de l’option input.
La deuxième instruction $form->getData()[’ma_date’] est équivalente car la
représentation Model data du formulaire principal est retournée via getData().
Comme aucun objet n’est associé au formulaire, un tableau est retourné et la
clé ma_date contient la valeur de l’enfant du même nom.
Récupérer les représentations View et Norm
Les méthodes getViewData() et getNormData() de l’objet Form renvoient
respectivement les View data et Norm data du formulaire.
Précédent
Un composant MVC
Suivant
Les types de champs de formulaire
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants
 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire

 1. L’héritage
 2. FormType
 a. label
 b. label attr
 c. data
 d. required
 e. disabled
 f. mapped
 g. property_path
 h. attr
 i. trim
 j. error_bubbling
 3. TextType
 4. PasswordType
 5. RepeatedType
 a. type
 b. first_options et second_options
 c. options
 d. first_name
 e. second_name
 f. invalid_message
 6. ChoiceType
 a. choices
 b. expanded et multiple
 c. placeholder
 d. preferred_choices
 e. Types similaires
 7. EntityType
 a. class
 b. choice_label
 c. query_builder
 d. group_by
 e. em
 8. DateType
 a. widget
 b. format
 c. model_timezone
 d. view_timezone
 e. years
 f. months
 g. days
 h. placeholder
 i. Types similaires
 9. FileType
 a. multiple
 b. Récupérer les fichiers
 c. Traiter les fichiers
 10. CheckboxType
 11. SubmitType, ResetType et ButtonType

Créer des formulaires réutilisables


Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Les types de champs de formulaire


L’objectif de cette section du chapitre est de vous donner un aperçu des différents
types de champs de formulaire et de leurs options. Cela n’est en aucun cas une liste
exhaustive mais plutôt un référentiel de base pour la compréhension du
fonctionnement de ces champs.
Afin de trouver la liste complète des types de champs de formulaire ainsi que leurs
options, rendez-vous sur la page de documentation dédiées au composant « Form » à
l’adresse : https://symfony.com/doc/4.4/reference/forms/types.html

1. L’héritage
Avant d’aborder les différents types ainsi que leurs options, il est nécessaire de
comprendre l’héritage de ceux-ci.
Chaque type peut hériter d’un parent. En pratique, la quasi-totalité des types utilisés
avec Symfony héritent du même type : FormType. Nous avons déjà utilisé ce type
indirectement : le « nœud » principal de la hiérarchie du formulaire est par défaut de
type FormType.
Au cours de cette section dédiée aux types, le parent de chacun de ceux-ci sera
spécifié. Cela vous permettra de retrouver la totalité de leurs options. Si un type B a
comme parent A, toutes les options de A sont alors disponibles lors de l’utilisation
de B.

2. FormType
C’est un type plutôt abstrait, contenant des options de configuration assez générales.
La plupart du temps, nous ne l’utilisons pas directement, mais au travers de ses
nombreux sous-types.
a. label
Cette option indique le label à utiliser pour le champ. Si cette option n’est pas
renseignée, le framework fait de son mieux pour « deviner » le label ; par exemple, un
champ nommé date_de_naissance aura le label Date de naissance.
b. label attr
En passant un tableau associatif à cette option, vous pouvez définir les attributs
HTML pour la balise <label>.
c. data
Cette option permet de modifier la valeur par défaut du champ.
Par exemple, pour préremplir un champ de type text (il hérite de form) avec une
valeur donnée, il suffit de configurer le type de cette manière :
$formBuilder->add('champ', TextType::class, array(
'data' => 'Valeur par défaut'
));

d. required
Si cette option vaut true, l’attribut required est ajouté au champ.
Cet attribut HTML5 spécifie que le champ en question doit obligatoirement être
renseigné et tant qu’il ne l’est pas, l’utilisateur ne peut soumettre le formulaire et voit
un message d’erreur. Ce comportement est géré nativement par le navigateur.
La validation HTML5 est un « complément » à la validation côté serveur,
évoquée plus loin dans ce chapitre. Elle ne doit pas être utilisée en tant que solution
de remplacement d’une validation serveur.
e. disabled
Lorsque cette option est à true, le framework ajoute un attribut disabled au champ.
Symfony ignore alors complètement le champ, c’est comme s’il n’existait pas : il
n’est pas validé mais uniquement affiché dans la vue.
f. mapped
Cette option vaut true par défaut. Si elle vaut false, le framework n’associera pas le
champ à l’objet de la couche Modèle.
Cette option est particulièrement utile, par exemple pour les champs « CAPTCHA »
ou la case à cocher pour accepter des conditions générales, qui contiennent des
informations qui ne sont pas destinées à être persistées de manière durable dans la
couche Modèle.
g. property_path
Cette option définit la manière dont les données du champ sont lues/injectées dans
l’objet de la couche Modèle.
Par défaut, cette option prend la valeur du nom du champ. Si cette option (ou le nom
du champ, donc) vaut date_de_naissance, par exemple, le framework s’attend à
interagir avec l’objet de la couche Modèle avec l’une de ces techniques (par ordre de
priorité) :
 Au travers d’une propriété publique date_de_naissance.
 Via les méthodes
publiques getDateDeNaissance(), isDateDeNaissance() ou hasDateDeNaissanc
e() pour la lecture, et pour l’écriture via la méthode setDateDeNaissance().
 Enfin, les méthodes magiques PHP __call(), __get()et __set().
h. attr
Cette option est utilisée pour ajouter des attributs HTML au champ. Elle se présente
sous forme de tableau dont les clés correspondent aux noms des attributs et les valeurs
aux valeurs des attributs.
Une classe (au sens HTML) peut donc facilement être ajoutée à un champ :
$formBuilder->add('champ', TextType::class, array(
'attr' => array('class' => 'ma_classe')
));

i. trim
La fonction trim() de PHP permet d’effacer les espaces en début et fin de chaîne de
caractères. Quand cette option vaut true (ce qui est le cas par défaut), cette fonction
est appliquée aux données provenant du champ en question.
j. error_bubbling
Cette option est liée à la validation des données, évoquée plus loin dans ce chapitre.
Définie à true, cette option a pour effet de rattacher les éventuelles erreurs du nœud à
son parent. Elle est généralement utilisée pour afficher les erreurs en haut du
formulaire et non près du champ comportant l’erreur.

3. TextType
Ce type hérite de FormType. Il est utilisé pour représenter un input de type text.
L’exemple ci-dessous crée un champ text acceptant uniquement des
caractères alphanumériques.
$formBuilder->add('champ', TextType::class, array(
'pattern' => '[a-zA-Z0-9]*'
));

Il existe d’autres types semblables à celui-


ci : hidden, textarea, email, integer, money, number, percent, search et url. Leur
nom est assez explicite et les différences minimes. Pour plus d’informations quant à
ceux-ci, n’hésitez pas à vous référer à la documentation officielle.

4. PasswordType
Le type password hérite de FormType. Il permet d’afficher des champs relatifs à la
saisie de mots de passe.
La particularité de ce type est qu’il cache la valeur entrée avec des puces noires.
Son autre spécificité est de ne jamais préremplir le champ avec la valeur soumise par
l’utilisateur. Dans le cas d’un formulaire d’inscription, par exemple, si l’utilisateur
soumet le formulaire avec un champ invalide, la page est réaffichée avec un message
d’erreur près du champ en question, mais les autres champs auront gardé les valeurs
soumises, à l’exception des champs de type password.
Ce comportement peut être modifié grâce à l’option always_empty, en la mettant
à false.

5. RepeatedType
Ce type duplique un champ et attend de l’utilisateur qu’il saisisse la même valeur dans
les deux.
Le principal cas d’utilisation est pour les mots de passe ou adresses e-mail : lors de
l’inscription d’un utilisateur, il lui est souvent demandé de confirmer la valeur saisie,
de manière à éviter les erreurs dues aux fautes de frappe.

a. type
Cette option configure le type des deux champs. L’exemple suivant affiche deux
champs de type password :
$builder->add('mot_de_passe', RepeatedType::class, array(
'type' => PasswordType::class,
));

b. first_options et second_options
Ces options permettent de passer respectivement les options au premier et
second champ. Grâce à celles-ci, nous pouvons configurer le label de chaque champ :
$builder->add('mot_de_passe', RepeatedType::class, array(
'type' => PasswordType::class,
'first_options' => array('label' => 'Mot de passe'),
'second_options' => array('label' => 'Confirmer'),
));

c. options
S’il existe des options communes aux deux champs, il convient de les passer par
l’option options, plutôt que de les dupliquer.
d. first_name
Cette option permet de définir le nom du premier champ.
e. second_name
Cette option permet de définir le nom du second champ.
f. invalid_message
Lorsque les valeurs des deux champs sont différentes, le message d’erreur affiché
peut être personnalisé grâce à cette option.

6. ChoiceType
Le type choice hérite de FormType. Il est utilisé pour générer des
champs select, radio ou checkbox.
Un champ choice contient donc une liste de données. L’utilisateur peut en choisir une,
ou éventuellement plusieurs.
a. choices
Cette option doit contenir les valeurs présentes dans la liste : les choix.
Ces choix sont sous la forme d’un tableau associatif. La clé correspond à la
valeur envoyée dans la requête HTTP, tandis que la valeur correspond au label affiché
à l’utilisateur.
$formBuilder->add('categorie_produit', ChoiceType::class, array(
'choices' => array(
'tv_em' => 'Télévisions et électroménager',
'jv' => 'Jeux vidéo',
'livres' => 'Livres et BD',
)
));

L’exemple ci-dessus affiche un champ de type select, dont les options ont pour
valeurs tv_em, jvet livres, et comme labels, respectivement, Télévisions et
électroménager, Jeux vidéo et Livres et BD.
b. expanded et multiple
Le type de champ affiché dépend des options expanded et multiple :
 multiple : cette option permet de définir si l’utilisateur peut sélectionner plusieurs
valeurs. Mise à true, cette option générera soit un select multiple, soit une liste de
checkboxes (en fonction de l’option expanded).
 expanded : si cette option vaut false (valeur par défaut), le champ généré est de
type select, selon la valeur de l’option multiple, c’est soit une liste de cases à
cocher, soit une liste de boutons radio.
Voici un tableau récapitulatif :
c.
multiple expanded champ

false false select

true false select multiple

false true boutons radio

true true checkboxes

placeholder
Cette option spécifie si, dans le cas d’un champ select, une option spéciale doit
apparaître en début de liste, avec un label de type « Choisissez une valeur », par
exemple.
$formBuilder->add('categorie_produit', ChoiceType::class, array(
'choices' => array(
'tv_em' => 'Télévisions et électroménager',
'jv' => 'Jeux vidéo',
'livres' => 'Livres et BD',
),
'empty_value' => 'Choisir une catégorie',
));

d. preferred_choices
Toujours dans le cas d’un champ select, cette option, contenant un tableau de choix
prioritaires, affiche ceux-ci en début de liste, les rendant plus visibles pour
l’utilisateur.
Un cas d’utilisation courant pourrait être les listes de pays. Il est ainsi possible de
placer le pays retrouvé grâce à l’adresse IP de l’utilisateur en début de liste, pour
faciliter le remplissage du formulaire.
$formBuilder->add('categorie_produit', ChoiceType::class, array(
'choices' => array(
'US' => 'Etats-Unis',
'FR' => 'France',
'IN' => 'Inde',
'VE' => 'Venezuela',
// ...
),
'preferred_choices' =>
array(geoip_country_code_by_name($userIp)),
));

e. Types similaires
Quelques types définis au sein du framework héritent
de choice : country, language, locale, timezone et currency.
La seule différence avec le type choice est que ces derniers renseignent
l’option choices, avec, comme leur nom l’indique, une liste de pays, ou de langues,
etc.

7. EntityType
entity hérite de choice, c’est un type spécifique à Doctrine.
Contrairement à choice, où c’est une valeur scalaire (ou un tableau de valeurs
scalaires) qui est récupérée, le type entity récupère des entités.
a. class
Cette option permet de spécifier la classe de l’entité de la liste. Les raccourcis de
classes sont autorisés (par exemple, App:MonEntite).
b. choice_label
Cette option indique le property_path (cf. options du type form) qui doit être affiché
en tant que label pour chaque entrée de la liste.
use Symfony\Bridge\Doctrine\Form\Type\EntityType;

$formBuilder->add('utilisateur', EntityType::class, array(


'class' => 'MonBundle:Utilisateur',
'property' => 'pseudo',
));

Si cette option n’est pas renseignée, le framework pourra « deviner » le label à utiliser
automatiquement, à condition que la méthode magique __toString() soit définie au
sein de la classe.
c. query_builder
Cette option prend comme argument une fonction anonyme, qui aura à sa disposition
le repository de l’entité quand elle sera invoquée par le framework.
Son rôle est de retourner la liste des entités disponibles pour remplir la liste
déroulante. Par défaut, toutes les entités existantes en base de données sont présentes
dans la liste déroulante.
use Doctrine\ORM\EntityRepository;

$formBuilder->add('utilisateur', EntityType::class, array(


'class' => 'MonBundle:Utilisateur',
'query_builder' => function(EntityRepository $repository) {
return $repository->createQueryBuilder('u')
->where('u.actif = :actif')
->setParameter('actif', true)
->orderBy('u.pseudo', 'ASC')
;
},
));

d. group_by
Dans le cas d’un champ select, cette option permet de regrouper les choix par
sections. Des balises HTML optgroup seront alors générées.
À l’instar de l’option property, la valeur de l’option group_by doit être
un property_path.
e. em
Cette option permet de spécifier l’EntityManager à utiliser pour récupérer les entités,
en passant son nom explicitement.
Cette option est destinée aux utilisateurs avancés. Dans l’immense majorité des cas, il
n’y a nul besoin de la renseigner.

8. DateType
Ce champ permet la saisie de dates.
a. widget
Au niveau du rendu, un champ date se traduit au choix par trois éléments select (jour,
mois et année), trois champs texte (également pour jour, mois et année) ou un seul
champ texte, où la date devra être saisie manuellement sous un format donné.
L’option widget définit ce rendu ; les valeurs possibles sont :
 choice (défaut) : affiche trois éléments select (listes déroulantes),
 text : affiche trois champs texte,
 single_text : affiche un seul champ texte.
b. format
Cette option gère le format d’affichage du widget.
Pour un widget de type choice ou text, il s’agit principalement de configurer l’ordre
d’affichage des éléments. La valeur dMy affiche par exemple le champ pour le jour,
puis le mois, et enfin l’année. L’affichage peut être ainsi rapidement personnalisé : la
valeur d - M - y, par exemple, sépare les différents champs avec un tiret et des
espaces.
Dans le cas d’un widget de type single_text, cette option spécifie la mise en forme
des données attendue dans le champ texte. Si l’option est placée à d-M-y HH:mm:ss,
par exemple, l’utilisateur devra saisir une valeur de type 18-06-2014 14:58:05.
Pour des exemples détaillés sur l’utilisation des formats, veuillez vous référer à la
documentation suivante : https://unicode-org.github.io/icu/userguide/icu/
c. model_timezone
Cette option permet de spécifier le fuseau horaire utilisé pour la représentation Model
data.
Les fuseaux horaires admis sont les mêmes que ceux de PHP, référencés à l’adresse
suivante : http://php.net/manual/fr/timezones.php
d. view_timezone
Cette option permet de spécifier le fuseau horaire utilisé pour la représentation View
data. En d’autres termes, c’est le fuseau horaire dans lequel l’utilisateur saisit les
données.
Cette option est utile lorsque le fuseau horaire de l’utilisateur est différent de celui du
serveur web, par exemple.
e. years
Dans le cas de listes déroulantes, cette option spécifie les années à afficher.
Par défaut, une période de dix années est disponible, avec l’année courante à égale
distance de la première et de la dernière.
Pour générer facilement les années à afficher, vous pouvez utiliser la fonction
range : range(2010, 2020) par exemple.
f. months
Dans le cas de listes déroulantes, cette option spécifie les mois à afficher.
g. days
Dans le cas de listes déroulantes, cette option spécifie les jours à afficher.
h. placeholder
Pour le widget choice (listes déroulantes), cette option ajoute une option sans valeur
en début de liste.
Elle peut être contrôlée individuellement pour chaque élément :
$builder->add('ma_date', DateType::class, array(
'placeholder' => array(
'day' => 'Jour',
'month' => 'Mois',
'year' => 'Année',
),
));

i. Types similaires
Quelques autres types permettant de gérer les dates sont semblables au
type date : BirthdayType, DatetimeType et TimeType.

9. FileType
Cette option permet de générer un champ capable de sélectionner et de charger des
fichiers.
Ce champ est assez simple au niveau du rendu et il est différent des autres au niveau
de l’utilisation des données soumises. En effet, tandis que pour la plupart des champs,
la valeur soumise est injectée dans l’objet de la couche Modèle, ici, il n’y a pas de
valeur concrète en tant que tel, mais un envoi de fichier.
a. multiple
Cette option accepte une valeur de type boolean. Lorsqu’elle est placée à true,
l’utilisateur a la possibilité de charger plusieurs fichiers via le champ. Cela se fait
grâce à l’attribut HTML5 multiple.
b. Récupérer les fichiers
La valeur utilisée par le framework est un objet spécial. Cet objet regroupe les
différentes informations sur le ou les fichiers chargés par l’utilisateur.
Il est de type Symfony\Component\HttpFoundation\File\UploadedFile.
Vous pouvez le récupérer en invoquant la
méthode getData() (ou getNormData(), car, ici, les
représentations Norm et Model sont identiques) du nœud représentant le champ
fichier :
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;

public function index(Request $request)


{
$form = $this->createFormBuilder()
->add('fichier', FileType::class)
->add('envoyer', SubmitType::class)
->getForm()
;

$form->handleRequest($request);

if ($form->isSubmitted()) {
$fichier = $form->get('fichier')->getNormData();
// traitement du fichier...
}

return $this->render('default/index.html.twig',
[ 'form' => $form->createView() ]
);
}

Si l’option multiple vaut true, la valeur récupérée est un tableau


d’objets UploadedFile.
c. Traiter les fichiers
Vérifier la validité du fichier
Par mesure de sécurité, la première action à effectuer lors du chargement d’un fichier
transmis par un utilisateur est de vérifier sa validité.
En effet, pour un champ de type file, par défaut, le framework ne se préoccupe pas de
la validité du fichier, mais met à disposition du développeur toutes les informations
concernant le fichier dans un objet UploadedFile.
La validité du fichier peut être vérifiée via la méthode isValid() :
if ($fichier->isValid()) {
// traitement du fichier
}

Nous verrons plus loin, au cours de la section sur la validation, comment valider
automatiquement un fichier.
Enregistrer un fichier
Lors du chargement de fichiers, PHP les traite en tant que fichiers « temporaires ». Ils
sont placés dans un répertoire volatile et il revient à l’application de les enregistrer
définitivement au sein d’un répertoire donné :
if ($fichier->isValid()) {
$fichier->move('/chemin/vers/repertoire', 'nom_du_fichier');
}

La méthode move() de l’objet UploadedFile enregistre le fichier à un emplacement


donné. Cette méthode est en quelque sorte l’équivalent de la fonction
PHP move_uploaded_file().
Travailler directement avec le fichier
Si vous ne souhaitez pas utiliser le fichier tel quel mais le transformer au
préalable pour, dans le cas d’une image, par exemple, en créer une miniature ou la
redimensionner, vous pouvez accéder au fichier temporaire directement, avec la
méthode getPathname().
L’objet UploadedFile hérite de la classe SplFileInfo de PHP.

10. CheckboxType
Ce champ correspond à une seule case à cocher. La valeur récupérée dans la couche
Modèle est de type boolean.
$builder->add('email_partenaires', CheckboxType::class, array(
'label' => 'Acceptez-vous de recevoir des e-mails de nos
partenaires ?',
'required' => false,
));

Dans certains cas, le champ n’a pas de correspondance au sein de l’objet de la couche
Modèle. Pour les conditions d’utilisation d’un site, par exemple, les utilisateurs étant
obligés de cocher cette case pour pouvoir s’inscrire, il convient de placer
l’option mapped à false.

11. SubmitType, ResetType et ButtonType


Les types submit, reset et button permettent de configurer des boutons au sein du
formulaire.
Le type submit est le plus important. Il ajoute un bouton qui, une fois cliqué, soumet
le formulaire.
Les boutons reset, quant à eux, réinitialisent le formulaire à son état initial : toutes les
modifications effectuées par l’utilisateur sont perdues. Cela se fait du côté client car la
réinitialisation est gérée par le navigateur.
Enfin, le type button est un bouton classique et cliquer dessus n’a aucun effet. En
pratique, le bouton est souvent utilisé en coordination avec du JavaScript.
Vérifier si un bouton a été cliqué
Depuis le contrôleur, il est possible de vérifier si un bouton a été cliqué. Il suffit de
récupérer le nœud du bouton submit depuis le formulaire principal et d’invoquer la
méthode isClicked() :
if ($form->get('nom_du_bouton_submit')->isClicked()) {
// bouton cliqué
}

À première vue, cette technique paraît inutile. En effet, nous savons que la
méthode isSubmitted() de l’objet Form principal permet de savoir s’il a été soumis
ou non.
Cependant, dans certains cas, les formulaires peuvent avoir plusieurs boutons submit.
Un exemple concret est la rédaction de messages : l’utilisateur, au lieu de l’envoyer
directement, pourrait l’enregistrer en brouillon pour continuer plus tard. Il y aurait
donc deux boutons distincts (« Envoyer » et « Enregistrer en brouillon ») :
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;

public function index(Request $request)


{
$form = $this->createFormBuilder()
->add('destinataire', TextType::class)
->add('message', TextareaType)
->add('envoyer', SubmitType::class)
->add('enregistrer_en_brouillon', SubmitType::class)
->getForm()
;

$form->handleRequest($request);

if ($form->isSubmitted()) {
$message = $form->getData();

if ($form->get('envoyer')->isClicked()) {
// envoi du message...
} else {
// enregistrement du brouillon
}
}

return $this->render('default/index.html.twig',
[ 'form' => $form->createView() ]
);
}
Précédent
Fonctionnement du composant
Suivant
Créer des formulaires réutilisables
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables

 1. Définir un formulaire avec la classe AbstractType


 2. Utiliser un formulaire défini dans une classe
 a. Définition manuelle
 b. Avec l’injection de dépendances

Validation des données


Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Créer des formulaires réutilisables


Pour l’instant, nous avons créé des formulaires directement depuis le contrôleur, grâce
au FormBuilder. Cependant, une bonne pratique consiste à extraire la configuration
d’un formulaire au sein d’une classe dédiée. Cela permet une meilleure clarté du code
et rend par la même occasion le formulaire réutilisable.
1. Définir un formulaire avec la classe AbstractType
Pour ce faire, nous devons créer une classe implémentant l’interface Symfony\
Component\Form\FormTypeInterface. Pour simplifier cette tâche, nous pouvons
nous servir de la classe Symfony\Component\Form\AbstractType. Cette classe
contient différentes méthodes liées à la configuration d’un formulaire.
En réalité, et comme nous l’avons vu précédemment, ce que nous entendons par
« formulaire » est simplement le nœud principal d’une certaine arborescence. À ce
titre, la principale différence entre un formulaire permettant la modification
d’informations sur un client et un champ de type text est que le premier est un nœud
avec des enfants, tandis que le second est un nœud simple. C’est pour cette raison que
pour configurer un formulaire au sein d’une classe, nous définissons un type.
Cela peut prêter à confusion mais il est très important de comprendre le point
précédent, car de celui-ci découlent deux utilisations distinctes de la
classe AbstractType :
 Configurer les formulaires d’une application (inscription d’utilisateurs,
passage de commandes, etc.).
 Configurer ses propres types pour pouvoir les réutiliser dans ses applications, ou
étendre les types natifs du framework pour les personnaliser. D’ailleurs, les types
natifs de Symfony étendent cette même classe et vous pouvez les retrouver au
sein de l’espace de noms Symfony\Component\Form\Extension\Core\Type.
L’exemple suivant contient la configuration d’un formulaire relatif aux
coordonnées d’un client :
// src/Form/Type/ClientType.php

namespace App\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ClientType extends AbstractType


{
public function buildForm(
FormBuilderInterface $builder,
array $options
) {
$builder
->add('nom', TextType::class)
->add('prenom', TextType::class)
->add('adresse', TextareaType::class)
->add('date_de_naissance', BirthdayType::class, array(
'required' => false,
)
);
}

public function configureOptions(OptionsResolver


$resolver)
{
$resolver->setDefaults(array(
'validation_groups' => 'Coordonnées',
));
}

public function getName()


{
return 'client';
}
}

getParent
Non utilisée dans l’exemple précédent, cette méthode retourne le nom du type dont
doit hériter le type courant. Par défaut, elle retourne form.
La classe ci-dessous contient un type ma_date héritant de date :
// src/Form/Type/MaDateType.php

namespace App\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class MaDateType extends AbstractType


{
public function getParent()
{
return 'date';
}

public function getName()


{
return 'ma_date';
}
}

Ici, le type ma_date hérite de date mais n’apporte aucune modification à ce dernier.
Les deux types sont identiques.
L’héritage d’un type doit toujours se faire avec la méthode getParent() et non en PHP
via l’héritage classique (par exemple MaDateType extends DateType).
configureOptions
La méthode configureOptions() est utilisée pour gérer les différentes options du
formulaire.
Le framework l’invoque avec en argument un objet implémentant Symfony\
Component\OptionsResolver\OptionsResolverInterface.
Le composant OptionsResolver est capable de gérer les options. Il offre une
multitude de possibilités (normalisation, valeurs autorisées, etc.), et le
composant Form l’intègre pour la configuration des options des types.
En pratique, vous l’utiliserez principalement pour :
 changer les valeurs par défaut de certaines options,
 créer vos propres options.
On pourrait qualifier le composant OptionsResolver comme étant une version
« allégée » de la configuration avec l’injection de dépendances (cf. L’injection de
dépendances - Les extensions de bundle) ; ils ont sensiblement les mêmes objectifs.
L’exemple ci-dessous modifie la valeur par défaut de l’option years et ajoute une
option use_jquery pour le type ma_date :
// src/Form/Type/MaDateType.php

namespace App\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\Options;

class MaDateType extends AbstractType


{
public function configureOptions(OptionsResolver
$resolver)
{
$resolver->setDefaults(array(
'years' => range(date('Y') - 1, date('Y') + 1),
'use_jquery' => false,
));

$resolver->setNormalizers(array(
'widget' => function (Options $options, $value) {
if ($options['use_jquery']) {
return 'single_text';
}

return $value;
}
));
}

public function getParent()


{
return 'date';
}

public function getName()


{
return 'ma_date';
}
}

Ici, les années sélectionnables seront restreintes aux années courante, précédente et
suivante.
L’option use_jquery est également disponible et on pourrait imaginer que, mise
à true, elle permette d’activer un plug-in jQuery pour la sélection d’une date sur un
champ texte (par exemple, jQuery UI datepicker).
C’est ici qu’intervient la normalisation. Son rôle est de transformer la valeur de
certaines options si cela est nécessaire. Dans l’exemple ci-dessus, l’option widget du
type est normalisée en fonction de l’option use_jquery. Si cette dernière vaut true,
l’option widget est placée à single_text car le plug-in jQuery doit s’appliquer sur un
champ de type texte.
buildForm
C’est la méthode incontournable d’une classe représentant un type. Elle permet de
configurer les champs (ou nœuds) du formulaire.
Le premier argument passé par le framework est un FormBuilder, le second est un
tableau contenant les options du formulaire.
// src/Form/Type/ClientType.php

namespace App\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;

class ClientType extends AbstractType


{
public function buildForm(
FormBuilderInterface $builder,
array $options
) {
$builder
->add('nom', TextType::class)
->add('prenom', TextType::class)
->add('adresse', TextareaType::class)
->add('date_de_naissance', BirthdayType::class, array(
'required' => false,
)
);
}

public function getName()


{
return 'client';
}
}
buildView
Cette méthode est liée à l’affichage. Son but est de personnaliser
l’objet FormView (qui est une représentation du formulaire adaptée à la vue, créée
par la méthode Form::createView()). Souvenez-vous, c’est lui que nous avons
envoyé à la vue au cours de nos précédents exemples (cf. Le contrôleur, au début de
ce chapitre).
L’objet FormView contient un tableau de variables au sein de sa propriété vars. Ce
sont des informations utilisées pour personnaliser l’affichage. Les thèmes de
formulaires y ont accès et, selon la valeur de chaque variable, ont la possibilité de
modifier le rendu du formulaire ou du champ.
Pour notre précédent exemple (le type ma_date), il est préférable de ne pas
afficher un champ de type date lorsque celui-ci est couplé à un plug-in jQuery ; le
rendu pourrait être bancal. En effet, en rencontrant un champ de type date, les
navigateurs supportant HTML5 ajouteront une aide à la saisie, sous forme de
calendrier et apparaissant au clic de la souris. Ce comportement, couplé à un plug-in
jQuery, générera de la confusion, avec deux calendriers potentiellement l’un sur
l’autre.
Il est donc préférable d’utiliser le plug-in jQuery sur un champ de type text :
// src/Form/Type/ClientType.php

namespace App\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;

class MaDateType extends AbstractType


{
// ...

public function buildView(FormView $view, FormInterface $form,


array $options)
{
if ('single_text' == $options['widget'] &&
$options['use_jquery']) {
$view->vars['type'] = 'text';
}
}
// ...
}

Ici, pour un widget single_text (champ unique), si l’option use_jquery est égale
à true, nous plaçons la variable type à text.

2. Utiliser un formulaire défini dans une classe


Après avoir défini votre type de formulaire dans sa classe dédiée (héritant
d’AbstractType), vous disposerez de deux méthodes pour l’utiliser.
a. Définition manuelle
La première méthode est la définition manuelle : vous devez spécifier le type de la
classe de formulaire en premier argument de la méthode createForm() du contrôleur.
Le second argument est l’objet de la couche Modèle associé au formulaire.
/**
* @Route("/")
*/
public function index(Request $request)
{
$client = new Client();
$form = $this->createForm(ClientType::class, $client);

$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {


return new Response('Le formulaire est valide.');
}

return $this->render('default/index.html.twig',
['form' => $form->createView() ]
);
}

b. Avec l’injection de dépendances


Cette seconde technique est plutôt adaptée aux types génériques, qui représentent des
champs réutilisables (plugin jQuery, CAPTCHA, etc.). Elle nécessite la création d’un
service pour la classe du type de formulaire :
# Fichier config/services.yaml
services:
app.form.type.ma_date:
class: App\Form\Type\MaDateType
tags:
- { name: form.type, alias: ma_date }

Le service doit comporter le tag form.type. Il peut ensuite être utilisé comme ceci :

/**
* @Route("/")
*/
public function index(Request $request)
{
$client = new Client;

$form = $this->createFormBuilder($client)
->add('nom', TextType::class)
->add('date_de_naissance', 'ma_date', array(
'use_jquery' => true,
))
->add('valider', 'submit')
->getForm()
;

$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {


return new Response('Le formulaire est valide.');
}

return $this->render('default/index.html.twig',
['form' => $form->createView() ]
);
}

Avec cette technique, votre type de formulaire est utilisé de la même manière que les
types de Symfony : en le référençant par son alias (ma_date dans l’exemple ci-
dessus).
Précédent
Les types de champs de formulaire
Suivant
Validation des données
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données

 1. Objectifs
 2. La définition des contraintes de validation
 a. Ajout des contraintes lors de la configuration d’un formulaire
 b. Ajout des contraintes sur l’objet associé au formulaire
 c. Les différents formats de configuration
 d. Les options
 3. Les contraintes et leurs options
 a. NotBlank et NotNull
 b. IsNull et Blank
 c. IsTrue, IsFalse
 d. Type
 e. Email, Url et Ip
 f. Regex
 g. Length, Count
 h. Range
 i. Comparaisons
 j. Dates
 k. File
 l. Image
 m. Choice
 n. UniqueEntity
 o. Données financières
 p. Callback
 q. All
 r. Valid
 4. Groupes de validation
 5. Validation d’un objet hors du contexte d’un formulaire

Personnaliser le rendu - thèmes de formulaires


Quiz
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Validation des données


1. Objectifs
Avec Symfony, la validation est un composant pouvant être utilisé de manière
autonome. Cependant, en pratique, il est principalement utilisé pour la validation de
données en provenance de formulaires. C’est pour cette raison que nous l’abordons au
sein de ce chapitre.
Nous entendons ici la validation « côté serveur ». Il est important de ne pas la
confondre avec la validation côté client en HTML5.
Les options telles que required, ou le fait d’utiliser un champ de type email,
donneront lieu à une validation au niveau du navigateur, mais celle-ci ne doit en
aucun cas être considérée comme fiable. C’est seulement un complément à la
validation côté serveur car cela permet de réduire les interactions avec le serveur en
détectant des erreurs avant la soumission.

2. La définition des contraintes de validation


Les contraintes de validation sont un ensemble de règles pouvant être appliquées à
une valeur. Ces règles permettent de s’assurer que la valeur correspond à un ou
plusieurs critères donnés.
Concrètement, pour un formulaire d’inscription par exemple, ces règles pourraient
être les suivantes :
 Le pseudo doit contenir entre 3 et 20 caractères.
 L’adresse e-mail saisie doit être valide.
 Les noms et prénoms doivent être renseignés.
 etc.
Il existe deux méthodes pour configurer les contraintes d’un formulaire :
 Au niveau de l’objet de la couche Modèle qui est associé au formulaire.
 Sur les champs de formulaire en utilisant l’option constraints.
Configurons la validation d’un formulaire d’envoi d’e-mails avec les deux
méthodes. Le formulaire est constitué avec les champs et règles suivants :
 destinataire : champ de type email ; sa valeur doit être renseignée et
correspondre à un e-mail valide.
 titre : champ de type text ; sa valeur est optionnelle.
 message : champ de type textarea, ce champ est obligatoire.
 piece_jointe : champ optionnel de type file pour une éventuelle pièce jointe.
a. Ajout des contraintes lors de la configuration d’un formulaire
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\SubmitTyp
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\File;
use Symfony\Component\Validator\Constraints\NotBlank;

...

public function index(Request $request)


{
$form = $this->createFormBuilder(null, array(
'attr' => array('novalidate' => 'novalidate')
))
->add('destinataire', EmailType::class, array(
'constraints' => array(
new NotBlank(),
new Email(),
)
))
->add('titre', TextType::class)
->add('message', TextareaType::class, array(
'constraints' => array(
new NotBlank(),
)
))
->add('piece_jointe', FileType::class, array(
'constraints' => new File(),
))
->add('envoyer', SubmitType::class)
->getForm()
;
$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {


...
}

return $this->render('default/index.html.twig',
['form' => $form->createView() ]
);
}

Nous utilisons l’option constraints pour définir les contraintes de validation de


chaque champ. Une contrainte est un objet dont la classe réside au sein du namespace
(espace de noms) Symfony\Component\Validation\Constraints.
Ici, nous définissons plusieurs contraintes : Email, NotBlank et File.
 Email vérifie que la valeur saisie est un e-mail valide.
 NotBlank signifie que la valeur saisie pour le champ ne doit pas être vide.
 Enfin, File vérifie que le fichier est valide. Ainsi, il n’est plus nécessaire
d’invoquer la méthode isValid() de l’objet UploadedFile ; invoquer isValid() sur
le formulaire principal est suffisant.
Nous utilisons l’attribut novalidate sur le formulaire principal de manière à éviter la
validation côté client et se concentrer sur celle côté serveur.
b. Ajout des contraintes sur l’objet associé au formulaire
Cette seconde technique est utilisable lorsqu’un objet est associé au formulaire.
Les contraintes de validation peuvent être configurées sur cet objet (une entité
Doctrine par exemple). Le framework sera ensuite capable de les récupérer pour
effectuer la validation des données soumises.
Voici l’exemple précédent avec des contraintes au niveau de l’objet :
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;


use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\File;
use Symfony\Component\Validator\Constraints\Email;

/**
* Message
*
* @ORM\Table()
* @ORM\Entity
*/
class Message
{
/**
* @var integer
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;

/**
* @var string
*
* @NotBlank()
* @Email()
* @ORM\Column(name="destinataire", type="string", length=255)
*/
private $destinataire;

/**
* @var string
*
* @ORM\Column(name="titre", type="string", length=255)
*/
private $titre;

/**
* @var string
*
* @NotBlank()
* @ORM\Column(name="message", type="text")
*/
private $message;

/**
*
* @File()
* @var UploadedFile
*/
private $piece_jointe;

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

/**
* Set destinataire
*
* @param string $destinataire
* @return Message
*/
public function setDestinataire($destinataire)
{
$this->destinataire = $destinataire;

return $this;
}
/**
* Get destinataire
*
* @return string
*/
public function getDestinataire()
{
return $this->destinataire
}

/**
* Set titre
*
* @param string $titre
* @return Message
*/
public function setTitre($titre)
{
$this->titre = $titre;

return $this;
}

/**
* Get titre
*
* @return string
*/
public function getTitre()
{
return $this->titre;
}

/**
* Set message
*
* @param string $message
* @return Message
*/
public function setMessage($message)
{
$this->message = $message;

return $this;
}

/**
* Get message
*
* @return string
*/
public function getMessage()
{
return $this->message;
}

/**
* Set pieceJointe
*
* @param UploadedFile $piece_jointe
* @return Message
*/
public function setPieceJointe($piece_jointe)
{
$this->piece_jointe = $piece_jointe;

return $this;
}

/**
* Get pieceJointe
*
* @return UploadedFile|null
*/
public function getPieceJointe()
{
return $this->piece_jointe;
}
}

Ici, les contraintes sont utilisées en tant qu’annotations au-dessus des propriétés.
Cette technique est semblable au mapping de Doctrine2. La seule différence est que
ces annotations n’ont pas le même but : les unes gèrent la structure d’une table en
base de données alors que les autres sont liées à la validation.
Ensuite, la création du formulaire se retrouve fortement allégée :
use Symfony\Component\HttpFoundation\Request;
use App\Entity\Message;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;

...

public function index(Request $request)


{
$message = new Message();
$form = $this->createFormBuilder($message, array(
'attr' => array('novalidate' => 'novalidate')
))
->add('destinataire', EmailType::class)
->add('titre', TextType::class)
->add('message', TextareaType::class)
->add('piece_jointe', 'file')
->add('envoyer', 'submit')
->getForm()
;

$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {


...
}

return $this->render('default/index.html.twig',
['form' => $form->createView() ]
);
}

Cette seconde technique a plusieurs avantages :


 Si un même objet est partagé entre plusieurs formulaires, les règles de validation
n’ont pas à être dupliquées entre ces différents formulaires.
 La validation pour l’objet donné est disponible de manière autonome. Cela
devient pratique si des modifications peuvent être apportées depuis d’autres
sources qu’un formulaire (par exemple une API).
Les deux techniques peuvent bien sûr être combinées. Il est possible d’associer à un
formulaire un objet avec ses propres contraintes de validation, mais d’en ajouter
manuellement via l’option constraints.
c. Les différents formats de configuration
De la même manière que pour le routage, la validation peut être configurée au travers
de différents formats : annotations, YAML, XML et PHP.
Annotations
C’est la technique que nous avons utilisée précédemment pour la classe App\
FormBundle\Entity\Message. Elle consiste à importer chaque contrainte avec
l’instruction PHP use, puis à l’apposer sous forme d’annotation au-dessus de la
propriété à valider.
namespace App\Entity;

use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\NotBlank;

class Message
{
/**
* @Email()
* @NotBlank()
*/
private $destinataire;

// ....
}

Ici, les contraintes Email et NotBlank sont appliquées à la propriété $destinataire de


la classe Message.
Ajouter une instruction use pour chaque contrainte serait rébarbatif, les
importations d’annotations peuvent être simplifiées de la manière suivante :
namespace App\Entity;

use Symfony\Component\Validator\Constraints as Assert;

class Message
{
/**
* @Assert\Email()
* @Assert\NotBlank()
*/
private $destinataire;

// ....
}

L’alias Assert est défini pour le namespace Symfony\Component\Validator\


Constraints. Les contraintes sont donc directement configurables : il suffit de
préfixer les annotations par Assert\.
Cette technique est semblable à l’utilisation de l’alias ORM pour la configuration du
mapping d’une entité avec Doctrine et les annotations.
YAML
Pour le format YAML, la création d’un seul fichier est nécessaire. Il doit résider dans
le répertoire config/validator du bundle et être nommé validation.yaml.
L’index de premier niveau correspond au nom de la classe.
Ensuite, il faut utiliser l’index properties (nous verrons plus loin qu’il est également
possible d’utiliser des contraintes sur des getters ou même la classe directement).
Chaque propriété contient un tableau non associatif avec les différentes contraintes
qui doivent lui être assignées.
# config/validator/validation.yaml

App\Entity\Message:
properties:
destinataire:
- NotBlank: ~
- Email: ~

XML
Pour le format XML, le principe est le même que pour le format YAML ; le
fichier doit être créé au même endroit et être nommé validation.xml.
<!-- config/validator/validation.xml -->

<?xml version="1.0" encoding="UTF-8" ?>


<constraint-mapping
xmlns="http://symfony.com/schema/dic/constraint-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/constraint-
mapping http://symfony.com/schema/dic/constraint-
mapping/constraint-mapping-1.0.xsd">

<class name="App\Entity\Message">
<property name="destinataire">
<constraint name="NotBlank" />
<constraint name="Email" />
</property>
</class>

</constraint-mapping>

PHP
Pour définir les contraintes en PHP, il faut créer la
méthode loadValidatorMetadata() au sein de la classe contenant les données à
valider.
Cette méthode est invoquée par le framework avec un objet spécial en paramètre. Cet
objet permet, entre autres, d’ajouter des contraintes grâce à sa
méthode addPropertyConstraint(). Le premier argument est le nom de la propriété,
le second est la contrainte.
namespace App\Entity;

use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints as Assert;

class Message
{
public $destinataire;

public static function loadValidatorMetadata(ClassMetadata $m)


{
$m->addPropertyConstraint(
'destinataire',
new Assert\Email()
);
$m->addPropertyConstraint(
'destinataire',
new Assert\NotBlank()
);
}
}

d. Les options
Les contraintes de validation ne sont pas des règles figées car des options
permettent de modifier leur comportement.
L’option commune à toutes les contraintes est le message d’erreur utilisé en cas de
valeur incorrecte. Le framework possédant un inventaire de messages d’erreur, vous
n’aurez heureusement pas à spécifier ce message à chaque fois, mais seulement si
vous souhaitez modifier ou apporter plus de précision au message par défaut.
Il existe ensuite des options spécifiques. Dans le cas d’une contrainte sur la longueur
d’un pseudo par exemple, il n’y a pas de règle absolue, universelle ou incontestée
pour la règle à utiliser, certains sites web limitont la longueur à 3 à 20 caractères, et
d’autres, plus stricts, utilisont une fourchette de 5 à 15 caractères. La contrainte a
donc une option pour modifier cette limite.
Voyons comment configurer les options pour une contrainte avec les différents
formats. Ici, nous configurons la contrainte Length avec les options min et max.
Annotations
namespace App\Entity;

use Symfony\Component\Validator\Constraints as Assert;

class Message
{
/**
* @Assert\Length(min="3", max="100")
*/
private $titre;

// ....
}

YAML
# config/validator/validation.yml

App\Entity\Message:
properties:
titre:
- Length:
min: 3
max: 100

XML
<!-- config/validator/validation.xml -->

<?xml version="1.0" encoding="UTF-8" ?>


<constraint-mapping
xmlns="http://symfony.com/schema/dic/constraint-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/constraint-
mapping http://symfony.com/schema/dic/constraint-
mapping/constraint-mapping-1.0.xsd">

<class name="App\Entity\Message">
<property name="titre">
<constraint name="Length">
<option name="min">5</option>
<option name="max">100</option>
</constraint>
</property>
</class>

</constraint-mapping>

PHP
namespace App\Entity;

use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints as Assert;

class Message
{
public $destinataire;

public static function loadValidatorMetadata(ClassMetadata $m)


{
$m->addPropertyConstraint(
'titre',
new Assert\Length(array('min' => 5, 'max' => 100))
);
}
}

3. Les contraintes et leurs options


Par défaut, chaque contrainte a une option message. Par souci de lisibilité, nous ne la
répétons pas pour chaque description de contrainte.
Le framework dispose de messages d’erreur prédéfinis. Pour les activer en
français, veillez à ce que la configuration suivante soit bien présente dans le
fichier config/packages/translation.yaml :
framework:
default_locale: fr
translator:
default_path: '%kernel.project_dir%/translations'
fallbacks:
- fr

a. NotBlank et NotNull
Ces deux contraintes vérifient que la valeur n’est pas vide (NotBlank) ou nulle
(NotNull).
La contrainte NotNull est plus restrictive que NotBlank,
car NotBlank implique NotNull mais l’inverse n’est pas vrai.
En pratique, en utilisant les formulaires, vous n’aurez souvent pas à faire cette subtile
distinction entre une valeur vide et une valeur nulle. Avec un champ de type text, par
exemple, si l’utilisateur soumet le formulaire sans aucune valeur pour ce champ, le
framework utilisera null en Model data.
Utilisation (annotation)
namespace App\Entity;

use Symfony\Component\Validator\Constraints as Assert;

class Message
{
/**
* @Assert\NotBlank(message="Le destinataire doit être
* renseigné".)
*/
protected $destinataire;
}

b. IsNull et Blank
Ces contraintes s’assurent que la valeur est vide (Blank) ou nulle (IsNull).
c. IsTrue, IsFalse
IsTrue et IsFalse valident les valeurs booléennes true et false.
IsTrue peut par exemple être utilisé pour vérifier que l’utilisateur a accepté les
conditions générales d’utilisation du site, en cochant une case à cocher.
d. Type
Cette contrainte peut vérifier qu’une valeur est d’un type donné.
Ici, par type, nous entendons les types PHP tels que boolean, int, string, etc. Cela n’a
aucun rapport avec les types de formulaires.
type
L’option type définit le type requis. En interne, le framework utilise les
fonctions PHP is_*() (is_int(), is_string(), etc.)
Cette option accepte également une classe. La valeur est valide si elle correspond à
une instance de cette classe donnée.
Contrainte de type string sur une propriété (annotation)
Avec l’exemple suivant, nous vérifions que la valeur de la propriété $messageest bien
une chaîne de caractères.
namespace App\Entity;
use Symfony\Component\Validator\Constraints as Assert;

class Message
{
/**
* @Assert\Type(type="string")
*/
protected $message;
}

e. Email, Url et Ip
Ces contraintes permettent la validation de chaînes de caractères par rapport à un
format précis (email, url et ip). Elles sont l’équivalent des filtres de validation
PHPFILTER_VALIDATE_EMAIL, FILTER_VALIDATE_IP, etc.
Email
Cette contrainte vérifie la validité d’une adresse e-mail.
L’option checkMX peut effectuer une validation plus approfondie de l’e-mail.
Lorsqu’elle est mise à true, le framework vérifiera que le domaine est a priori capable
de traiter les e-mails. Pour cela, il utilise la fonction PHP checkdnsrr() sur le nom de
domaine de l’e-mail, en cherchant un enregistrement de type MX.
L’option checkHost est moins restrictive car elle considère l’e-mail valide si son
domaine possède au moins un enregistrement de type MX, A ou AAAA.
Ces deux options ont pour objectif de filtrer certaines adresses e-mail frauduleuses,
qui seraient correctes au niveau de la forme, mais dont le nom de domaine serait
inexistant.
Si vous souhaitez effectuer une validation du nom de domaine de l’e-mail, nous vous
conseillons d’utiliser checkHost. En effet, selon les spécifications du protocole
SMTP, s’il n’y a pas d’enregistrement MX, l’enregistrement A doit être utilisé.
Url
Valide une valeur comme étant une URL. C’est une validation du format.
Contrairement à la contrainte Email, ici, il ne peut y avoir aucun test au niveau du
réseau.
L’option protocols accepte un tableau de protocoles valables pour l’URL à
valider. Par défaut, cette option contient http et https.
Ip
Cette contrainte peut valider les adresses IP et, comme avec la contrainte Url, c’est
uniquement une validation du format.
L’option version permet de spécifier s’il y a une contrainte de type (V4 ou V6) ou de
plages (privées et/ou réservées). Les valeurs possibles
sont 4, 6, all, 4_no_priv, 6_no_priv, all_no_priv, 4_no_res, 6_no_res, all_no_res, 4
_public, 6_public et all_public. Par défaut, seules les IPv4 sont acceptées.
f. Regex
Cette contrainte valide une chaîne de caractères par rapport à une expression régulière
(Regex).
pattern
Cette option contient l’expression régulière. C’est l’option par défaut ; elle peut donc
être utilisée sans être nommée.
L’exemple suivant valide un numéro de téléphone portable français :
use Symfony\Component\Validator\Constraints as Assert;

// ...

/**
* @var string
*
* @Assert\Regex("/^0[6|7][0-9]{8}$/")
*/
private $portable;

match
L’option match vaut true par défaut. Si elle est configurée à false, la valeur ne devra
pas correspondre au pattern pour être considérée comme valide.
g. Length, Count
Ces contraintes contrôlent la longueur d’une chaîne de caractères, la valeur ou la taille
d’un tableau.
min et max
Ces options définissent les longueurs minimale et/ou maximale de la valeur. Il s’agit
du nombre de caractères pour la contrainte Length, ou, pour la contrainte Count, du
nombre d’éléments.
Renseigner les deux options n’est pas obligatoire. Si l’une d’entre elles est omise, il
n’y a pas de limite minimale ou maximale.
namespace App\Entity;

use Symfony\Component\Validator\Constraints as Assert;

class Message
{
/**
* @Assert\Length(min="3")
*/
private $message;

// ....
}

Ici, la propriété $message doit contenir au minimum trois caractères pour être valide.
Attention, comme c’est le cas avec la plupart des contraintes, la valeur null ou une
chaîne vide seront considérées comme valides. Si ce n’est pas le comportement
souhaité, les contraintes NotBlank ou NotNull peuvent être utilisées en complément.
minMessage
Cette option définit le message d’erreur qui est affiché si la valeur est inférieure
à min.
maxMessage
Cette option définit le message d’erreur qui est affiché si la valeur est supérieure
à max.
exactMessage
Enfin, les contraintes Length et Count peuvent être configurées de manière à valider
une longueur fixe et non une fourchette de valeurs. Pour cela, il faut placer les mêmes
valeurs dans les options min et max.
Dans ce cas-là, c’est le message d’erreur contenu dans l’option exactMessage qui est
affiché.
Les contraintes Length et Count, contrairement aux autres, n’ont donc pas
d’option message, mais une option spécifique pour chaque message en fonction de
l’erreur.
h. Range
Cette contrainte est semblable à Length et Count, mais s’applique à des
valeurs numériques. Elle permet de vérifier qu’un nombre est compris entre deux
valeurs.
Ses options sont les mêmes que celles de Length et Count.
i. Comparaisons
Il existe plusieurs contraintes de comparaisons ; elles ont toutes (en plus de message)
une seule option : value.
Il existe une contrainte pour chacun des opérateurs suivants : ==, !=, ===, !
==, >, >=, < et <=.
 EqualTo : compare en utilisant l’opérateur ==.
 NotEqualTo : compare en utilisant l’opérateur !=.
 IdenticalTo : compare en utilisant l’opérateur ===.
 NotIdenticalTo : compare en utilisant l’opérateur !==.
 LessThan : compare en utilisant l’opérateur <.
 LessThanOrEqual : compare en utilisant l’opérateur <=.
 GreaterThan : compare en utilisant l’opérateur >.
 GreaterThanOrEqual : compare en utilisant l’opérateur >=.
L’exemple suivant est une classe Devis, dont le montant ne doit pas être fixé en
dessous de 20 euros.
namespace App\Entity;

use Symfony\Component\Validator\Constraints as Assert;

class Devis
{
/**
* @Assert\GreaterThanOrEqual(20)
*/
protected $montant;

// ...

j. Dates
Certaines contraintes sont capables de valider des dates : Date, DateTime et Time.
Ces trois contraintes considèrent un objet PHP de type DateTime comme
valide. Elles acceptent toutefois des chaînes de caractères respectant un certain format
:
 Date : YYYY-MM-DD (par exemple 2014-05-15)
 DateTime : YYYY-MM-DD HH:MM:SS (par exemple 2014-05-15 15:10:39)
 Time : HH:MM:SS (par exemple 15:10:39)
k. File
File valide que la valeur est un fichier chargé par formulaire (une instance
de Symfony\Component\HttpFoundation\File\UploadedFile) ou un fichier
classique (une instance de Symfony\Component\HttpFoundation\File\File ou une
chaîne de caractères contentant le chemin vers le fichier).
maxSize
Cette option spécifie la taille maximale du fichier. Au-dessus de celle-ci, il est
considéré comme invalide.
Il existe différents formats de valeurs pour cette option :
 un entier : correspond à un nombre d’octets (par exemple, 1024).
 un entier suivi de k ou M : correspond à un nombre de kilo-octet ou méga-octet
(par exemple 1M ou 50k).
Cette option est une configuration applicative. Si sa valeur dépasse la
directive PHP post_max_size, l’option n’aura aucun effet.
mimeTypes
Cette option contient un tableau de chaînes ou une chaîne représentant un type
MIME ; le sous-type peut être remplacé par un astérisque :
# config/validator/validation.yaml

App\Entity\Chanson:
properties:
fichier:
- File:
maxSize: 10M
mimeTypes: audio/*

l. Image
Cette contrainte hérite de File, mais elle valide seulement les fichiers représentant des
images.
Les options minWidth, maxWidth, minHeight et maxHeight spécifient (en pixels)
les largeurs et hauteurs (minimales et maximales) pour l’image.
m. Choice
Choice restreint les valeurs valides en fonction d’une liste donnée.
Il n’est pas nécessaire d’utiliser cette contrainte pour valider les données en
provenance d’un champ de type choice car le composant Form le fait déjà.
choices
Cette option contient les valeurs valides, sous la forme d’un tableau :
# config/validator/validation.yml

App\Entity\Commande:
properties:
statut:
- Choice:
choices: [créée, validée, annulée, envoyée]

Ici, la propriété statut d’un objet de type App\Entity\Commande doit valoir une des
valeurs spécifiées.
callback
Au lieu de définir la liste de valeurs manuellement avec l’option choices, il est
possible d’utiliser le retour d’une méthode statique. Dans ce cas là, le nom de cette
méthode doit être renseigné via le paramètre callback.
multiple
Cette option, mise à true, affectera la contrainte de validation de manière à ce qu’elle
s’attende à un tableau de valeurs et non à une seule valeur scalaire.
min et max
Si multiple vaut true, les options min et max peuvent limiter le nombre d’entrées du
tableau.
n. UniqueEntity
Cette contrainte est l’équivalent applicatif d’une clé unique avec MySQL. Elle peut
être appliquée à une ou plusieurs propriétés mappées par Doctrine (donc des colonnes,
cf. Accéder aux bases de données avec Doctrine - Définition des entités et de leur
mapping).
UniqueEntity doit être configuré sur la classe et non sur une propriété :
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;


use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

/**
* @ORM\Entity
* @UniqueEntity("email")
*/
class Utilisateur
{
/**
* @var string $email
*
* @ORM\Column(type="string", unique=true)
*/
protected $email;

// ...
}

Ici, l’option unique de l’annotation @Column doit être distinguée de


l’annotation @UniqueEntity. Cette option configure, au niveau de la base de
données, une clé unique pour la colonne email, tandis que @UniqueEntity est une
contrainte de validation applicative.
fields
C’est l’option par défaut. Elle contient la propriété ou un tableau de propriétés qui
doivent être uniques.
repositoryMethod
Par défaut, la méthode findBy() du repository est invoquée. Pour utiliser une méthode
personnalisée, vous devez la renseigner sous cette option.
o. Données financières
Il existe une multitude de contraintes permettant de valider des données
financières telles que des numéros de carte de crédit ou des numéros IBAN.
CardScheme
Cette contrainte valide un numéro de carte de crédit. L’option schemes doit contenir
un tableau de types de cartes de crédit acceptées (VISA, MASTERCARD, etc.).
Iban
Cette contrainte valide un identifiant de compte IBAN. Elle n’a aucune option si ce
n’est la possibilité d’éditer le message d’erreur, comme c’est le cas avec toutes les
autres contraintes.
p. Callback
Callback est une contrainte particulière par les possibilités qu’elle offre. Ce n’est pas
une contrainte de validation avec une règle établie, mais grâce à cette contrainte, le
développeur peut établir sa ou ses propres règles, au sein d’une méthode de l’objet.
Cette contrainte offre une très grande liberté car le développeur peut exécuter des
vérifications qui ne seraient pas possibles avec les contraintes existantes.
Exemple
Supposons une classe Vehicule avec une règle de validation personnalisée : si le
véhicule n’est pas neuf, son kilométrage doit être renseigné.
// ...

use Symfony\Component\Validator\ExecutionContextInterface;
use Symfony\Component\Validator\Constraints as Assert;

class Vehicule
{
private neuf;
private kilometrage;

// ...
/**
* @Assert\Callback()
*/
public function isValid(ExecutionContextInterface $context)
{
if (!$this->neuf && !$this->kilometrage) {
$context->addViolationAt(
'kilometrage',
'Le kilométrage du véhicule doit être renseigné.'
);
}

// autres vérifications personnalisées


}
}

Ici, la contrainte Callback est placée au-dessus de la méthode isValid() et le


framework invoque cette méthode avec en paramètre un objet implémentant
l’interface Symfony\Component\Validator\ExecutionContextInterface. Nous
utilisons sa méthode addViolationAt() pour ajouter une erreur sur une propriété
donnée (kilometrage) si nécessaire.
q. All
La contrainte All est utilisée sur les tableaux. Elle permet d’appliquer une contrainte à
chacun de ses éléments.
Imaginons qu’un utilisateur puisse avoir une multitude d’adresses e-mail définies. La
propriété emails pourrait avoir une contrainte All définie comme ceci :
namespace App\Entity;

use Symfony\Component\Validator\Constraints as Assert;


class Email
{
/**
* @Assert\All({
* @Assert\NotBlank
* @Assert\Email(),
* })
*/
protected $emails = array();
}

Dans l’exemple ci-dessus, toutes les valeurs du tableau qu’est la


propriété $emails devront être non vides et représenter des adresses e-mail au format
valide.
r. Valid
La contrainte Valid est utile pour valider récursivement un objet. Si un objet contient
un autre objet imbriqué, avec lui-même ses propres contraintes de validation, en
apposant la contrainte Valid sur la propriété correspondant à l’objet imbriqué,
Symfony validera celui-ci également.

4. Groupes de validation
Parfois, un objet est modifiable depuis plusieurs sources différentes, chacune ayant
ses spécificités. Par exemple, un objet Utilisateur peut être créé à la suite du
remplissage d’un formulaire d’inscription et des informations peuvent être
ajoutées/modifiées depuis un autre formulaire, dans une rubrique Mon compte ou via
une API.
À chaque fois, les contraintes de validation à utiliser sont potentiellement
différentes (en fonction de la source). C’est alors qu’interviennent les groupes de
validation. Leur rôle est de répartir les contraintes de validation en plusieurs sections.
// src/Entity/Utilisateur.php

namespace App\Entity;

use Symfony\Component\Validator\Constraints as Assert;

class Utilisateur
{
/**
* @Assert\Email()
*/
private $email;

/**
* @Assert\NotBlank()
* @Assert\Length(min=5)
*/
private $mot_de_passe;

/**
* @Assert\EqualTo(
* "Kj8Zep12kado",
* message="Le code coupon est invalide.",
* groups={"Privileges"}
* )
*/
private $coupon_privileges;
}

Ici, la contrainte de la propriété $coupon_privileges est assignée au


groupe Privileges. Les autres contraintes, n’étant assignées à aucun groupe,
appartiennent au groupe Default.
Ensuite, pour valider un objet en utilisant le groupe Privileges, le
composant Validator peut être utilisé comme ceci :
$erreurs = $validator->validate($utilisateur,
array('Privileges'));

Cependant, le cas le plus courant est sans aucun doute une utilisation des groupes au
travers des formulaires.
Pour l’exemple précédent, on pourrait imaginer une page spéciale où l’utilisateur
aurait la possibilité de remplir un champ avec le code d’un coupon lui donnant accès à
des offres spéciales. Le formulaire serait défini comme ceci :
namespace App\Controller;

use Symfony\Component\HttpFoundation\Request;
use App\Entity\Client;

...
public function index(Request $request)
{
$client = new Client();

$form = $this->createFormBuilder($client, array(


'validation_groups' => array('Privileges')
))
->add('coupon_privileges', TextType::class)
->add('envoyer', 'submit')
->getForm()
;

$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {


// Le coupon est valide...
}

return $this->render('default/index.html.twig',
['form' => $form->createView() ]
);
}

L’option validation_groups est configurée sur le formulaire principal, et non un


champ en particulier. Elle contient un tableau avec le nom des différents groupes de
validation à utiliser. Ici, c’est le groupe Privileges. Ainsi, seules les contraintes de la
propriété $coupon_privileges de l’objet Client seront utilisées pour effectuer la
validation.
Si un seul groupe doit être spécifié, le tableau à une entrée peut être remplacé par une
simple chaîne de caractères.

5. Validation d’un objet hors du contexte d’un


formulaire
Jusqu’à présent nous utilisions la validation dans le contexte d’un formulaire et la
validation était déclenchée par l’appel à la méthode isValid() de l’objet Form. Le
composant Validator de Symfony peut tout à fait être utilisé de manière autonome,
c’est-à-dire en dehors du contexte d’un formulaire, au sein d’un service par exemple,
ou bien dans une API REST.
Pour déclencher la validation, il suffit d’invoquer la méthode validate() du
service Validator en lui passant l’objet du modèle à valider en paramètre.
Pour obtenir le service Validator, on pourra le localiser en utilisant la
méthode get() (dans un contrôleur par exemple) :
$validator = $this->get(‘validator');

Il est également possible de l’injecter en paramètre d’une méthode en utilisant le


type Symfony\Component\Validator\Validator\ValidatorInterface :
public function index(ValidatorInterface $validator)
{
...
}

Voici un exemple complet de mise en œuvre.


use Symfony\Component\HttpFoundation\Request;
use Eni\FormBundle\Entity\Message;

public function indexAction(Request $request)


{
// Récupération des paramètres POST
$donnees = $request->request;

// Création d'un objet Message et injection des données


$message = new Message();
$message->setDestinataire($donnees->get('api_destinataire'));
$message->setTitle($donnees->get('api_titre'));
// ...

$erreurs = $this->get('validator')->validate($message);

if (0 === count($erreurs)) {
// Les données sont valides.
} else {
// Il y a au moins une erreur, les données sont invalides.
}
}
Précédent
Créer des formulaires réutilisables
Suivant
Personnaliser le rendu - thèmes de formulaires
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires

 1. Afficher le formulaire manuellement


 a. form_start()
 b. form_end()
 c. form_widget()
 d. form_errors()
 e. form_label()
 f. form_row()
 g. form_rest()
 h. Arborescence des parties de formulaires
 2. Créer des thèmes
 a. Formulaire d’exemple
 b. Créer et associer un thème de formulaires
 c. Comprendre le nom des blocks

Quiz
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Personnaliser le rendu - thèmes de


formulaires
Le rendu du formulaire intervient au niveau de la couche Vue. Tout comme pour la
gestion des templates, il peut être utilisé avec deux langages : Twig et PHP. Ici aussi,
nous allons nous concentrer sur son utilisation au travers de Twig.
Il existe plusieurs approches quant à la personnalisation du rendu d’un formulaire,
allant d’un contrôle direct depuis le template où il est affiché à des processus plus
automatisés avec les « thèmes de formulaires ».
1. Afficher le formulaire manuellement
Pour l’instant, nous avons utilisé la méthode Twig form() sur l’objet FormView pour
afficher un formulaire :
{{ form(form) }}

Cette simple fonction permet l’affichage du formulaire entier. Elle est donc très
pratique mais est aussi très restrictive. En effet, elle n’offre aucune possibilité de
contrôle sur le rendu final.
Le morceau de code suivant est équivalent à la fonction form() :
{{ form_start(form) }}

{{ form_label(form) }}

{{ form_errors(form) }}

{{ form_widget(form) }}

{{ form_end(form) }}

Il introduit de nouvelles fonctions Twig liées à l’affichage de


formulaires : form_start(), form_label(), form_errors(), form_widget() et form_en
d().
a. form_start()
Cette fonction ouvre le formulaire en affichant la balise d’ouverture <form>.
Les principaux attributs contenus dans cette balise sont action et method. Le premier
désigne la page vers laquelle le formulaire doit être soumis, tandis que le second
spécifie la méthode HTTP à utiliser pour la requête.
Vous pouvez contrôler ces attributs de différentes manières. Il existe tout d’abord les
options method et action, qui peuvent être passées au formulaire principal :
$form = $this->createForm(new RechercheType(), null, array(
'action' => $this->generateUrl('recherche'),
'method' => 'GET',
));

Ici, le formulaire est configuré de manière à envoyer les données via une requête de
type GET et vers l’URL correspondant à la route recherche.
Enfin, il est possible de configurer ces attributs au niveau du template, lors de
l’invocation de la fonction form() ou de celle de form_start() :
{{ form(form, {'action': path('recherche'), 'method': 'GET'}) }}
{# ou #}

{{ form_start(form, {'action': path('recherche'), 'method':


'GET'}) }}

En plus de gérer la définition des attributs action et method, la


fonction form_start() place automatiquement l’attribut enctype à multipart/form-
data si ce dernier contient un champ file.
b. form_end()
Cette fonction referme le formulaire.
Avant cela, si certains champs n’ont pas été « rendus », elle les affiche. Cette
automatisation est principalement utile pour les champs cachés, qu’il serait fastidieux
de devoir lister manuellement. Ce comportement peut être contrôlé grâce à la
variable render_rest ; si elle est placée à false, il sera désactivé :
{{ form_end(form, { render_rest: false }) }}

c. form_widget()
C’est la principale fonction liée au rendu d’un formulaire. Elle est chargée
d’afficher l’élément correspondant au formulaire. Pour un nœud de type text par
exemple, elle affiche un input de type text.
Cependant, elle se comporte différemment selon le type de nœud qui lui est passé en
paramètre :
 Si c’est un nœud de type compound (composé), comme c’est le cas avec un
formulaire principal, elle itère sur tous ses enfants et les affiche.
 Si le nœud n’est pas compound (nœud de type text, par exemple), elle
affiche directement l’élément correspondant.
Dans l’exemple précédent, nous avons passé le formulaire principal en paramètre, la
fonction form_widget a donc itéré et affiché chacun des champs composant le
formulaire.
Mais si nous lui passons un nœud non composé, le champ correspondant est affiché
directement :
{{ form_start(form) }}

{{ form_widget(form.prenom) }}

{{ form_end(form, { render_rest: false }) }}

Ici, seul le champ correspondant au nœud prenom du formulaire est affiché.


d. form_errors()
Cette fonction permet d’afficher les erreurs du nœud passé en paramètre. Il est
important de comprendre que les erreurs sont rattachées à un nœud et non au
formulaire entier.
Par conséquent, form_errors(form) affiche uniquement les erreurs du
formulaire principal. Ces dernières correspondent souvent aux erreurs des nœuds
enfants dont l’option error_bubbling a été placée à true.
Pour afficher les erreurs d’un nœud enfant, il faut le passer explicitement en
paramètre (par exemple, form_errors(form.prenom)).
e. form_label()
Cette fonction permet d’afficher le libellé du nœud passé en paramètre. Celui-ci peut
être surchargé grâce à un second paramètre :
form_label(form.prenom, 'Votre prénom') }}

f. form_row()
form_row() est un raccourci pour
invoquer form_label(), form_errors() et form_widget() en une séquence.
g. form_rest()
Cette fonction affiche tous les nœuds qui ne l’ont pas encore été.
h. Arborescence des parties de formulaires
Les différentes fonctions que nous venons d’évoquer sont complémentaires, elles
forment les maillons d’une même chaîne. Chaque fonction génère une partie du
formulaire. Ces parties sont ensuite imbriquées les unes dans les autres et forment une
arborescence.
En réalité, même si vous n’invoquez pas toutes les fonctions directement, elles seront
invoquées par le framework comme défini sur le schéma suivant :
L’invocation d’une des fonctions ci-dessus entraîne d’autres appels en cascade. Ainsi,
invoquer form() revient à invoquer form_start(), form_row() pour chaque nœud,
puis form_end(), tandis que form_row() invoque à son
tour form_label(), form_errors() et form_widget(). Enfin, form_end() est chargé de
faire appel à form_rest(), pour, si nécessaire, afficher les champs manquants avant de
refermer le formulaire.
Contrairement aux autres parties du formulaire, form_rows n’est pas disponible en
fonction Twig. C’est simplement un bloc contenant une boucle itérant sur les
formulaires composés, pour ensuite invoquer form_row() sur chacun des nœuds.

2. Créer des thèmes


La création de thèmes de formulaires est une alternative à la technique précédente.
Elle permet de modifier manuellement des parties précises du formulaire, laissant au
framework le soin de générer automatiquement les autres parties.
La création de thèmes a également pour avantage le fait de pouvoir être globalisée.
Un thème de formulaire peut être créé une seule fois, puis réutilisé pour toute une
application.
Les thèmes sont des blocks Twig correspondant chacun à une partie d’un
formulaire. Il vous est par exemple possible de créer un block personnalisant
le label d’un champ donné, le widget d’un autre champ ou même une ligne complète
(form_row).
a. Formulaire d’exemple
Nous utiliserons le formulaire suivant pour illustrer nos explications sur les thèmes de
formulaires :
...
public function index()
{
$form = $this->createFormBuilder()
->add('nom', TextType::class)
->add('fichier', FileType::class)
->add('valider', SubmitType::class)
->getForm()
;

return $this->render('default/index.html.twig'
[ 'form' => $form->createView() ]
);
}

...

C’est un formulaire contenant un champ nom de type text, un champ fichier de


type file, ainsi qu’un bouton de soumission.
Dans le template correspondant, par défaut, la fonction form() affiche le
formulaire de la manière suivante :
{# templates/default/index.html.twig #}

{# ... #}

{{ form(form) }}

b. Créer et associer un thème de formulaires


Ce rendu par défaut est peu attractif mais il est heureusement facilement
modifiable. Pour cela, il suffit de créer un fichier qui contient nos thèmes pour ce
formulaire, localisé à l’emplacement suivant : templates/form.html.twig.
L’emplacement et le nom du fichier ci-dessus sont complètement arbitraires. Vous
pouvez utiliser l’emplacement qui vous convient, dans la mesure où il correspond à un
emplacement valide pour un template Twig.
Créons un thème pour la partie form_row (celle-ci correspond à une ligne de
formulaire) de manière à rendre le formulaire plus ordonné :
{# templates/form.html.twig #}

{% block form_row %}
<div style="margin: 15px 0;">
<div style="float: left; width: 100px;">
{{ form_label(form) }}
</div>
<div>
{{ form_widget(form) }}
</div>
<div style="color: red;">
{{ form_errors(form) }}
</div>
</div>
{% endblock %}

Le thème ci-dessus est capable de personnaliser le rendu d’une ligne de formulaire. Il


fait appel à form_label(), form_widget() et form_errors() car il est la partie
« parente » de celles-ci (comme expliqué sur le schéma précédent avec l’arborescence
des parties d’un formulaire).
Une fois le template du thème créé, il doit être associé au(x) formulaire(s)
souhaité(s).
Association manuelle
Nous pouvons associer manuellement le thème à notre formulaire d’exemple :
{# templates/default/index.html.twig #}

{# ... #}

{% form_theme form 'form.html.twig' %}

{{ form(form) }}

Le tag form_theme est chargé d’effectuer l’association manuelle. Pour cela, il attend
deux arguments :
 Le premier est l’objet FormView sur lequel doit être appliqué le thème de
formulaires (ici form).
 Le second est le template contenant le thème de formulaires
(ici form.html.twig).
 Optionnellement, form_theme peut accepter des troisième, quatrième où énième
arguments. Ces arguments correspondent à des thèmes supplémentaires à associer
au formulaire.
Une fois notre tag configuré, le rendu du formulaire change :

Si vous avez besoin de faire de légères modifications sur un formulaire, créer un


fichier de thèmes à part peut être disproportionné. Pour ce type de cas, il est possible
de configurer le thème au sein même du template chargé d’afficher le formulaire, en
utilisant la variable globale Twig _self :
{# templates/default/index.html.twig #}

{% extends 'base.html.twig' %}

{% block body %}

{# ... #}

{% form_theme form _self %}

{{ form(form) }}

{% endblock %}

{% block form_row %}
<div style="margin: 15px 0;">
<div style="float: left; width: 100px;">
{{ form_label(form) }}
</div>
<div>
{{ form_widget(form) }}
</div>
<div style="color: red;">
{{ form_errors(form) }}
</div>
</div>
{% endblock %}

Comme vous pouvez le constater, l’exemple ci-dessus est un template utilisant


l’héritage. Il n’est pas possible de définir un thème de formulaire dans le template
courant si ce dernier n’a pas de template parent.
Thèmes globaux

Personnaliser plusieurs blocks


Un thème de formulaires peut personnaliser plusieurs blocks. Imaginons par exemple
que nous ne souhaitions pas afficher de label pour le champ du fichier. Il suffirait de
modifier le thème de la manière suivante :
{% block form_row %}
<div style="margin: 15px 0;">
<div style="float: left; width: 100px;">
{{ form_label(form) }}
</div>
<div>
{{ form_widget(form) }}
</div>
<div style="color: red;">
{{ form_errors(form) }}
</div>
</div>
{% endblock %}

{% block file_label %}&nbsp;{% endblock %}

Le formulaire est maintenant affiché comme suit :


c. Comprendre le nom des blocks
Les possibilités offertes par les thèmes de formulaires sont nombreuses. Encore faut-il
savoir comment les exploiter pleinement.
Le principal rouage de ce système est l’organisation des noms des blocks. En effet,
nous venons de voir que pour personnaliser une partie d’un formulaire, il suffisait de
définir un block avec un nom précis au sein d’un thème de formulaire.
Il est très important que vous compreniez la manière dont ces noms sont gérés. Les
noms des blocks à utiliser dans les thèmes sont en réalité composés de deux parties :
 La première partie est le type de nœud à personnaliser.
 La seconde correspond au contexte dans lequel il doit être personnalisé. Par
« contexte », nous entendons la fonction Twig qui est utilisée
(form_widget(), form_row(), etc.) sur le nœud.
Examinons ainsi les noms définis dans notre thème
précédent : form_row et file_label.
Le contenu du block form_row, tel que défini dans notre thème, est affiché pour les
nœuds de type form lors de l’invocation de la fonction form_row().
Le contenu du block file_label est quant à lui affiché pour les nœuds de type file lors
de l’invocation de la fonction form_label().
Nous savons aussi que chaque type a potentiellement un ou plusieurs parents. Ainsi,
le framework utilise un système de priorité pour retrouver le block à appliquer à un
nœud donné. Il recherche par le type au plus bas de l’arborescence du nœud, puis
remonte jusqu’au dernier parent, et dès qu’il trouve un block correspondant, il
l’affiche et arrête sa recherche.
Ce comportement paraît complexe au premier abord, mais il est en réalité
enfantin. Analysons le comportement du framework au travers d’un exemple,
l’application de la fonction form_widget() sur le nœud fichier de notre
formulaire défini précédemment. Voici le template :
{{ form(form) }}

Ici, la fonction utilisée est form(). Étant en haut de l’arborescence, elle


invoquera form_start(), form_row() sur chaque nœud, et
enfin form_end(). form_row() invoque quant à
elle, form_label(), form_errors() et form_widget().
form_widget() est donc invoqué à un moment donné sur le nœud fichier. Dès lors, le
framework recherche les blocs suivants dans les thèmes (généraux ou associés
manuellement au formulaire) :
 _form_fichier_widget
 file_widget
 form_widget
Ces noms ont un ordre de priorité : si aucun block
nommé _form_fichier_widget n’existe dans les thèmes, le framework
recherche file_widget, puis form_widget. Une fois l’un d’entre eux trouvé, il est
affiché et la recherche s’arrête.
Dans ces noms de blocks, la seconde partie est, comme nous l’avons précédemment
expliqué, le type de la fonction qui est appliquée (ici widget, car c’est la
fonction form_widget). Quant à la première partie, elle correspond au nom de chaque
type dont hérite le nœud, ainsi qu’à son titre propre (file et form, car le nœud est de
type file et ce dernier hérite de form).
Enfin, la première partie du premier nom de block recherché est particulière : c’est un
nom unique permettant d’identifier chaque nœud du formulaire. Il est la concaténation
du nom de chaque parent du nœud affiché (précédé par un underscore), en
commençant par le plus haut parent.
Ainsi, notre formulaire d’exemple a un nœud principal nommé form (c’est le nom par
défaut des formulaires créés à la volée depuis le contrôleur ; si le formulaire était
défini dans sa propre classe, son nom serait la valeur retournée par la
méthode getName()). Ensuite, au niveau d’arborescence suivant, nous retrouvons ce
nœud fichier. Son nom unique pour la fonction form_widget() est
donc _form_fichier_widget.
Précédent
Validation des données
Suivant
Quiz
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires

 1. Afficher le formulaire manuellement


 a. form_start()
 b. form_end()
 c. form_widget()
 d. form_errors()
 e. form_label()
 f. form_row()
 g. form_rest()
 h. Arborescence des parties de formulaires
 2. Créer des thèmes
 a. Formulaire d’exemple
 b. Créer et associer un thème de formulaires
 c. Comprendre le nom des blocks

Quiz
La sécurité dans une application Symfony
Tester son application Symfony
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Personnaliser le rendu - thèmes de


formulaires
Le rendu du formulaire intervient au niveau de la couche Vue. Tout comme pour la
gestion des templates, il peut être utilisé avec deux langages : Twig et PHP. Ici aussi,
nous allons nous concentrer sur son utilisation au travers de Twig.
Il existe plusieurs approches quant à la personnalisation du rendu d’un formulaire,
allant d’un contrôle direct depuis le template où il est affiché à des processus plus
automatisés avec les « thèmes de formulaires ».

1. Afficher le formulaire manuellement


Pour l’instant, nous avons utilisé la méthode Twig form() sur l’objet FormView pour
afficher un formulaire :
{{ form(form) }}

Cette simple fonction permet l’affichage du formulaire entier. Elle est donc très
pratique mais est aussi très restrictive. En effet, elle n’offre aucune possibilité de
contrôle sur le rendu final.
Le morceau de code suivant est équivalent à la fonction form() :
{{ form_start(form) }}

{{ form_label(form) }}

{{ form_errors(form) }}

{{ form_widget(form) }}

{{ form_end(form) }}
Il introduit de nouvelles fonctions Twig liées à l’affichage de
formulaires : form_start(), form_label(), form_errors(), form_widget() et form_en
d().
a. form_start()
Cette fonction ouvre le formulaire en affichant la balise d’ouverture <form>.
Les principaux attributs contenus dans cette balise sont action et method. Le premier
désigne la page vers laquelle le formulaire doit être soumis, tandis que le second
spécifie la méthode HTTP à utiliser pour la requête.
Vous pouvez contrôler ces attributs de différentes manières. Il existe tout d’abord les
options method et action, qui peuvent être passées au formulaire principal :
$form = $this->createForm(new RechercheType(), null, array(
'action' => $this->generateUrl('recherche'),
'method' => 'GET',
));

Ici, le formulaire est configuré de manière à envoyer les données via une requête de
type GET et vers l’URL correspondant à la route recherche.
Enfin, il est possible de configurer ces attributs au niveau du template, lors de
l’invocation de la fonction form() ou de celle de form_start() :
{{ form(form, {'action': path('recherche'), 'method': 'GET'}) }}

{# ou #}

{{ form_start(form, {'action': path('recherche'), 'method':


'GET'}) }}

En plus de gérer la définition des attributs action et method, la


fonction form_start() place automatiquement l’attribut enctype à multipart/form-
data si ce dernier contient un champ file.
b. form_end()
Cette fonction referme le formulaire.
Avant cela, si certains champs n’ont pas été « rendus », elle les affiche. Cette
automatisation est principalement utile pour les champs cachés, qu’il serait fastidieux
de devoir lister manuellement. Ce comportement peut être contrôlé grâce à la
variable render_rest ; si elle est placée à false, il sera désactivé :
{{ form_end(form, { render_rest: false }) }}

c. form_widget()
C’est la principale fonction liée au rendu d’un formulaire. Elle est chargée
d’afficher l’élément correspondant au formulaire. Pour un nœud de type text par
exemple, elle affiche un input de type text.
Cependant, elle se comporte différemment selon le type de nœud qui lui est passé en
paramètre :
 Si c’est un nœud de type compound (composé), comme c’est le cas avec un
formulaire principal, elle itère sur tous ses enfants et les affiche.
 Si le nœud n’est pas compound (nœud de type text, par exemple), elle
affiche directement l’élément correspondant.
Dans l’exemple précédent, nous avons passé le formulaire principal en paramètre, la
fonction form_widget a donc itéré et affiché chacun des champs composant le
formulaire.
Mais si nous lui passons un nœud non composé, le champ correspondant est affiché
directement :
{{ form_start(form) }}

{{ form_widget(form.prenom) }}

{{ form_end(form, { render_rest: false }) }}

Ici, seul le champ correspondant au nœud prenom du formulaire est affiché.


d. form_errors()
Cette fonction permet d’afficher les erreurs du nœud passé en paramètre. Il est
important de comprendre que les erreurs sont rattachées à un nœud et non au
formulaire entier.
Par conséquent, form_errors(form) affiche uniquement les erreurs du
formulaire principal. Ces dernières correspondent souvent aux erreurs des nœuds
enfants dont l’option error_bubbling a été placée à true.
Pour afficher les erreurs d’un nœud enfant, il faut le passer explicitement en
paramètre (par exemple, form_errors(form.prenom)).
e. form_label()
Cette fonction permet d’afficher le libellé du nœud passé en paramètre. Celui-ci peut
être surchargé grâce à un second paramètre :
form_label(form.prenom, 'Votre prénom') }}

f. form_row()
form_row() est un raccourci pour
invoquer form_label(), form_errors() et form_widget() en une séquence.
g. form_rest()
Cette fonction affiche tous les nœuds qui ne l’ont pas encore été.
h. Arborescence des parties de formulaires
Les différentes fonctions que nous venons d’évoquer sont complémentaires, elles
forment les maillons d’une même chaîne. Chaque fonction génère une partie du
formulaire. Ces parties sont ensuite imbriquées les unes dans les autres et forment une
arborescence.
En réalité, même si vous n’invoquez pas toutes les fonctions directement, elles seront
invoquées par le framework comme défini sur le schéma suivant :

L’invocation d’une des fonctions ci-dessus entraîne d’autres appels en cascade. Ainsi,
invoquer form() revient à invoquer form_start(), form_row() pour chaque nœud,
puis form_end(), tandis que form_row() invoque à son
tour form_label(), form_errors() et form_widget(). Enfin, form_end() est chargé de
faire appel à form_rest(), pour, si nécessaire, afficher les champs manquants avant de
refermer le formulaire.
Contrairement aux autres parties du formulaire, form_rows n’est pas disponible en
fonction Twig. C’est simplement un bloc contenant une boucle itérant sur les
formulaires composés, pour ensuite invoquer form_row() sur chacun des nœuds.

2. Créer des thèmes


La création de thèmes de formulaires est une alternative à la technique précédente.
Elle permet de modifier manuellement des parties précises du formulaire, laissant au
framework le soin de générer automatiquement les autres parties.
La création de thèmes a également pour avantage le fait de pouvoir être globalisée.
Un thème de formulaire peut être créé une seule fois, puis réutilisé pour toute une
application.
Les thèmes sont des blocks Twig correspondant chacun à une partie d’un
formulaire. Il vous est par exemple possible de créer un block personnalisant
le label d’un champ donné, le widget d’un autre champ ou même une ligne complète
(form_row).
a. Formulaire d’exemple
Nous utiliserons le formulaire suivant pour illustrer nos explications sur les thèmes de
formulaires :
...

public function index()


{
$form = $this->createFormBuilder()
->add('nom', TextType::class)
->add('fichier', FileType::class)
->add('valider', SubmitType::class)
->getForm()
;

return $this->render('default/index.html.twig'
[ 'form' => $form->createView() ]
);
}

...

C’est un formulaire contenant un champ nom de type text, un champ fichier de


type file, ainsi qu’un bouton de soumission.
Dans le template correspondant, par défaut, la fonction form() affiche le
formulaire de la manière suivante :
{# templates/default/index.html.twig #}

{# ... #}

{{ form(form) }}
b. Créer et associer un thème de formulaires
Ce rendu par défaut est peu attractif mais il est heureusement facilement
modifiable. Pour cela, il suffit de créer un fichier qui contient nos thèmes pour ce
formulaire, localisé à l’emplacement suivant : templates/form.html.twig.
L’emplacement et le nom du fichier ci-dessus sont complètement arbitraires. Vous
pouvez utiliser l’emplacement qui vous convient, dans la mesure où il correspond à un
emplacement valide pour un template Twig.
Créons un thème pour la partie form_row (celle-ci correspond à une ligne de
formulaire) de manière à rendre le formulaire plus ordonné :
{# templates/form.html.twig #}

{% block form_row %}
<div style="margin: 15px 0;">
<div style="float: left; width: 100px;">
{{ form_label(form) }}
</div>
<div>
{{ form_widget(form) }}
</div>
<div style="color: red;">
{{ form_errors(form) }}
</div>
</div>
{% endblock %}

Le thème ci-dessus est capable de personnaliser le rendu d’une ligne de formulaire. Il


fait appel à form_label(), form_widget() et form_errors() car il est la partie
« parente » de celles-ci (comme expliqué sur le schéma précédent avec l’arborescence
des parties d’un formulaire).
Une fois le template du thème créé, il doit être associé au(x) formulaire(s)
souhaité(s).
Association manuelle
Nous pouvons associer manuellement le thème à notre formulaire d’exemple :
{# templates/default/index.html.twig #}

{# ... #}

{% form_theme form 'form.html.twig' %}

{{ form(form) }}

Le tag form_theme est chargé d’effectuer l’association manuelle. Pour cela, il attend
deux arguments :
 Le premier est l’objet FormView sur lequel doit être appliqué le thème de
formulaires (ici form).
 Le second est le template contenant le thème de formulaires
(ici form.html.twig).
 Optionnellement, form_theme peut accepter des troisième, quatrième où énième
arguments. Ces arguments correspondent à des thèmes supplémentaires à associer
au formulaire.
Une fois notre tag configuré, le rendu du formulaire change :

Si vous avez besoin de faire de légères modifications sur un formulaire, créer un


fichier de thèmes à part peut être disproportionné. Pour ce type de cas, il est possible
de configurer le thème au sein même du template chargé d’afficher le formulaire, en
utilisant la variable globale Twig _self :
{# templates/default/index.html.twig #}

{% extends 'base.html.twig' %}

{% block body %}

{# ... #}

{% form_theme form _self %}

{{ form(form) }}
{% endblock %}

{% block form_row %}
<div style="margin: 15px 0;">
<div style="float: left; width: 100px;">
{{ form_label(form) }}
</div>
<div>
{{ form_widget(form) }}
</div>
<div style="color: red;">
{{ form_errors(form) }}
</div>
</div>
{% endblock %}

Comme vous pouvez le constater, l’exemple ci-dessus est un template utilisant


l’héritage. Il n’est pas possible de définir un thème de formulaire dans le template
courant si ce dernier n’a pas de template parent.
Thèmes globaux

Personnaliser plusieurs blocks


Un thème de formulaires peut personnaliser plusieurs blocks. Imaginons par exemple
que nous ne souhaitions pas afficher de label pour le champ du fichier. Il suffirait de
modifier le thème de la manière suivante :
{% block form_row %}
<div style="margin: 15px 0;">
<div style="float: left; width: 100px;">
{{ form_label(form) }}
</div>
<div>
{{ form_widget(form) }}
</div>
<div style="color: red;">
{{ form_errors(form) }}
</div>
</div>
{% endblock %}

{% block file_label %}&nbsp;{% endblock %}

Le formulaire est maintenant affiché comme suit :

c. Comprendre le nom des blocks


Les possibilités offertes par les thèmes de formulaires sont nombreuses. Encore faut-il
savoir comment les exploiter pleinement.
Le principal rouage de ce système est l’organisation des noms des blocks. En effet,
nous venons de voir que pour personnaliser une partie d’un formulaire, il suffisait de
définir un block avec un nom précis au sein d’un thème de formulaire.
Il est très important que vous compreniez la manière dont ces noms sont gérés. Les
noms des blocks à utiliser dans les thèmes sont en réalité composés de deux parties :
 La première partie est le type de nœud à personnaliser.
 La seconde correspond au contexte dans lequel il doit être personnalisé. Par
« contexte », nous entendons la fonction Twig qui est utilisée
(form_widget(), form_row(), etc.) sur le nœud.
Examinons ainsi les noms définis dans notre thème
précédent : form_row et file_label.
Le contenu du block form_row, tel que défini dans notre thème, est affiché pour les
nœuds de type form lors de l’invocation de la fonction form_row().
Le contenu du block file_label est quant à lui affiché pour les nœuds de type file lors
de l’invocation de la fonction form_label().
Nous savons aussi que chaque type a potentiellement un ou plusieurs parents. Ainsi,
le framework utilise un système de priorité pour retrouver le block à appliquer à un
nœud donné. Il recherche par le type au plus bas de l’arborescence du nœud, puis
remonte jusqu’au dernier parent, et dès qu’il trouve un block correspondant, il
l’affiche et arrête sa recherche.
Ce comportement paraît complexe au premier abord, mais il est en réalité
enfantin. Analysons le comportement du framework au travers d’un exemple,
l’application de la fonction form_widget() sur le nœud fichier de notre
formulaire défini précédemment. Voici le template :
{{ form(form) }}
Ici, la fonction utilisée est form(). Étant en haut de l’arborescence, elle
invoquera form_start(), form_row() sur chaque nœud, et
enfin form_end(). form_row() invoque quant à
elle, form_label(), form_errors() et form_widget().
form_widget() est donc invoqué à un moment donné sur le nœud fichier. Dès lors, le
framework recherche les blocs suivants dans les thèmes (généraux ou associés
manuellement au formulaire) :
 _form_fichier_widget
 file_widget
 form_widget
Ces noms ont un ordre de priorité : si aucun block
nommé _form_fichier_widget n’existe dans les thèmes, le framework
recherche file_widget, puis form_widget. Une fois l’un d’entre eux trouvé, il est
affiché et la recherche s’arrête.
Dans ces noms de blocks, la seconde partie est, comme nous l’avons précédemment
expliqué, le type de la fonction qui est appliquée (ici widget, car c’est la
fonction form_widget). Quant à la première partie, elle correspond au nom de chaque
type dont hérite le nœud, ainsi qu’à son titre propre (file et form, car le nœud est de
type file et ce dernier hérite de form).
Enfin, la première partie du premier nom de block recherché est particulière : c’est un
nom unique permettant d’identifier chaque nœud du formulaire. Il est la concaténation
du nom de chaque parent du nœud affiché (précédé par un underscore), en
commençant par le plus haut parent.
Ainsi, notre formulaire d’exemple a un nœud principal nommé form (c’est le nom par
défaut des formulaires créés à la volée depuis le contrôleur ; si le formulaire était
défini dans sa propre classe, son nom serait la valeur retournée par la
méthode getName()). Ensuite, au niveau d’arborescence suivant, nous retrouvons ce
nœud fichier. Son nom unique pour la fonction form_widget() est
donc _form_fichier_widget.
Précédent
Validation des données
Suivant
Quiz
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants
 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification

 1. Pare-feu
 a. Pare-feu pour ressources statiques/développement
 2. Authentification HTTP
 3. Authentification par formulaire de connexion
 4. Connexion automatique des utilisateurs
 5. Déconnexion des utilisateurs

Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Authentification
1. Pare-feu
Le rôle du pare-feu est de définir les zones de l’application sujettes au système de
sécurité.
Lorsque la requête d’un utilisateur correspond aux règles définies par un pare-feu, la
sécurité est activée. La requête doit donc passer au travers de ce pare-feu et satisfaire
les contraintes d’autorisations qui y sont spécifiées.
Une requête est associée à un pare-feu en fonction de son chemin (path). Reprenons la
section firewalls de notre configuration de base :
# config/packages/security.yaml

security:

# ...
firewalls:
mon_pare_feu:
pattern: ^/
anonymous: ~

Ici, nous définissons un pare-feu nommé mon_pare_feu. La


directive pattern contient l’expression régulière à appliquer sur le chemin de la
requête pour déterminer si elle doit passer par le pare-feu ou non. Comme tous les
chemins commencent obligatoirement par un slash, l’expression régulière ci-
dessus fait que toutes les requêtes passeront par le pare-feu mon_pare_feu.
La directive anonymous indique que les utilisateurs anonymes (non authentifiés)
peuvent accéder à ce pare-feu. L’application n’est donc pas sécurisée, toutes les pages
sont en libre accès.
Pare-feu pour un sous-domaine
En plus du path, le pare-feu peut analyser le nom de domaine de la requête. Cela se
fait avec la directive host :
host:
pattern: ^/
host: ma-section\.example\.com
http_basic: ~

Ici, le nom de domaine ma-section.example.com requiert une authentication HTTP.


Nous reviendrons sur cette technique plus loin dans ce chapitre.
a. Pare-feu pour ressources statiques/développement
Certains chemins doivent toujours être accessibles. C’est notamment le cas des
ressources statiques (images, feuilles de style, etc.) ou des URL utilisées en
interne par Symfony (comme la barre de débogage du Profiler).
Pour éviter de sécuriser malencontreusement ce type de ressources, nous vous
conseillons de créer un pare-feu sans règle de sécurité pour ces dernières :
security:

# ...

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

Veillez également à ce que ce pare-feu soit toujours en première position.

2. Authentification HTTP
La méthode d’authentification la plus simple est sans doute l’authentification HTTP
Basic :
security:
encoders:
Symfony\Component\Security\Core\User\User: plaintext

providers:
mes_utilisateurs:
memory:
users:
user: { password: userpass, roles: [ 'ROLE_USER' ] }

firewalls:
mon_pare_feu:
pattern: ^/
http_basic:
realm: "Mon pare-feu demande une authentification"

Comme mon_pare_feu n’accepte pas les utilisateurs anonymes, lors de


l’ouverture de n’importe quelle page du site web, une authentification HTTP est
demandée. Vous devez saisir le nom d’utilisateur user et le mot de
passe userpass pour vous identifier. Ces derniers sont définis dans la
section providers. Nous y reviendrons plus loin.

3. Authentification par formulaire de connexion


L’authentification HTTP Basic est très simple à mettre en place mais en général peu
utilisée dans le monde du Web.
La plupart des sites web possèdent un formulaire de connexion. Il permet
d’authentifier un visiteur grâce à un couple identifiant/mot de passe.
Dans la configuration de la sécurité, remplaçons l’authentification HTTP Basic par
une authentification par formulaire :
security:
encoders:
Symfony\Component\Security\Core\User\User: plaintext

providers:
mes_utilisateurs:
memory:
users:
user: { password: userpass, roles: [ 'ROLE_USER' ] }

firewalls:
mon_pare_feu:
pattern: ^/
anonymous: ~
form_login: ~

Une authentification par formulaire requiert la création de deux routes pour les
chemins (paths) /login et /login_check. La première correspondra à la page avec le
formulaire de connexion, la seconde est automatiquement interceptée par le pare-feu.
Ajoutons d’abord la route pour le chemin /login_check dans le
fichier config/routes.yaml, fichier principal de configuration des routes :
# config/routes.yaml

login_check:
path: /login_check

Cette dernière ne comporte pas d’action et nous la définissons dans le simple but de
pouvoir générer la valeur de l’attribut target du formulaire de connexion (grâce aux
fonctions Twig path() ou url()).
Voici l’action de la page du formulaire d’authentification :
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;

class LoginController extends AbstractController


{
/**
* @Route("/login", name="login")
*/
public function index(AuthenticationUtils $auth): Response
{
// On récupère les éventuelles erreurs d'authentification
précédentes
// pour afficher un message.
$erreur = $auth->getLastAuthenticationError();

return $this->render('login/index.html.twig', [
'erreur' => $erreur
]);
}
}

Le template associé à cette action contient le formulaire de connexion :


{% extends 'base.html.twig' %}

{% block body %}
{% if erreur %}
<div>{{ erreur.message }}</div>
{% endif %}

<form action="{{ path('login_check') }}" method="post">


<label>
Identifiant :
</label>
<input type="text" name="_username" />
<br />

<label>
Mot de passe :
</label>
<input type="password" name="_password" />
<br />

<button type="submit">Connexion</button>
</form>
{% endblock %}

En cas d’erreur d’authentification, le message est en anglais. Pour le traduire,


reportez-vous au chapitre Internationalisation des applications Symfony.
Vous pouvez maintenant vous connecter avec l’utilisateur renseigné dans le fichier de
configuration. Son pseudonyme est user et son mot de passe est userpass.
4. Connexion automatique des utilisateurs
La durée de l’authentification d’un utilisateur est limitée par la durée de vie de la
session.
Pour autoriser un utilisateur à rester authentifié pendant une plus longue période, il
existe la fonctionnalité « se souvenir de moi ». Lors de la connexion au travers d’un
formulaire, l’utilisateur a la possibilité de cocher une case « se souvenir de moi », qui
active un cookie :
security:

# ..

firewalls:
mon_pare_feu:
pattern: ^/
anonymous: ~
form_login: ~
remember_me:
key: "%secret%"
lifetime: 7776000 # 90 jours (en secondes)
path: /
domain: ~

Pour l’exemple ci-dessus, le cookie a une durée de vie de 90 jours.


À l’intérieur de notre formulaire de connexion, il nous faut ajouter un
champ checkbox nommé _remember_me :
<label>
Se souvenir de moi
</label>
<input type="checkbox" name="_remember_me" checked="checked" />

5. Déconnexion des utilisateurs


En rajoutant la directive logout, on peut fournir aux utilisateurs un lien leur
permettant de se déconnecter :
security:
encoders:
Symfony\Component\Security\Core\User\User: plaintext
providers:
mes_utilisateurs:
memory:
users:
user: { password: userpass, roles: [ 'ROLE_USER' ] }

firewalls:
mon_pare_feu:
pattern: ^/
anonymous: ~
form_login: ~
logout: ~

Par défaut, le chemin utilisé est /logout. Vous devez obligatoirement créer une route
pour ce chemin dans le fichier config/routes.yaml :
# config/routes.yaml

# route pour la vérification de l'authentification


login_check:
path: /login_check

# route pour la déconnexion


logout:
path: /logout

Précédent
Présentation et concepts de sécurité
Suivant
Utilisateurs et rôles
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants
 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles

 1. L’utilisateur
 a. Récupérer l’utilisateur courant
 2. Les fournisseurs d’utilisateurs
 a. En mémoire
 b. Fournisseur d’utilisateurs de bases de données
 c. Fournisseur d’utilisateurs personnalisé
 d. Notes additionnelles
 3. Cryptage des mots de passe
 a. Encodeurs
 b. Le salage
 c. Crypter un mot de passe
 4. Les rôles

Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Utilisateurs et rôles
1. L’utilisateur
Avec Symfony, l’objet représentant un utilisateur implémente obligatoirement
l’interface Symfony\Component\Security\Core\User\UserInterface. Elle garantit
les méthodes suivantes :
 getUsername(), qui retourne le nom d’utilisateur.
 getPassword(), qui retourne le mot de passe de l’utilisateur.
 getSalt(), qui retourne la valeur du salage (salt) du mot de passe ; nous
reviendrons plus loin dans ce chapitre sur ce concept, avec les encodeurs.
 getRoles(), qui retourne les rôles de l’utilisateur. Ces rôles ont un impact sur le
niveau d’autorisation de chaque membre. En pratique, ils permettent, par
exemple, de dire si le membre est un administrateur, un modérateur ou un simple
utilisateur.
 eraseCredentials(), qui est utilisée en interne par le framework. Cette
méthode efface les informations sensibles (s’il en existe) contenues dans l’objet
utilisateur. Elle est utile si, par exemple, à un moment donné, le mot de passe est
stocké en clair dans l’objet.
Selon le fournisseur d’utilisateurs que vous choisissez ou créez, les objets représentant
les utilisateurs peuvent implémenter une classe plus complète que
UserInterface : Symfony\Component\Security\Core\User\AdvancedUserInterface.
Le but de cette dernière est de prendre en charge les fonctionnalités de suspension et
d’expiration de comptes utilisateur.
a. Récupérer l’utilisateur courant
Pour récupérer l’utilisateur actuellement authentifié, vous disposez du
raccourci getUser() dans vos contrôleurs :
namespace App\Controller;

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

class DefaultController extends AbstractController


{
/**
* @Route("/")
*/
public function index()
{
$utilisateur = $this->getUser();
// ...
}
}

Si l’utilisateur est authentifié, la méthode retourne un objet implémentant


l’interface Symfony\Component\Security\Core\User\UserInterface.
Depuis la vue
Vous avez également accès à l’utilisateur courant depuis vos templates Twig :
{# affiche l'objet utilisateur pour un aperçu de son contenu #}
{{ dump(app.user) }}

{# affiche le nom d'utilisateur #}


{{ app.user.username }}

2. Les fournisseurs d’utilisateurs


Avec les différentes méthodes d’authentification que nous venons d’évoquer,
l’authentification d’un utilisateur nécessite la soumission d’informations sur son
identité (dans la majorité des cas, il s’agit d’un nom d’utilisateur et d’un mot de
passe).
Lors de la demande d’authentification, Symfony vérifie si les données soumises par le
client sont valides, en s’appuyant dans un premier temps sur un (ou plusieurs)
fournisseur d’utilisateurs (user provider).
Un fournisseur d’utilisateurs est une « source » capable de renvoyer les utilisateurs de
l’application.
a. En mémoire
Jusqu’à présent, nous avons utilisé le fournisseur d’utilisateurs memory :
# config/packages/security.yaml

security:

# ...

providers:
mes_utilisateurs:
memory:
users:
bob: { password: pa$S, roles: [ 'ROLE_USER' ] }
sarah: { password: 4Dm1nP4$s , roles: [ 'ROLE_USER',
'ROLE_ADMIN' ] }

# ...

Avec cette configuration, nous définissons deux utilisateurs (bob et sarah) ayant
chacun un mot de passe ainsi qu’un ou plusieurs rôles.
Le fournisseur d’utilisateurs memory consiste donc à définir des comptes
utilisateur directement dans le fichier security.yaml. Il s’avère pratique uniquement si
votre pare-feu est à destination d’un groupe restreint et figé d’utilisateurs. Il convient
par exemple parfaitement à une zone d’administration à laquelle vous seul avez accès.
b. Fournisseur d’utilisateurs de bases de données
Le fournisseur d’utilisateurs que nous allons décrire ici persiste les utilisateurs dans
une base de données. Il répond à la demande de la majorité des sites web, où les
utilisateurs s’enregistrent d’eux-mêmes au travers d’un formulaire d’inscription, leurs
données étant enregistrées en base.
Avant de configurer ce fournisseur d’utilisateurs, il convient de créer une
entité Utilisateur :
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;


use Symfony\Component\Security\Core\User\UserInterface;

/**
* @ORM\Entity()
*/
class Utilisateur implements UserInterface
{
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;

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

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

public function getId()


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

public function getPassword()


{
return $this->password;
}

public function getRoles()


{
return array('ROLE_USER');
}

public function getSalt()


{
return null;
}

public function eraseCredentials() {}

// mutateurs...
}

Comme nous l’avons expliqué précédemment, les objets utilisateurs utilisés par le
système de sécurité de Symfony implémentent l’interface Symfony\Component\
Security\Core\User\UserInterface. C’est donc le cas ici pour votre classe d’entité
Utilisateur car elle est destinée à représenter un utilisateur.
Par souci de simplicité, cette entité est volontairement minimaliste. Elle stocke le mot
de passe en clair, et elle ne doit en aucun cas être utilisée en production. Elle a
uniquement valeur d’exemple.
En exécutant la commande php bin/console doctrine:schema:update --force, une
table Utilisateur sera créée. Vous pouvez d’ores et déjà y insérer quelques utilisateurs
avec leur mot de passe (en clair).
Pour créer le fournisseur d’utilisateurs approprié, il faut utiliser la valeur entity :
security
encoders:
App\Entity\Utilisateur: plaintext

providers:
ma_bdd:
entity:
class: App\Entity\Utilisateur
property: username

firewalls:
mon_pare_feu:
pattern: ^/
anonymous: ~
form_login: ~
logout: ~

Le fournisseur d’utilisateurs entity a besoin de deux paramètres pour fonctionner : la


classe (class) de l’entité utilisateur et sa propriété contenant le nom d’utilisateur
(property).
En accédant à la page contenant le formulaire de connexion (path /login), vous
pouvez vous identifier avec les utilisateurs présents en base de données.
c. Fournisseur d’utilisateurs personnalisé
Symfony est très flexible et vous autorise à enregistrer vos propres fournisseurs
d’utilisateurs. Cette technique s’adresse aux personnes ayant un système
d’authentification particulier, via un service web distant par exemple.
Un autre exemple nécessitant un fournisseur d’utilisateurs personnalisé serait une
application enregistrant ses utilisateurs en base de données, mais n’utilisant pas
Doctrine.
Dans cette situation, on pourrait tout à fait imaginer un fournisseur d’utilisateurs
personnalisé se servant de PDO :
namespace App\Security;

use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\
UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\
UnsupportedUserException;
class UtilisateurProvider implements UserProviderInterface
{
private $pdo;

public function __construct(\PDO $pdo)


{
$this->pdo = $pdo;
}

public function loadUserByUsername($username)


{
$stmt = $this->pdo->prepare(
'SELECT password FROM Utilisateur WHERE username = ?'
);

$stmt->execute(array($username));

if ($password = $stmt->fetchColumn()) {
return new Utilisateur($username, $password);
}

throw new UsernameNotFoundException(


'Utilisateur introuvable.'
);
}

public function refreshUser(UserInterface $user)


{
if (!$user instanceof Utilisateur) {
throw new UnsupportedUserException(
'Utilisateur non supporté.'
);
}

return $this->loadUserByUsername($user->getUsername());
}

public function supportsClass($class)


{
return 'App\Security\Utilisateur' === $class;
}
}

Un fournisseur d’utilisateurs doit implémenter l’interface Symfony\Component\


Security\Core\User\UserProviderInterface. La méthode principale de cette
interface est loadUserByUsername(). Elle est chargée de récupérer un objet
utilisateur en fonction d’un nom d’utilisateur. Si le nom d’utilisateur n’existe pas, elle
doit retourner une exception.
Voici le contenu de la classe Utilisateur que nous utilisons :
namespace App\Security;

use Symfony\Component\Security\Core\User\UserInterface;

class Utilisateur implements UserInterface


{
private $username;
private $password;

public function __construct($username, $password)


{
$this->username = $username;
$this->password = $password;
}

public function getUsername()


{
return $this->username;
}

public function getPassword()


{
return $this->password;
}
public function getRoles()
{
return array('ROLE_USER');
}

public function getSalt()


{
return null;
}

public function eraseCredentials() {}


}

Notre fournisseur d’utilisateurs doit être configuré en tant que service dans le
fichier config/services.yaml :
# config/services.yaml

# ...

services:
mon_pdo:
class: PDO
arguments:
- mysql:host=localhost;dbname=monjournal
- monjournaluser
- monjournalpass
mon_fournisseur_pdo:
class: App\Security\UtilisateurProvider
arguments: [@mon_pdo]

Ce service de fournisseur d’utilisateurs dépend d’un service PDO assurant la


connexion avec la base de données.
Enfin, la dernière étape consiste à faire connaître le fournisseur d’utilisateurs au
SecurityBundle :
# config/packages/security.yml

security:
encoders:
App\Security\Utilisateur: plaintext

providers:
ma_bdd:
id: mon_fournisseur_pdo

firewalls:
mon_pare_feu:
pattern: ^/
anonymous: ~
form_login: ~
logout: ~

d. Notes additionnelles
FOSUserBundle
Pour la gestion de vos utilisateurs en base de données (inscription, confirmation par e-
mail, récupération du mot de passe, etc.), nous vous conseillons l’incontournable
FOSUserBundle. C’est le plus populaire des bundles Symfony.
Une annexe est consacrée à l’installation et à la configuration de ce bundle, en fin
d’ouvrage.
OAuth, annuaire LDAP, etc.
Avant de vous lancer dans la création d’un fournisseur d’utilisateurs personnalisé,
pensez à vérifier qu’un bundle tiers ne réponde pas déjà à votre besoin. Ainsi, le
bundle HWIOAuthBundle peut être utilisé pour une authentification OAuth (pour
Facebook et Twitter par exemple) tandis que le bundle IMAGLdapBundle est capable
d’authentifier les utilisateurs d’un annuaire LDAP.
HTTP et les formulaires de connexion
Le protocole HTTP étant défini comme stateless (sans état), chaque requête y est
traitée indépendamment, et non dans une continuité avec les précédentes requêtes de
l’utilisateur.
Avec une authentification HTTP Basic, les informations d’identification sont
soumises à chaque requête par le navigateur. Ce n’est pas le cas avec la traditionnelle
connexion par formulaire de connexion. Pour cette dernière, le framework utilise
les cookies, qui servent à conserver l’authentification pendant toute la navigation de
l’utilisateur.
L’utilisation de cookies crée certaines contraintes pour vos classes utilisateurs. Vous
devez ainsi veiller à ce qu’elles soient sérialisables.
Avec Doctrine, une entité désérialisée est particulière car elle n’est plus suivie par
l’EntityManager. C’est notamment pour cette raison que vous remarquerez qu’à
chaque changement de page, Doctrine effectue une requête pour rafraîchir l’utilisateur
courant.

3. Cryptage des mots de passe


Pour simplifier les explications et nous concentrer sur les fonctionnalités que nous
abordions, nous avons jusqu’à présent, au fil de nos exemples, stocké les mots de
passe en clair en base de données.
Avec des mots de passe en clair, si la sécurité de votre base de données venait à être
compromise, en plus de pouvoir se connecter sur votre site avec le compte de
n’importe lequel de vos membres, des pirates informatiques pourraient aussi usurper
l’identité de certains membres sur d’autres sites (réseaux sociaux, boîtes e-mail, etc.)
si, par souci de simplicité, ces derniers utilisent le même mot de passe partout.
Stocker des mots de passe en clair peut donc potentiellement nuire à votre site web, à
vos utilisateurs, mais aussi à d’autres sites web. C’est un acte irresponsable qu’il faut
à tout prix éviter.
a. Encodeurs
Les encodeurs de mots de passe pour vos classes utilisateurs sont définis sous la
section encoders :
security:
encoders:
App\Entity\Utilisateur:
algorithm: sha256
iterations: 1
encode_as_base64: false

# ...

La directive algorithm contient l’algorithme à utiliser pour crypter le mot de passe.


Les valeurs acceptées sont celles retournées par la fonction PHP hash_algos().
Le nœud de configuration iterations définit le nombre de cryptages à appliquer. S’il
vaut 2 par exemple, le mot de passe sera crypté une première fois, puis, sa valeur
cryptée sera encore une fois cryptée.
Enfin, encode_as_base_64 indique si la valeur cryptée du mot de passe doit être
encodée en Base64.
b. Le salage
Bien que stocker un mot de passe crypté soit déjà beaucoup plus sûr que de le stocker
en clair, ce n’est pas considéré comme une mesure de sécurité suffisante.
Les rainbow tables (tables arc-en-ciel) sont des bases de données conçues dans le but
de décrypter des mots de passe encodés. Elles contiennent, pour un algorithme donné,
des millions d’entrées avec des paires mot de passe en clair/ mot de passe crypté. Ces
tables peuvent être utilisées pour décrypter certains mots de passe.
Il est recommandé de concaténer une valeur de salage (en anglais, salt ou hash) au
mot de passe avant de le crypter. Le salage doit être dynamique et unique pour chaque
utilisateur. Il est retourné par la méthode getSalt() de l’objet utilisateur :
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;


use Symfony\Component\Security\Core\User\UserInterface;

/**
* @ORM\Entity()
*/
class Utilisateur implements UserInterface
{
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;

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

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

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

public function __construct()


{
$this->salt = md5(uniqid(rand(), true));
}

public function getId()


{
return $this->id;
}

public function getUsername()


{
return $this->username;
}

public function getSalt()


{
return $this->salt;
}

public function getPassword()


{
return $this->password;
}

public function getRoles()


{
return array('ROLE_USER');
}

public function eraseCredentials() {}

public function setPassword($password)


{
$this->password = $password;
}

// ...
}

Ici, à la création d’une nouvelle entité Utilisateur, nous générons une valeur de salage
aléatoire avec md5(uniqid(rand(), true)).
c. Crypter un mot de passe
Voici comment crypter un mot de passe depuis un contrôleur :
$factory = $this->get('security.encoder_factory');

$utilisateur = new \App\Entity\Utilisateur();

$encodeur = $factory->getEncoder($user);
$password = $encodeur->encodePassword(
'p4s$',
$utilisateur->getSalt()
);

$utilisateur->setPassword($password);

Le service security.encoder_factory retourne l’encodeur pour un objet utilisateur


donné. Il faut ensuite invoquer la méthode encodePassword() de l’encodeur ; le
premier argument est le mot de passe à crypter, le second est la valeur du salage.
Enfin, le mot de passe crypté peut être injecté dans l’objet utilisateur.

4. Les rôles
Les rôles définissent le niveau d’autorisation de chaque membre. Des rôles
couramment utilisés sont par exemple administrateur, modérateur, éditeur ou
simple utilisateur. L’administrateur a tous les droits, le modérateur peut supprimer
des messages injurieux et bannir les membres les propageant, l’éditeur peut publier
des articles sur le site, et enfin, l’utilisateur est le membre « normal » de votre site.
La méthode getRoles() de vos objets utilisateurs doit retourner un tableau de rôles,
chaque rôle étant une chaîne de caractères qui commence par ROLE_. La seconde
partie de cette chaîne de caractères est libre.
Même si la méthode getRoles() retourne un tableau, ce dernier peut tout à fait ne
contenir qu’un seul rôle.
Voici un exemple d’entité Utilisateur gérant deux rôles ; le rôle de base (nous
choisissons ROLE_USER) et le rôle administrateur (que nous
appelons ROLE_ADMIN) :
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;


use Symfony\Component\Security\Core\User\UserInterface;

/**
* @ORM\Entity()
*/
class Utilisateur implements UserInterface
{
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;

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

/**
* @ORM\Column(type="string", length=32)
*/
private $salt;

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

/**
* @ORM\Column(name="is_admin", type="boolean")
*/
private $isAdmin;

public function __construct()


{
$this->salt = md5(uniqid(rand(), true));
$this->isAdmin = false;
}

public function getId()


{
return $this->id;
}

public function getUsername()


{
return $this->username;
}

public function getSalt()


{
return $this->salt;
}

public function getPassword()


{
return $this->password;
}

public function getRoles()


{
$roles = array('ROLE_USER');

if ($this->isAdmin) {
$roles[] = 'ROLE_ADMIN';
}
return $roles;
}

public function isAdmin()


{
return $this->isAdmin;
}

public function eraseCredentials() {}

// mutateurs

// ...
}

Pour cette entité, nous avons une propriété isAdmin mappée par Doctrine. Elle
accepte une valeur booléenne indiquant si l’utilisateur est un administrateur ou non ;
cette valeur est persistée en base de données. La méthode getRoles() ajoute le rôle
d’administrateur en fonction de cette propriété. En clair, si vous utilisez une base de
données MySQL, il vous suffit de passer la valeur de cette colonne à 1 pour qu’un
utilisateur obtienne le rôle d’administrateur.
Hiérarchie de rôles
Les rôles correspondent rarement à des droits isolés les uns des autres : il existe la
plupart du temps une hiérarchie. L’administrateur possède, en plus de ses propres
autorisations, les mêmes autorisations que le modérateur ou l’éditeur, ces derniers
étant quant à eux aussi des utilisateurs de base, avec certes quelques autorisations
supplémentaires.
Plutôt que de devoir retourner l’ensemble des rôles pour chaque utilisateur, vous
pouvez facilement définir une hiérarchie de rôles :
security:
role_hierarchy:
ROLE_MODERATEUR: ROLE_USER
ROLE_EDITEUR: ROLE_USER
ROLE_ADMIN: [ ROLE_MODERATEUR, ROLE_EDITEUR ]

# ...

Ainsi, le simple fait de posséder le rôle ROLE_ADMIN implique tous les autres
rôles. Cela rend la gestion des rôles plus simple et vous pouvez dorénavant retourner
un seul rôle depuis votre objet utilisateur. Symfony y associera automatiquement les
rôles inférieurs dans la hiérarchie.
Précédent
Authentification
Suivant
Autorisations
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations

 1. Les rôles, au cœur du processus


 2. Vérifier le rôle de l’utilisateur
 3. Sécuriser une action
 4. Sécuriser une section de l’application
 5. Sécuriser selon d’autres critères
 6. Pour aller plus loin

Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Autorisations
Jusqu’ici, nous avons appris à authentifier des utilisateurs, au travers de différentes
méthodes et de différents fournisseurs d’utilisateurs. Nous connaissons la
représentation d’un utilisateur et nous savons qu’il possède des rôles. Nous allons
maintenant voir comment sécuriser des ressources.

1. Les rôles, au cœur du processus


Le principe de l’autorisation est simple : vos utilisateurs possèdent des rôles et l’accès
à certaines ressources peut être limité à certains rôles. Les rôles sont donc l’élément
central du processus d’autorisation. Avant de continuer, il est nécessaire de vous
présenter de nouveaux rôles.
Si un utilisateur est derrière l’un des pare-feu que vous avez définis
(dans config/packages/security.yaml), il possède obligatoirement au moins un des
rôles suivants :
 IS_AUTHENTICATED_ANONYMOUSLY : l’utilisateur n’est pas authentifié
(il est derrière un pare-feu qui autorise les utilisateurs anonymes).
 IS_AUTHENTICATED_REMEMBERED : l’utilisateur est authentifié grâce à
la fonctionnalité « se souvenir de moi ».
 IS_AUTHENTICATED_FULLY : l’utilisateur s’est authentifié dans la requête
ou session courante.
Vous n’avez pas à gérer ces rôles dans la méthode getRoles() de votre objet utilisateur
car ils sont générés automatiquement par Symfony.

2. Vérifier le rôle de l’utilisateur


Pour sécuriser l’une de vos actions, vous ne devez jamais vous baser sur les rôles
retournés par l’objet utilisateur. Les rôles que nous venons de présenter
(IS_AUTHENTICATED_*) ne sont par exemple pas présents au niveau de l’objet
utilisateur car Symfony utilise en interne un jeton (ou token) pour vérifier une
autorisation.
Voici comment vérifier qu’un rôle donné a été accordé à l’utilisateur courant :
namespace App\Controller;

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

class DefaultController extends AbstractController


{
/**
* @Route("/")
*/
public function index()
{
if ($this->isGranted('ROLE_ADMIN')) {
return new Response('Vous êtes un admin !');
}

if ($this->isGranted('ROLE_USER')) {
return new Response('Bienvenue cher utilisateur.');
}

return new Response('Vous n\'êtes pas authentifié.');


}
}

Ici, nous retournons une réponse différente selon le rôle de l’utilisateur.


Dans les templates
Pour vérifier le rôle d’un utilisateur depuis un template Twig, il faut utiliser la
fonction is_granted() :
{% if is_granted('ROLE_ADMIN') %}
Vous êtes un administrateur.
{% else %}
Vous n'êtes pas un administrateur.
{% endif %}

3. Sécuriser une action


Pour sécuriser une action, il suffit de lancer une exception de type Symfony\
Component\Security\Core\Exception\AccessDeniedException si l’utilisateur n’a
pas le rôle souhaité :
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class DefaultController extends AbstractController
{
/**
* @Route("/page-admin")
*/
public function pageAdmin()
{
if (!$this->isGranted('ROLE_ADMIN')) {
throw new AccessDeniedException();
}

// suite de l'action réservée aux admins


}
}

L’exception Symfony\Component\Security\Core\Exception\
AccessDeniedException diffère de Symfony\Component\HttpKernel\Exception\
AccessDeniedHttpException par le fait que l’exception du
composant HttpKernel est définitive : la réponse renvoyée sera obligatoirement de
statut 403.
Avec l’exception du composant Security, une réponse de statut 403 n’est pas
obligatoirement renvoyée. Si l’utilisateur n’est pas authentifié (anonyme), son
authentification sera demandée et il sera par exemple redirigé vers un formulaire de
connexion.

4. Sécuriser une section de l’application


Si vous avez une zone d’administration, sécuriser chacune de ses actions serait trop
fastidieux. Il est possible de sécuriser de plus grands ensembles grâce à la
directive access_control du fichier de configuration de la sécurité.
security:

# ...

access_control:
- { path: ^/admin, roles: ROLE_ADMIN }

Cette directive accepte un ensemble de règles de contrôle d’accès.


L’option path contient l’expression régulière pour le path de la requête. Ici, s’il
commence par /admin, l’utilisateur devra posséder le rôle ROLE_ADMIN.
Avec cette règle de contrôle d’accès, toutes les routes préfixées
par /admin seront uniquement accessibles aux administrateurs.

5. Sécuriser selon d’autres critères


En plus du chemin (path), il existe d’autres moyens de configurer une règle de
contrôle d’accès : par adresse IP, par protocole ou même par nom d’hôte.
Nom d’hôte
Grâce à ces autres critères, vous pouvez par exemple utiliser un sous-domaine pour
votre zone d’administration :
security:

# ...

access_control:
- { host: ^admin\.example\.com$, roles: ROLE_ADMIN }

Avec cette configuration, le sous-domaine admin du site example.com n’est


accessible qu’aux administrateurs.
Tout comme pour le routage par sous-domaine, ici vous devrez typiquement utiliser
un paramètre de container pour différencier vos noms de domaine de développement
et de production.
Protocole HTTPS
Vous pouvez forcer le protocole HTTPS pour certaines sections d’un site comme une
zone de paiement :
security:

# ...

access_control:
- { path: ^/paiement, roles: IS_AUTHENTICATED_ANONYMOUSLY,
requires_channel: https }

Si un utilisateur essaye d’accéder à la section de paiement avec le protocole HTTP, il


subira une redirection avec la même URL, mais sous protocole HTTPS.
Pour utiliser ce protocole, vous devrez posséder un certificat SSL et votre serveur web
doit être configuré en conséquence.
Adresse IP ou méthode HTTP
Dans ce dernier exemple, nous allons combiner plusieurs critères. Imaginons que vous
ayez une API HTTP acceptant des requêtes de type POST depuis l’un de vos
partenaires, dont vous connaissez l’adresse IP.
Vous pourriez ajouter cette règle :
security:

# ...

access_control:
- { path: ^/api, ip: 11.22.33.44, methods: [POST], roles:
IS_AUTHENTICATED_ANONYMOUSLY }

Ici, la première règle de contrôle d’accès est valide pour votre partenaire, elle autorise
les utilisateurs anonymes. Cependant, si toute autre personne tente de requêter votre
API, comme elle ne répond pas aux critères de la règle de contrôle d’accès, elle ne
verra pas son accès être refusé : il y a une règle qui autorise l’accès à votre partenaire,
mais il n’y a pas de règle qui refuse l’accès aux autres.
Pour éviter cela, vous pouvez utiliser la technique suivante :
security:

# ...

access_control:
- { path: ^/api, ip: 11.22.33.44, methods: [POST], roles:
IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: ROLE_FORBIDDEN }

Ici, la deuxième règle de contrôle d’accès est un subterfuge : si l’utilisateur ne


correspond pas aux critères de la première règle, la deuxième sera utilisée. Elle
contient un rôle inexistant (vous pouvez utiliser n’importe quelle valeur, dans la
mesure où vous ne l’assignez à aucun de vos utilisateurs). Ainsi, l’utilisateur n’est pas
autorisé à accéder à l’API car il ne possède pas ce rôle « fantôme ».

6. Pour aller plus loin


Si vous souhaitez découvrir des fonctionnalités plus poussées, nous vous
renvoyons vers la documentation Symfony concernant le
SecurityBundle : https://symfony.com/doc/4.4/security.html
La section « Learn More » en fin de cette page apporte notamment une liste de
conseils et solutions pratiques pour la mise en œuvre de la sécurité dans un contexte
entreprise. Cela couvre, par exemple, l’usage d’un annuaire LDAP ou encore la prise
en charge d’un système d’ACL (listes de contrôle d’accès).
Précédent
Utilisateurs et rôles
Suivant
Quiz
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel

 1. Les tests : un indispensable pour la qualité logicielle


 2. Les différentes catégories de tests
 a. Les tests unitaires
 b. Les tests d’intégration
 c. Les tests fonctionnels
 3. Analogie
 4. L’approche des tests en PHP

Les tests unitaires avec PHPUnit


Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Introduction au test logiciel


1. Les tests : un indispensable pour la qualité
logicielle
Les tests logiciels garantissent la robustesse et la qualité d’une application en partant
d’un principe simple : plus un problème est identifié tôt, moins il coûte cher à réparer.
Si une application ne dispose pas d’une suite de tests, il est fort probable qu’au cours
de son évolution, au fur et à mesure des corrections de bogues et de l’ajout de
nouvelles fonctionnalités, de nouveaux bogues apparaissent sur des fonctionnalités
déjà existantes. Cela s’appelle la « régression », et les tests logiciels permettent de
s’assurer de la non-régression de l’application.
Au-delà de ce concept de non-régression, il est indispensable de pouvoir valider
toutes les étapes de construction d’une application, des premières lignes de code en
passant par l’assemblage des fonctionnalités en classes et composants et, enfin, de
pouvoir tester l’interface utilisateur de l’application.
Durant toutes ces phases de construction logicielle, différentes familles (ou
catégories) de tests entrent en œuvre.

2. Les différentes catégories de tests


Dans un processus de construction logiciel, plusieurs catégories de tests vont rentrer
en action, chacune à une étape plus ou moins avancée du projet. Il est réducteur de
croire que seuls les tests intervenant à la fin du développement d’une application sont
importants. En effet, chaque étape devant être validée, tous les tests sont importants.
En attendant la fin du développement pour tester votre application, vous risquez de
vous trouver face à une montagne de défauts à corriger, ce qui humainement est assez
décourageant, sans parler du fait que, pris par le temps, vous risquez de ne pas
pouvoir tous les corriger.
Les tests qui intéressent le développeur sont les suivants :
 Les tests unitaires
 Les tests d’intégration
 Les tests fonctionnels
a. Les tests unitaires
Les tests unitaires se focalisent sur la plus petite unité de code testable, c’est-à-dire
une fonction ou une méthode. Ils interviennent donc dès les premiers moments du
développement dès qu’une méthode peut être testée.
Par exemple, si vous développez une calculatrice, vous y implémenterez
probablement une fonction d’addition. Le test unitaire de cette fonction est simple à
écrire : si l’on y entre les valeurs 2 et 3, il suffit de vérifier qu’il en sort bien 5.
Ils sont assez simples à mettre en œuvre s’ils sont outillés, comme nous le
verrons plus loin dans ce chapitre.
b. Les tests d’intégration
Les tests d’intégration vont plus loin dans le processus de test que les tests
unitaires. Ils se focalisent sur l’assemblage des fonctionnalités, là où les tests unitaires
se limitent à des fonctionnalités unitaires sorties de leur contexte d’usage.
Ils utilisent les mêmes outils que les tests unitaires mais ils interviennent un peu plus
tard dans le processus de construction logicielle puisqu’ils nécessitent que des
composants soient assemblés.
c. Les tests fonctionnels
Enfin, les tests fonctionnels se concentrent sur le fonctionnement de l’application d’un
point de vue de l’utilisateur. Ils simulent donc des actions utilisateur sur l’interface de
l’application et évaluent le résultat attendu. Ils interviennent à un moment où
l’application est suffisamment avancée pour qu’ils puissent être menés.
De la même manière, les tests fonctionnels nécessitent des outils pour être menés à
bien.

3. Analogie
Un test est donc la vérification que la réaction entraînée par une action
donnée correspond à nos attentes. La différence entre un test unitaire ou
d’intégration et un test fonctionnel est que les premiers s’appliquent à une partie
précise et isolée de l’application, tandis que le second s’applique à un plus large
ensemble, généralement une fonctionnalité.
L’automobile
Si ces concepts de tests unitaires et fonctionnels vous semblent flous, rassurez-vous,
nous les utilisons presque tous les jours, à notre insu.
Imaginez qu’un matin d’hiver, en montant à bord de votre véhicule, vous vous rendiez
compte que vos phares éclairent moins bien que d’habitude. Très vite, en sortant de
votre voiture et en vous approchant de l’avant, l’explication vous saute aux yeux : un
phare ne s’allume plus.
Vous emmenez votre automobile à un garage, et le mécanicien retire l’ampoule du
bloc optique, mesure sa résistance électrique grâce à un ohmmètre, puis vous informe
qu’elle est défaillante : elle doit être changée.
Le matin, vous avez remarqué une anomalie sur votre véhicule, vous avez donc
effectué un test fonctionnel : en actionnant vos feux de croisement depuis l’intérieur
de la voiture, les deux phares devaient s’allumer, mais cela n’a pas été le cas, le test a
donc échoué.
Le mécanicien a quant à lui réalisé un test unitaire. Ce dernier était isolé sur une pièce
précise, l’ampoule, et a également échoué. Nous avons pu en déduire la correction à
appliquer sur le véhicule pour régler le problème : changer l’ampoule.
Ici, le problème était léger et une panne plus grave aurait pu, par exemple,
empêcher le démarrage de l’automobile. C’est pour prévenir ces grosses pannes
désagréables que votre véhicule doit régulièrement subir un contrôle technique. Le
contrôle technique contient une multitude de tests fonctionnels et unitaires sur les
pièces de votre voiture. Ainsi, les problèmes sont détectés dans un garage et non en
pleine nuit au milieu d’une autoroute.

4. L’approche des tests en PHP


Notre parallèle avec la mécanique s’applique également à vos applications PHP. Pour
éviter que des problèmes surviennent en production et soient portés à votre
connaissance par des utilisateurs mécontents, vous aurez préparé une batterie de tests
unitaires et fonctionnels que vous exécuterez régulièrement, à la manière d’un
contrôle technique.
Symfony ne dispose pas de son propre système de tests, mais se base sur un
framework existant, le très populaire PHPUnit.
Précédent
Quiz
Suivant
Les tests unitaires avec PHPUnit
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit

 1. Mise en place des tests


 2. Règle d’écriture des tests
 3. Exécuter les tests
 a. Exécuter une partie des tests

Les tests fonctionnels


Quiz
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes
Les tests unitaires avec PHPUnit
1. Mise en place des tests
Pour pouvoir mettre en place les tests dans votre application Symfony, il est tout
d’abord nécessaire d’installer PHPUnit. PHPUnit s’installe via une recette Flex grâce
à la commande suivante :
composer require --dev phpunit/phpunit symfony/test-pack

Une fois l’installation terminée, vous devriez pouvoir invoquer PHPUnit avec la
commande suivante passée depuis votre répertoire de projet :
php ./vendor/bin/phpunit

Par défaut, cette commande exécute tous les tests présents dans le répertoire tests/ ;
ici, aucun test n’est exécuté, comme l’indique la sortie de la commande.

2. Règle d’écriture des tests


Tous les tests unitaires de vos fonctionnalités doivent être stockés dans le
répertoire tests/ de votre application Symfony. En effet, ce répertoire est celui qui est
associé à l’environnement test du framework pouvant ainsi s’appuyer sur une
configuration spécifique définie dans un fichier .env.test à la racine de votre projet.
Ce fichier pourrait notamment contenir une URL de connexion à une base de données
dédiée aux tests.
Prenons comme exemple la classe suivante :
namespace App\Model;

class Utilisateur
{
private $prenom;
private $nom;

public function __construct($prenom, $nom)


{
$this->prenom = $prenom;
$this->nom = $nom;
}

public function getNomComplet()


{
return $this->prenom . ' ' . $this->nom;
}
}

Cette classe Utilisateur est assez simple. Lors de son instanciation, elle prend en
argument un prénom et un nom. Sa méthode getNomComplet() retourne le nom
complet de l’utilisateur.
Pour tester cette méthode getNomComplet(), nous allons créer un fichier de tests
pour la classe Utilisateur dans le dossier tests/ de l’application. Ce dossier tests/ doit
être le reflet de la structure du dossier src/ et chacun des noms de ses fichiers de tests
doit être suffixé de Test.
Ainsi, les tests de la classe App\Model\Utilisateur, déclarée dans le
fichier src/Model/Utilisateur.php, doivent être dans le
fichier tests/Model/UtilisateurTest.php, dont voici quel pourrait être le contenu :
namespace App\Tests\Model;

use App\Model\Utilisateur;

class UtilisateurTest extends \PHPUnit_Framework_TestCase


{
public function testGetNomComplet()
{
$utilisateur = new Utilisateur('Jean', 'Valjean');

$nomComplet = $utilisateur->getNomComplet();

$this->assertEquals('Jean Valjean', $nomComplet);


}
}

Cette classe comporte un seul test, qui vérifie le retour de la


méthode getNomComplet().
Pour vos tests unitaires, il n’y a pas de fonctionnalités spécifiques à Symfony. Si vous
n’êtes pas familier de PHPUnit, veuillez-vous reporter à sa documentation
officielle : https://phpunit.readthedocs.io/en/stable/writing-tests-for-phpunit.html

3. Exécuter les tests


La commande suivante permet de lancer vos tests :
php ./vendor/bin/phpunit

Si vos tests réussissent, la sortie contient le message « OK ». Dans le cas contraire,


vous avez des messages d’erreurs vous indiquant les tests qui ont échoué.
a. Exécuter une partie des tests
Si votre application comporte beaucoup de tests, les lancer tous à chaque fois pourrait
être assez long. Si vous travaillez uniquement sur une partie de votre site, vous
pouvez restreindre l’exécution à un certain sous-répertoire ou fichier :
php ./vendor/bin/phpunit tests/Model/UtilisateurTest.php

Tests d’un fichier


php ./vendor/bin/phpunit tests/Model/

Tests d’un dossier


Précédent
Introduction au test logiciel
Suivant
Les tests fonctionnels
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels

 1. Différence par rapport aux tests unitaires et d’intégration


 2. Tester une action
 3. Les objets pour l’écriture des test
 a. L’objet Client
 b. L’objet Crawler
 4. Soumettre un formulaire
 5. Pour aller plus loin

Quiz
Journalisation et surveillance avec Symfony
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Les tests fonctionnels


1. Différence par rapport aux tests unitaires et
d’intégration
Contrairement aux tests unitaires et d’intégration, qui sont destinés à tester les
résultats des invocations des différentes méthodes de vos classes, les tests
fonctionnels vérifient des comportements de l’application.
Le test fonctionnel le plus courant est le test de vos contrôleurs. Vos actions étant
dépendantes d’un certain contexte HTTP, il ne s’agit pas ici de tester directement le
retour des méthodes de vos contrôleurs, mais de simuler des requêtes HTTP et
d’analyser les réponses retournées.
En clair, les tests fonctionnels reproduisent les conditions d’utilisation réelles de votre
application. D’un certain point de vue, ils peuvent être considérés comme étant plus
complexes que les tests unitaires en ce qu’ils nécessitent la création d’un certain
contexte.

2. Tester une action


Introduisons les tests fonctionnels au travers d’une action basique :
namespace App\Controller;

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

class DefaultController extends AbstractController


{
/**
* @Route("/bonjour/{nom}")
*/
public function bonjour($nom)
{
return new Response('Bonjour ' . $nom . '!');
}
}

Pour cette action, il serait légitime de penser à la classe de tests suivante :


namespace App\Tests\Controller;

use App\Controller\DefaultController;

class DefaultControllerTest extends \PHPUnit_Framework_TestCase


{
public function testBonjour()
{
$controleur = new DefaultController;

$reponse = $controleur->bonjourAction('Amadou');

$this->assertEquals(
'Bonjour Amadou !',
$reponse->getContent()
);
}
}

À première vue, ce test est correct, mais même si PHPUnit le marquait comme valide,
il est en réalité inapproprié.

3. Les objets pour l’écriture des test


Ici, on ne souhaite pas tester le retour d’une méthode, mais le résultat du
traitement d’une requête HTTP, à savoir une réponse. Le contrôleur peut
potentiellement utiliser des services, et ses actions peuvent également retourner des
tableaux et non des réponses (annotation @Template). Toutes ces contraintes font du
contrôleur une classe ne pouvant être traitée comme une classe PHP classique, car elle
est fortement dépendante du contexte.
a. L’objet Client
Voici comment l’action bonjour() devrait être testée :
namespace App\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class DefaultControllerTest extends WebTestCase


{
public function testBonjour()
{
$client = static::createClient();

$client->request('GET', '/bonjour/Amadou');

$this->assertEquals(
'Bonjour Amadou !',
$client->getResponse()->getContent()
);
}
}

Nous ne testons plus le retour d’une méthode, mais le comportement de


l’application. La classe n’étend plus PHPUnit_Framework_TestCase, mais une
classe intermédiaire : Symfony\Bundle\FrameworkBundle\Test\WebTestCase.
Cette dernière possède la très utile méthode statique createClient(), qui retourne un
objet « client », une sorte de navigateur virtuel.
Dans notre test fonctionnel, nous demandons au client, via sa méthode request(), de
simuler une requête HTTP de méthode GET sur le path /bonjour/Amadou.
La méthode getResponse() du client retourne l’objet réponse renvoyé
par l’application, à savoir une instance de la classe Symfony\Component\
HttpFoundation\Response. Nous pouvons ainsi effectuer très facilement une
assertion sur le contenu de la réponse.
Au-delà du contenu de la réponse, vous pouvez tester une multitude
d’informations sur cette dernière, comme son code de statut. Si vous utilisez
l’annotation @Method pour, par exemple, n’accepter que les méthodes GET sur
votre action, vous pourriez définir le test fonctionnel suivant :
namespace Eni\DemoBundle\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class DefaultControllerTest extends WebTestCase


{
public function testBonjour()
{
$client = static::createClient();

$client->request('GET', '/bonjour/Amadou');

$this->assertEquals(
'Bonjour Amadou !',
$client->getResponse()->getContent()
);

$client->request('POST', '/bonjour/Amadou');

$this->assertTrue(
$client->getResponse()->isClientError()
);

// pour un test plus précis, vous pouvez vérifier


// le code exact de la réponse
// $this->assertEquals(
// 405,
// $client->getResponse()->getStatusCode()
// );
}
}

Ce dernier, en plus de vérifier le bon fonctionnement de l’action, s’assure que les


requêtes de méthode POST sont refusées.
Méthodes utiles de l’objet client :
b.
$client- Récupère le Service Container.
>getContainer()

$client->back() Permettent de naviguer dans


l’historique du client (pages
$client->forward() précédente et suivante, rechargement
$client->reload() de page et suppression de
l’historique).
$client->restart()

$client- Récupèrent les requêtes et réponses


>getResponse() HTTP.

$client->getRequest()

$client->insulate() Effectue les prochaines requêtes dans


des processus PHP enfants.

$client->click() et $cl Permettent de cliquer sur un lien ou


ient->submit() soumettre un formulaire présent dans
la réponse. Nous y reviendrons après
avoir présenté l’objet Crawler.

L’objet Crawler
Tester le contenu des réponses de l’application est très utile, mais offre son lot de
limitations. Lors des tests, il est parfois nécessaire d’effectuer des opérations plus
complexes sur les pages HTML, comme la soumission d’un formulaire. Le
framework dispose d’un objet facilitant l’extraction d’informations et la navigation
dans vos réponses HTML : le Crawler.
Un objet Crawler est retourné à chaque fois que vous effectuez une requête avec le
Client :
namespace App\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class DefaultControllerTest extends WebTestCase


{
public function testMaPageHtml()
{
$client = static::createClient();

$crawler = $client->request('GET', '/ma-page.html');


// ...
}
}

L’API de cet objet a été inspirée par le framework JavaScript jQuery :


$input = $crawler
->filter('form#mon_formulaire input')
->first()
;

La méthode filter() du Crawler accepte un sélecteur CSS et, ici, nous récupérons le
premier élément input du formulaire ayant comme identifiant mon_formulaire.
Un exemple d’utilisation concret du Crawler pourrait être le test suivant, qui vérifie
que le premier champ du formulaire est de type text :
namespace App\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class DefaultControllerTest extends WebTestCase


{
public function testIndex()
{
$client = static::createClient();

$crawler = $client->request('GET', '/');

$input = $crawler
->filter('form#mon_formulaire input')
->first()
;

$this->assertEquals('text', $input->attr('type'));
}
}

Pour découvrir toutes les possibilités du Crawler, sa page de documentation est d’une
précieuse aide : https://symfony.com/doc/4.4/components/dom_crawler.html

4. Soumettre un formulaire
En combinant les objets Client et Crawler, des fonctionnalités intéressantes
apparaissent. La plus puissante est sans doute la soumission de formulaires :
namespace App\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class DefaultControllerTest extends WebTestCase


{
public function testBonjour()
{
$client = static::createClient();

$crawler = $client->request('GET', '/');

$form = $crawler
->filter('form#mon_formulaire')
->selectButton('Valider')
->form(array(
'mon_champ_1' => 'White',
'mon_champ_2' => 'Pinkman',
))
;

$client->submit($form);
// $client->getResponse(); contient la nouvelle réponse
// $client->getCrawler(); contient le nouveau Crawler
}
}

Cet exemple prépare la soumission d’un formulaire de la page avec des valeurs pour
les champs mon_champ_1 et mon_champ_2. Le Client est ensuite chargé de le
soumettre, et vous avez finalement accès à la réponse de cette nouvelle requête.

5. Pour aller plus loin


Les objets Client et Crawler sont très puissants et permettent de créer une suite de
tests fonctionnels robustes pour votre site.
Néanmoins, ces derniers ne font que « simuler » des interactions entre un client et le
serveur. Si vous souhaitez tester les interactions réelles entre un navigateur et votre
serveur, vous pourriez être intéressé par des outils comme Selenium.
Si vous êtes ouvert à l’idée d’écrire vos tests avant le développement
(méthodologie Test Driven Development, ou TDD), le framework de tests Behat
(https://behat.org) pourrait vous convenir. Il offre un certain niveau d’abstraction (on
parle de Behavior Driven Development, littéralement « développement orienté par le
comportement »), permettant aux personnes sans connaissances techniques (par
exemple, des chefs de produit) de participer à la rédaction des tests avec les
développeurs, de manière à éviter toute incompréhension sur les fonctionnalités à
livrer, parfois présentes avec le « cahier des charges » classique.
Précédent
Les tests unitaires avec PHPUnit
Suivant
Quiz
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog

 1. La journalisation
 2. La librairie Monolog
 3. Le service logger
 4. Le fichier journal
 a. Identifier la cause d’un bogue
 b. Le problème
 5. Les gestionnaires (handlers)
 a. Définir plusieurs gestionnaires
 b. Envoyer des logs par e-mail
 c. Utiliser un tampon (buffer)
 d. Ajouter des informations complémentaires
 6. Les canaux (channels)
 a. Ajouter ses propres canaux
 b. Envoyer un enregistrement sur un canal donné
 c. Configurer les gestionnaires par canaux
 d. Gestion des erreurs 404

Le monitoring avec Prometheus et Grafana


Quiz
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Générer des journaux avec


Monolog
1. La journalisation
La journalisation (logging en anglais) est une technique qui accompagne une
application aussi bien durant la phase de développement qu’après sa mise en
production. Elle consiste à enregistrer dans un journal (généralement matérialisé par
un fichier) des événements (logs) qui sont déclenchés par l’application.
Au cours du chapitre sur le répartiteur d’événements, nous avions déjà parlé des
événements. Bien que le mot soit le même, les événements du répartiteur
d’événements n’ont strictement aucun rapport avec les événements de la
journalisation. Pour éviter toute confusion, nous utiliserons exclusivement la
dénomination anglaise « logs » tout au long de ce chapitre. Le terme français, même
s’il est sémantiquement correct, est de toute façon peu employé par les informaticiens
francophones.
Les logs sont datés et comportent un message de description. Voici à quoi pourrait
ressembler une portion de journal d’un site web lambda :
[2021-02-16 15:28:30] INFO: "Un nouvel utilisateur s'est inscrit."
[2021-02-16 15:58:55] ERROR: "La page '/article/18' n'existe pas."
[2021-02-16 17:59:11] ALERT: "La connexion avec la base de données
a échoué."
[2021-02-16 18:24:17] WARNING: "Il y a eu 5 tentatives de connexion
non concluantes pour l'utilisateur 'toto18'."
Chaque ligne correspond à un log. En plus d’un message et d’une date, nous
remarquons qu’un log dispose aussi d’un niveau (ERROR, ALERT, etc.) ; il s’agit du
niveau de journalisation. Ce dernier indique la gravité du log.
Bien que ce soit souvent le cas, les logs ne correspondent pas forcément à des erreurs.
Ils sont répartis sur une multitude de niveaux, allant d’un message
purement informatif à une alerte sur une situation d’extrême urgence.

2. La librairie Monolog
Monolog est une librairie capable non seulement d’enregistrer les logs dans un fichier
de journalisation, mais également de les envoyer par e-mail, avec le protocole syslog,
ou même de les stocker en base de données.
Monolog dans Symfony
Monolog est installable dans Symfony à partir d’une recette Flex. Voici la commande
à utiliser pour l’installer dans votre application :
composer require symfony/monolog-bundle

Les niveaux de journalisation


Monolog met à votre disposition un objet de journalisation qui implémente
l’interface Psr\Log\LoggerInterface. L’interface LoggerInterface vous garantit huit
méthodes. Chaque méthode est chargée de la création d’un log pour un niveau donné.
Ces niveaux sont les mêmes que ceux du protocole syslog, à savoir, par ordre
croissant de
gravité : debug, info, notice, warning, error, critical, alert et emergency.

3. Le service logger
Le service de journalisation par défaut s’appelle logger. Monolog supporte le standard
PSR-3 et implémente l’interface Psr\Log\LoggerInterface.
Voici comment déclencher un log depuis un contrôleur :
$this->get('logger')->warning('Houston, on a eu un problème.');

Cette ligne de code enregistre un log de niveau WARNING avec le message


« Houston, on a eu un problème. »
Ici, la journalisation est déclenchée grâce à une récupération du service logger depuis
un contrôleur ; ce service aurait très bien pu être injecté afin d’être utilisé :
use Psr\Log\LoggerInterface;

...

public function index(LoggerInterface $logger)


{
...
$logger->warning('Houston, on a eu un problème.');
...
}
...

Par défaut, ce log sera enregistré dans un


fichier : var/log/prod.log ou var/log/dev.log (selon votre environnement).

4. Le fichier journal
Jusqu’à présent, l’utilisation de la journalisation s’avère assez simple. Il suffit de
récupérer le service logger, puis d’invoquer une méthode (warning(), error(), etc.)
avec un message. Les logs sont ensuite enregistrés dans un fichier au sein du
répertoire var/log.
a. Identifier la cause d’un bogue
Le fichier de logs contient des informations intéressantes sur votre application. Il peut
être utilisé si vous souhaitez effectuer des statistiques ou, plus couramment, pour
identifier la cause d’un dysfonctionnement.
Si un utilisateur porte à votre connaissance le fait qu’il a rencontré un bogue sur votre
application, le fichier de logs peut vous aider à détecter la cause de ce bogue.
Les logs étant listés chronologiquement, il vous suffit de naviguer jusqu’au créneau
horaire durant lequel le bogue a eu lieu et de rechercher un log dont le niveau est au
minimum ERROR. Le message de ce log vous donnera sûrement de précieuses
indications quant à la cause de ce dysfonctionnement.
Imaginons que vous trouviez un log de niveau ALERT dont le message explique que
la connexion à la base de données a échoué : vous contactez la personne en charge de
la base de données, qui corrige le problème.
b. Le problème
Même si, grâce au fichier de logs, vous avez réussi à identifier (et par la suite
corriger) un bogue, vous avez fait preuve d’un manque de réactivité sur une situation
urgente.
Si un log survient dans votre application, l’enregistrer dans un fichier pour une
analyse a posteriori n’est donc parfois pas suffisant. Cela dépend surtout du niveau de
gravité du log.
Nous allons voir comment les gestionnaires (ou handlers) peuvent nous aider à traiter
cette problématique. Il est possible d’affecter différents gestionnaires pour vos logs,
en fonction notamment de leur niveau de gravité.

5. Les gestionnaires (handlers)


Un handler (gestionnaire en français) est un service qui traite les logs déclenchés par
l’application.
Par défaut, l’édition standard de Symfony configure un seul handler :
# config/packages/dev/monolog.yaml

monolog:
handlers:
main:
type: stream
path: %kernel.logs_dir%/%kernel.environment%.log
level: debug

Le handler main est de type stream (flux). En PHP, le terme générique


de flux désigne une ressource ; cela correspond généralement à un fichier local.
La directive path indique le chemin vers la ressource, tandis que la
directive level spécifie le niveau de gravité minimum pour lequel le handler devra être
invoqué.
En clair, avec cette configuration, tous les logs dont le niveau est supérieur ou égal
à debug seront enregistrés dans le fichier var/log/dev.log.
Vous noterez qu’il est fait ici référence au fichier de configuration de Monolog pour
l’environnement de dev de Symfony. Si vous souhaitez agir sur la journalisation pour
l’environnement de production (prod) il faudra modifier le
fichier config/packages/prod/monolog.yaml.
a. Définir plusieurs gestionnaires
Dans la section de configuration handlers, vous pouvez définir plusieurs handlers :
monolog:
handlers:
important:
type: stream
path: %kernel.logs_dir%/important.log
level: error
bubble: false
secondaire:
type: stream
path: %kernel.logs_dir%/secondaire.log
level: debug

Ici, nous en définissons deux : important et secondaire. Avec cette configuration, les
logs seront répartis en deux fichiers : important.log contiendra les enregistrements de
niveaux ERROR et supérieurs, tandis que secondaire.log contiendra les autres
enregistrements (niveaux inférieurs à ERROR).

L’option bubble mise à false sur le premier handler indique qu’une fois un
enregistrement traité, il ne doit pas être envoyé aux handlers suivants ; l’ordre dans
lequel les handlers sont définis est donc important.
Si vous retirez l’option bubble, les enregistrements de niveaux ERROR et supérieurs
seront écrits dans les deux fichiers.
b. Envoyer des logs par e-mail
Le handler swift_mailer est capable d’envoyer des e-mails avec la
librairie SwiftMailer, intégrée par défaut à Symfony.
Ce handler est tout à fait adapté aux enregistrements de niveaux CRITICAL et
supérieurs. Voici comment le configurer :
monolog:
handlers:
swift:
type: swift_mailer
from_email: rapport@mon-projet.local
to_email: developpeurs@mon-projet.local
subject: Rapport d'erreur
level: critical

Ici, les logs de niveaux CRITICAL et supérieurs seront envoyés dans des
messages, à l’adresse developpeurs@mon-projet.local.
c. Utiliser un tampon (buffer)
Le handler swift_mailer que nous venons de configurer n’est pas optimal. Si
plusieurs erreurs critiques surviennent au cours d’une même requête, vous
recevrez autant d’e-mails.
Il est possible de regrouper les enregistrements d’une requête au sein du même
message grâce à un tampon (buffer en anglais) :
monolog:
handlers:
buffer:
type: buffer
handler: swift
level: critical
swift:
type: swift_mailer
from_email: rapport@mon-projet.local
to_email: developpeurs@mon-projet.local
subject: Rapport d'erreur

Avec cette configuration, le handler buffer englobe le handler swift, qui n’existe plus
de lui-même, il n’a donc pas besoin de niveau. Le handler buffer récupérera les
enregistrements de niveaux CRITICAL et supérieurs, mais ne les transférera au
handler swift qu’en fin de script. Ainsi, si plusieurs enregistrements sont récupérés, ils
seront regroupés en un seul e-mail.
d. Ajouter des informations complémentaires
Pour récupérer des informations complémentaires, il existe un handler très utile
appelé fingers_crossed :
monolog:
handlers:
fingers_crossed:
type: fingers_crossed
level: info
action_level: error
handler: nested
nested:
type: stream
path: %kernel.logs_dir%/enregistrements.log

Tout comme buffer, ce handler en englobe un autre (ici nested, qui n’existe donc plus
de lui-même).
Le handler fingers_crossed comporte deux principales
directives : level et action_level. L’option level indique le niveau à partir duquel les
enregistrements seront interceptés, alors qu’action_level définit le niveau à partir
duquel les enregistrements interceptés doivent être transmis au handler englobé.
Ainsi, avec l’exemple ci-dessus, si, au cours d’une requête, seuls des enregistrements
de niveaux DEBUG et WARNING sont déclenchés, rien ne sera écrit dans le
fichier enregistrements.log. Cependant, si, au cours d’une autre requête, au moins un
log de niveaux ERROR ou supérieur est déclenché, tous les enregistrements
interceptés par le handler fingers_crossed seront ajoutés au
fichier enregistrements.log.
Grâce à cette technique, vous pouvez récupérer des informations complémentaires sur
le déroulement de la requête et non les messages d’erreur isolés.
Ce handler peut facilement être intégré à swift_mailer :
monolog:
handlers:
mail:
type: fingers_crossed
level: info
action_level: critical
handler: buffered
buffered:
type: buffer
handler: swift
swift:
type: swift_mailer
from_email: rapport@mon-projet.local
to_email: developpeurs@mon-projet.local
subject: Rapport d'erreur

Grâce à cette configuration, les e-mails de rapport d’erreurs critiques contiendront


plus de détails.

6. Les canaux (channels)


Durant tous nos précédents exemples, nous avons expliqué comment Monolog
pouvait répartir les logs différemment selon leur niveau de gravité.
Il existe un deuxième critère très utile que nous n’avons pas abordé jusqu’à présent :
la source. En effet, selon la source d’un enregistrement, vous ne voudrez peut-être pas
utiliser le même handler. À titre d’exemple, nous pourrions imaginer que les erreurs
relatives à la base de données pourraient être traitées par un handler particulier,
destiné à avertir l’administrateur de la base de données, plutôt qu’être mélangées avec
les autres erreurs.
C’est dans le but d’offrir cette fonctionnalité que Monolog intègre des canaux. Par
défaut, les enregistrements que vous déclenchez avec le service logger transitent par
le canal app. Mais il en existe d’autres :
 doctrine : pour les enregistrements en rapport avec la base de données.
 security : pour les enregistrements en provenance du composant Security.
 emergency : pour les erreurs fatales.
 request : pour les enregistrements en rapport avec la requête et son routage.
Cette liste n’est pas exhaustive, les canaux configurés par défaut pour votre
application dépendent des bundles que vous avez installés.
a. Ajouter ses propres canaux
Si vous avez une « sous-application » ou une intégration avec un service
externe comme une API HTTP, vous souhaitez peut-être isoler leurs enregistrements.
Pour cela, vous devez ajouter vos propres canaux :
# config/packages/dev/monolog.yaml
monolog:
channels: ["mon_api", "mon_autre_canal"]

Ici, nous ajoutons deux canaux : mon_api et mon_autre_canal. Ils seront par défaut
traités par tous vos handlers.
b. Envoyer un enregistrement sur un canal donné
Pour envoyer un enregistrement sur un canal donné, vous devez utiliser le
service correspondant. En clair, chaque canal possède son propre service de
journalisation :
// Canal « app »
$this->get('logger')->error('Une erreur est survenue.');

// Canal « mon_api »
$this
->get('monolog.logger.mon_api')
->critical('Mon API ne répond pas.')
;

// Canal « doctrine »
$this
->get('monolog.logger.doctrine')
->debug('Ma requête n\'a retourné aucun résultat.')
;

Comme vous l’aurez compris, pour envoyer les enregistrements sur un canal X, il
suffit d’utiliser le service monolog.logger.X.
Notons tout de même que, à titre d’exception, le service logger utilise le canal app :
c’est le service de journalisation principal.
c. Configurer les gestionnaires par canaux
Il n’y a pas d’intérêt à utiliser ses propres canaux, si ce n’est pour les attacher à
différents gestionnaires.
La directive channels est chargée d’effectuer cette tâche. Elle fonctionne soit par
inclusion, soit par exclusion :
monolog:
handlers:
api:
type: stream
path: %kernel.logs_dir%/mon_api.log
channels: [mon_api]
principal:
type: stream
path: /%kernel.logs_dir%/enregistrements.log
channels: [!mon_api, !doctrine]

Ici, nous configurons deux gestionnaires : api et principal.


Le gestionnaire api fonctionne par inclusion, seul le canal mon_api sera intercepté.
Le gestionnaire principal fonctionne quant à lui par exclusion, il sera utilisé pour tous
les canaux, à l’exception de mon_api et doctrine.
d. Gestion des erreurs 404
Si vous avez déjà un site web Symfony en production et que vous ouvrez votre fichier
de logs (var/log/prod.log), vous remarquerez peut-être qu’il est rempli de lignes
relatives à des pages non trouvées (erreurs 404).
Cela est dû à des robots malveillants qui sévissent sur Internet. En quête de failles de
sécurité, ils essayent d’accéder à des ressources de type /phpmyadmin ou
/phpinfo.php, par exemple. Votre application génère donc des erreurs, car ces
ressources ne correspondent à aucune de vos routes.
Si vous utilisez la configuration par défaut (avec le
gestionnaire fingers_crossed), votre fichier de logs deviendra rapidement encombré
avec des informations complémentaires autour des erreurs 404 provoquées par les
robots. En conséquence, votre fichier de logs deviendra peu lisible et il sera plus
compliqué de repérer d’éventuelles erreurs réelles.
La fonctionnalité que nous avons précédemment découverte (cf. section Configurer
les gestionnaires par canaux) peut très facilement extraire ces erreurs 404 dans un
autre fichier :
monolog:
handlers:
request:
type: stream
path: %kernel.logs_dir%/requetes.log
level: error
channels: request
bubble: false
main:
type: fingers_crossed
action_level: error
level: debug
handler: nested
nested:
type: stream
path: %kernel.logs_dir%/%kernel.environment%.log

Avec cette configuration, les enregistrements du canal request de niveaux ERROR ou


supérieurs seront extraits dans un fichier dédié. De cette manière, votre fichier
principal se voit allégé.
Vous devez bien sûr continuer à analyser le fichier de logs requetes.log pour détecter
les erreurs 404 qui ne seraient pas liées à des robots ou à une mauvaise manipulation
des utilisateurs, mais à des liens morts vers ou sur votre site, par exemple.
Exclure du fichier de logs certaines erreurs 404
Si vous utilisez, comme nous vous l’avons conseillé, la version 2.5 ou supérieure de
MonologBundle, vous avez la possibilité de définir des règles pour exclure certaines
erreurs 404 de vos fichiers de logs.
C’est la directive excluded_404s qui active cette fonctionnalité ; elle est
seulement disponible sur les handlers de type fingers_crossed.
Voici une variante de la configuration par défaut de Monolog dans l’édition standard
du framework et sous l’environnement de production, qui utilise la
directive excluded_404s :
monolog:
handlers:
main:
type: fingers_crossed
level: debug
action_level: error
handler: nested
excluded_404s:
- ^/phpmyadmin
- ^/wp-admin
nested:
type: stream
path: %kernel.logs_dir%/%kernel.environment%.log

Avec cette dernière, Monolog exclura du fichier de logs les erreurs 404 dont le path
de l’URL commence par /phpmyadmin ou /wp-admin. Ce sont typiquement des
erreurs provoquées par des robots ou des pirates informatiques qui tentent d’accéder
respectivement aux pages d’administration de PHPMyAdmin et WordPress, en se
basant sur les paths par défaut de ces projets.
Précédent
Quiz
Suivant
Le monitoring avec Prometheus et Grafana
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana

 1. Un allié proactif au logging


 2. Préparation d’une application Symfony pour Prometheus
 3. Instrumenter les mesures
 4. Pour aller plus loin

Quiz
Amélioration des performances
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Le monitoring avec Prometheus et


Grafana
1. Un allié proactif au logging
Pour une application en production, le logging seul ne permet pas de gérer
efficacement les problèmes qui pourraient survenir. Son principal inconvénient est lié
à sa nature : il n’est qu’un journal répertoriant une série d’événements ayant déjà eu
lieu. Même si vous avez configuré Monolog pour vous envoyer un e-mail en cas
d’erreur critique, vous restez dans une démarche « a posteriori » où vous ne pouvez
que « limiter les dégâts ».
Le monitoring est une technique complémentaire au logging pour les systèmes en
production. Il permet, dans certains cas, de détecter les potentiels incidents avant
qu’ils ne surviennent. Pour cela, il s’appuie sur un prélèvement de « mesures »
(ou metrics en anglais). Ces dernières seront principalement utilisées pour :
 envoyer des alertes (via e-mail, SMS, etc.) lorsque certaines mesures
atteignent des valeurs préoccupantes,
 créer des graphiques (ex. afficher le nombre de requêtes par seconde, les taux
d’erreurs, etc.).

Exemples de graphiques générés depuis des mesures


Exemple d’alerte depuis des mesures
Pour illustrer la notion d’alerte d’un système de monitoring, imaginons un simple
formulaire permettant d’uploader des fichiers. L’utilisateur peut uploader des fichiers
locaux vers un serveur distant, dans le but de sauvegarder ses fichiers dans le « cloud
». Ces fichiers occupant de l’espace disque, il serait intéressant de vérifier
régulièrement combien d’espace est disponible. Lorsqu’une mesure indique, par
exemple, que 80 % de l’espace est utilisé, vous pourriez recevoir une alerte, que ce
soit par SMS, e-mail ou autre.

2. Préparation d’une application Symfony pour


Prometheus
Prometheus est un système de monitoring ; il va nous permettre de collecter des
mesures auprès de notre application Symfony. Nous pourrons ensuite les visualiser
dans des graphiques et les intégrer à un système d’alerte.
Prometheus s’appuie sur un système de type pull, l’application Symfony doit
simplement exposer ses mesures sous un path donné (généralement /metrics).
Prometheus effectuera ensuite des requêtes régulières pour collecter ces mesures.
Pour ce faire, nous allons dans un premier temps installer une librairie permettant
d’exposer nos mesures facilement :
composer require jimdo/prometheus_client_php

Cette libraire est capable de définir trois types de mesures :


 Des compteurs ; ce sont des valeurs numériques qui augmentent avec le temps.
Ils peuvent être utilisés, par exemple, pour le nombre de requêtes HTTP totales
reçues ou le nombre d’inscriptions sur votre plateforme.
 Des jauges ; elles contiennent également des valeurs numériques.
Cependant, contrairement aux compteurs, leur valeur peut aussi diminuer. Elles
sont utiles pour des mesures comme la température ou l’utilisation du disque.
 Les histogrammes ; il s’agit d’un type de mesures un peu plus complexe. Ils
donnent lieu à un ensemble de compteurs. Nous verrons plus loin comment les
utiliser pour mesurer la durée des requêtes HTTP.
Commençons par définir le service pour collecter les mesures. Étant donné que ces
dernières doivent êtres conservées au fur et à mesure des requêtes, nous devons les
persister. Pour cela, la librairie PHP que nous avons installée offre principalement
deux possibilités : APC et Redis. APC est le plus simple à mettre en place, il n’est
cependant plus très utilisé depuis PHP 5.5, où il a été remplacé par OpCache, tandis
que son cache utilisateur a été déplacé vers une nouvelle extension APCu. Nous vous
conseillons donc d’installer les extensions apcu et apcu_bc (pour pouvoir utiliser les
fonctions obsolètes apc_*).
Vous pouvez ensuite définir le service suivant :
# config/services.yaml
services:

# ...

Prometheus\Storage\APC: ~

Prometheus\CollectorRegistry:
public: true
arguments: ['@Prometheus\Storage\APC']

Les mesures peuvent maintenant être servies sous le path /metrics :


namespace App\Controller;

use Prometheus\RenderTextFormat;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class MetricsController extends AbstractController


{
/**
* @Route("/metrics", name="metrics")
*/
public function metrics(Request $request)
{
$renderer = new RenderTextFormat();
$registry = $this->get('Prometheus\CollectorRegistry');

return new Response(


$renderer->render($registry->getMetricFamilySamples()),
200,
['Content-Type', RenderTextFormat::MIME_TYPE]
);
}
}
Si vous ouvrez cette page, vous ne voyez rien, car aucune mesure n’a encore été
enregistrée.
Nous pouvons dès à présent ajouter une mesure avant de renvoyer la réponse (au
niveau de l’action metrics()) :
$diskTotal = disk_total_space('/');

$gauge = $registry->getOrRegisterGauge('', 'disk_used_bytes',


'used disk size', []);
$gauge->set($diskTotal - disk_free_space('/'), []);

$gauge = $registry->getOrRegisterGauge('', 'disk_total_bytes',


'total disk size', []);
$gauge->set($diskTotal, []);

En ouvrant la page /metrics, vous apercevez les deux jauges relatives à


l’utilisation du disque. Nous allons maintenant définir une autre mesure relative à la
durée des requêtes HTTP. Cette dernière nécessite la création d’un event subscriber :
namespace App\EventSubscriber;

use HRTime\StopWatch;
use Prometheus\CollectorRegistry;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;

class RequestDurationSubscriber implements EventSubscriberInterface


{
private $registry;
private $timer;

public static function getSubscribedEvents()


{
return array(
KernelEvents::REQUEST => array('startTimer', 255),
KernelEvents::TERMINATE => array('stopTimer', 255),
);
}
public function __construct(CollectorRegistry $registry)
{
$this->registry = $registry;
$this->timer = new Stopwatch();
}

public function startTimer(GetResponseEvent $event)


{
if ($event->isMasterRequest()) {
$this->timer->start();
}
}

public function stopTimer(PostResponseEvent $event)


{
if (!$event->isMasterRequest()) {
return;
}

$this->timer->stop();

$histogram = $this->registry->getOrRegisterHistogram(
'',
'http_request_duration_seconds',
'request duration',
['route'],
[0.05, 0.1, 0.2, 0.5, 1, 2, 5]
);

$histogram->observe(
$this->timer->getElapsedTime(),
[$event->getRequest()->attributes->get('_route')]
);
}
}
Ici, nous mesurons la durée de chaque requête puis l’enregistrons dans un
histogramme. Nous utilisons l’extension hrtime plutôt que la
fonction microtime(), car cette dernière n’est pas monotonique (elle peut avancer ou
reculer arbitrairement).

3. Instrumenter les mesures


Ici, nous allons devoir installer Prometheus et Grafana. Nous ne détaillons pas leur
installation, mais vous pouvez trouver dans les fichiers en téléchargement de cet
ouvrage une configuration Docker Compose pour intégrer ces derniers à une
application Symfony.
Voici la configuration à utiliser pour Prometheus :
scrape_configs:
- job_name: 'mon_app'
scrape_interval: 30s
static_configs:
- targets: ['app:8000']

Ici, vous devez remplacer les valeurs selon votre installation : job_name avec le nom
de votre application, par exemple, scrape_interval pour l’intervalle entre chaque
requête de Prometheus, et enfin targets pour l’hôte et le port de votre application.
Prometheus est maintenant capable de récupérer les mesures. Il ne reste plus qu’à les
intégrer à Grafana pour obtenir des graphiques personnalisables.
Dans l’interface Grafana, ouvrez le menu Data Source - Add a data source, donnez
un nom à votre source et sélectionnez le type « Prometheus ». Saisissez l’URL de
votre application Symfony (sans le path /metrics, par exemple http://localhost:9000).
Créez maintenant un dashboard ; vous pouvez y ajouter des graphiques. Voici un
exemple de dashboard avec deux graphiques utilisant les mesures précédemment
définies.

Ici, nous avons utilisé la requête disk_used_bytes / disk_total_bytes pour l’espace


disque ; cela nous renvoie le pourcentage d’espace disque utilisé.
Pour la durée des réponses, nous utilisons :
rate(http_request_duration_seconds_sum[5m])
/
rate(http_request_duration_seconds_count[5m])

Cette syntaxe est spécifique à Prometheus et s’appelle le PromQL.

4. Pour aller plus loin


Dans cette section, nous nous sommes surtout concentrés sur la préparation d’une
application Symfony pour le monitoring avec Prometheus. Nous avons vu comment
exposer des mesures en rapport avec l’espace disque et la durée des requêtes.
Le monitoring est cependant un vaste sujet. Nous vous conseillons de vous référer à la
documentation de Prometheus pour approfondir vos connaissances quant à son
utilisation ou la gestion des alertes.
Précédent
Générer des journaux avec Monolog
Suivant
Quiz
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana
Quiz
Amélioration des performances
La mise en cache de pages
 1. Autour du protocole HTTP
 2. Un serveur proxy inverse (ou « reverse proxy »)
 a. HttpCache
 b. Nginx
 c. Varnish
 3. Les en-têtes
 4. Les réponses publiques et privées
 5. L’expiration
 a. L’en-tête Expires
 b. Les directives max-age et s-max-age
 c. L’annotation @Cache
 6. La validation
 a. Par date avec Last-Modified
 b. Par empreinte avec l’en-tête ETag
 7. Les ESI
 a. Activation
 b. Générer une balise ESI

L’autochargement des classes


Le cache avec Doctrine
Le cache d’annotations
Les sessions
Autres optimisations
Test des performances d'un site web
Quiz
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

La mise en cache de pages


1. Autour du protocole HTTP
Symfony s’appuie sur les spécifications HTTP pour la gestion du cache des pages. Le
protocole HTTP définit en effet un certain nombre d’en-têtes relatifs à la mise en
cache.
Voici un rapide avant-goût de ses fonctionnalités :
<?php header('Cache-Control: max-age=10') ?>

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<?php echo date('y-m-d H:i:s') ?><br />
<a href="">Rafraîchir</a>
</body>
</html>

Si vous ouvrez cette page avec un navigateur web, puis cliquez sur le
bouton Rafraîchir, vous vous rendrez compte que la date affichée se met à jour
seulement au bout de 10 secondes.
Lors des autres chargements, le navigateur utilise la page présente dans son cache,
car, lorsqu’il récupère la réponse la première fois, l’en-tête Cache-
Control indique que la page peut être gardée en cache pendant 10 secondes, et qu’au-
delà de ce délai, une nouvelle version devra être demandée au serveur web.
Grâce à cette technique, la page peut être renvoyée très rapidement à l’utilisateur,
aucune requête HTTP n’est effectuée, et la charge du serveur web s’en trouve allégée.

2. Un serveur proxy inverse (ou « reverse proxy »)


Cette technique de mise en cache « côté client » montre malheureusement très
rapidement ses limites.
Les pages étant mises en cache au niveau du navigateur, un nouvel utilisateur, lors de
sa première ouverture de la page, doit obligatoirement envoyer une requête au serveur
web, de manière à récupérer la page à mettre en cache. Votre application doit donc
sans cesse régénérer la même page, pour chaque nouvel utilisateur : le cache n’est pas
partagé.

Le serveur web doit générer la page pour chaque client, et si ces derniers
effectuent une nouvelle requête pour la même page, et que les en-têtes indiquent que
la copie est encore valable (Cache-Control), ils utilisent la copie dans le cache du
navigateur.
Il existe un mécanisme capable de fournir un cache commun pour tous les clients : le
proxy inverse (ou reverse proxy).
Ici, les clients ne communiquent plus directement avec le serveur web, mais passent
par un proxy inverse.
Le proxy transmet ensuite la requête à l’application, et si la réponse renvoyée
comporte des en-têtes liés à la mise en cache, il enregistre une copie de la page en
cache avant de l’envoyer au client.
Lors des prochaines requêtes pour cette même page, quel que soit le client, si la copie
enregistrée par le proxy inverse est toujours valable, il la retourne au client sans
interroger l’application.
La particularité du proxy inverse est d’être extrêmement rapide. Il a été
spécialement conçu pour effectuer certaines tâches, comme la gestion du cache ou la
compression des réponses.
a. HttpCache
HttpCache est un proxy inverse développé par Symfony. Ce composant ne nécessite
aucune installation système mais doit être mis en place au niveau du projet.
Il commence par créer le cache. Pour cela, créez par exemple le
fichier CacheKernel.php dans le dossier src/ et mettez-y le code suivant :
// src/CacheKernel.php
namespace App;

use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache;
class CacheKernel extends HttpCache {
}

Ensuite, il faut modifier le contrôleur frontal (public/index.php) pour y intégrer ce


cache applicatif. Les lignes à ajouter sont indiquées en gras dans le code suivant :
use App\Kernel;
use App\CacheKernel;
use Symfony\Component\ErrorHandler\Debug;
use Symfony\Component\HttpFoundation\Request;

require dirname(__DIR__).'/config/bootstrap.php';

if ($_SERVER['APP_DEBUG']) {
umask(0000);
Debug::enable();
}

if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? false) {


Request::setTrustedProxies(explode(',', $trustedProxies),
Request::HEADER_X_FORWARDED_FOR | Request::
HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO);
}

if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? false) {


Request::setTrustedHosts([$trustedHosts]);
}

$kernel = new Kernel($_SERVER['APP_ENV'], (bool)


$_SERVER['APP_DEBUG']);

if ('prod' === $kernel->getEnvironment()) {


$kernel = new CacheKernel($kernel);
}

$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);

La classe CacheKernel englobe le Kernel ; c’est une simulation d’un proxy inverse au
niveau applicatif.
b. Nginx
Nginx peut être utilisé en tant que proxy inverse de mise en cache. En voici la
configuration minimale :
http {

# ...

proxy_redirect off;
proxy_cache_path /var/cache/http_cache levels=1:2
keys_zone=http_cache:128m;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

server {
listen 80;
server_name mon_projet.local;

location / {
proxy_pass http://127.0.0.1:8080/;
proxy_cache http_cache;
}
}

server {
listen 8080;
root /var/www/mon_projet/web;

location / {
try_files $uri @rewriteapp;
}

location @rewriteapp {
# rewrite all to app.php
rewrite ^(.*)$ /app.php/$1 last;
}

location ~ ^/(app|app_dev|config)\.php(/|$) {
fastcgi_pass 127.0.0.1:9000;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME
$document_root$fastcgi_script_name;
}

error_log /var/log/nginx/project_error.log;
access_log /var/log/nginx/project_access.log;
}
}

La directive proxy_cache_path définit une zone de cache, contenant principalement


l’endroit où les fichiers seront enregistrés (ici /var/cache/http_cache).
Avec cette configuration, Nginx agit en tant que proxy inverse sur le port 80, et les
requêtes sont transmises au serveur web classique en arrière-plan, sur le port 8080.
c. Varnish
Varnish est un proxy inverse très performant car les pages en cache sont stockées en
mémoire vive. De plus, Varnish est capable de traiter un très grand nombre de
requêtes simultanées (https://info.varnish-software.com/blog/275-000-requests-
second-yes-we-can).
Si votre site web est très visité, il peut être préférable d’opter pour Varnish plutôt que
HttpCache.
L’installation de Varnish est assez simple. Nous vous renvoyons vers la
documentation officielle : https://www.varnish-cache.org/docs
Si vous développez sous Windows, Varnish est plus compliqué à installer
(https://www.varnish-cache.org/trac/wiki/VarnishOnCygwinWindows). Dans ce cas-
là, nous vous conseillons d’utiliser HttpCache de Symfony sur votre poste de
développement, et de le remplacer par Varnish sur votre serveur de production. En
production, faites également attention à ne pas laisser les deux systèmes de cache
activés en même temps, ce qui provoquerait un ralentissement inutile avec une double
mise en cache.
Les ports
Après son installation, Varnish écoute sur le port 6081 et il s’attend à trouver
l’application sur le port 8080.
Pour changer ces deux ports, il faut modifier les fichiers /etc/default/varnish (port du
démon Varnish) ou /etc/varnish/default.vcl (section backend default, pour
l’emplacement de l’application).
En production, vous devez bien évidemment placer Varnish sur le port 80.
Les cookies
Par défaut, Varnish n’utilise pas le cache s’il découvre un cookie, que ce soit dans la
requête du client (en-tête Cookie) ou dans la réponse de l’application (en-tête Set-
Cookie). Pour éviter cela, vous devez modifier la configuration
dans /etc/varnish/default.vcl :
# This is a basic VCL configuration file for varnish. See the
# vcl(7) man page for details on VCL syntax and semantics.
#
# Default backend definition. Set this to point to your content
# server.
#
backend default {
.host = "127.0.0.1";
.port = "8080";
}
#
# Below is a commented-out copy of the default VCL logic. If you
# redefine any of these subroutines, the built-in logic will be
# appended to your code.
sub vcl_recv {
if (req.restarts == 0) {
if (req.http.x-forwarded-for) {
set req.http.X-Forwarded-For =
req.http.X-Forwarded-For + ", " + client.ip;
} else {
set req.http.X-Forwarded-For = client.ip;
}
}
if (req.request != "GET" &&
req.request != "HEAD" &&
req.request != "PUT" &&
req.request != "POST" &&
req.request != "TRACE" &&
req.request != "OPTIONS" &&
req.request != "DELETE") {
/* Non-RFC2616 or CONNECT which is weird. */
return (pipe);
}
if (req.request != "GET" && req.request != "HEAD") {
/* We only deal with GET and HEAD by default */
return (pass);
}
if (req.http.Authorization) {
/* Not cacheable by default */
return (pass);
}
return (lookup);
}

# sub vcl_pipe {
# # Note that only the first request to the backend will have
# # X-Forwarded-For set. If you use X-Forwarded-For and want
# # to have it set for all requests, make sure to have:
# # set bereq.http.connection = "close";
# # here. It is not set by default as it might break some
# # broken web applications, like IIS with NTLM authentication.
# return (pipe);
# }
#
# sub vcl_pass {
# return (pass);
# }
#
# sub vcl_hash {
# hash_data(req.url);
# if (req.http.host) {
# hash_data(req.http.host);
# } else {
# hash_data(server.ip);
# }
# return (hash);
# }
#
#sub vcl_hit {
# return (deliver);
#}
#
# sub vcl_miss {
# return (fetch);
# }
#
sub vcl_fetch {
if (beresp.ttl <= 0s ||
beresp.http.Vary == "*") {
/*
* Mark as "Hit-For-Pass" for the next 2 minutes
*/
set beresp.ttl = 120 s;
return (hit_for_pass);
}
remove beresp.http.Set-Cookie;
return (deliver);
}
#sub vcl_deliver {
# return (deliver);
#}
#
# sub vcl_error {
# set obj.http.Content-Type = "text/html; charset=utf-8";
# set obj.http.Retry-After = "5";
# synthetic {"
# <?xml version="1.0" encoding="utf-8"?>
# <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
# "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
# <html>
# <head>
# <title>"} + obj.status + " " + obj.response + {"</title>
# </head>
# <body>
# <h1>Error "} + obj.status + " " + obj.response + {"</h1>
# <p>"} + obj.response + {"</p>
# <h3>Guru Meditation:</h3>
# <p>XID: "} + req.xid + {"</p>
# <hr>
# <p>Varnish cache server</p>
# </body>
# </html>
# "};
# return (deliver);
# }
#
# sub vcl_init {
# return (ok);
# }
#
# sub vcl_fini {
# return (ok);
# }

Chaque section (sub vcl_fetch, sub vcl_recv, etc.) correspond à ce qu’on appelle une
« subroutine ». Les subroutines définissent le comportement de Varnish tout au long
du traitement d’une requête. Elles utilisent un langage spécial : le VCL (Varnish
Configuration Language). Par défaut, toutes les subroutines sont commentées avec le
comportement standard de Varnish
Pour modifier une subroutine, il suffit de la décommenter et d’y placer sa
logique personnalisée. Ici, nous avons modifié les
subroutines vcl_recv et vcl_fetch pour autoriser la mise en cache de pages ayant un
cookie.
Pour plus d’informations sur les subroutines de Varnish : https://www.varnish-
cache.org/docs/3.0/reference/vcl.html#subroutines
Lorsque vous autorisez la mise en cache de pages avec cookie, vous devez faire
particulièrement attention à ne pas mettre en cache des pages protégées. Si le cookie
d’un utilisateur contient une authentification avec un certain niveau d’autorisation, lui
permettant d’accéder à une ressource donnée, et qu’elle est mise en cache, n’importe
quel utilisateur pourrait potentiellement accéder à cette ressource.
Durée de vie (ou Time To Live)
Par défaut, Varnish met en cache toutes les réponses qui ne présentent pas de directive
d’expiration (max-age, s-max-age ou Expires) pendant deux minutes.
Pour désactiver cette option, vous devez spécifier un Time To Live (durée de vie) à
zéro. Le moyen le plus simple consiste à passer une option lors du lancement du
démon Varnish, l’option -t. Vous pouvez l’ajouter dans le
fichier /etc/default/varnish.

3. Les en-têtes
Une fois votre proxy inverse configuré, vous êtes prêt à mettre vos pages en cache.
Comme nous l’avons expliqué au début de ce chapitre, au niveau applicatif, vous
n’aurez pas à stocker ou récupérer quoi que ce soit dans un système de cache car
toutes les techniques de mise en cache utilisent les en-têtes de la requête et/ou de la
réponse HTTP. Toutes ces règles sont définies dans les spécifications du protocole
HTTP et Symfony ne fait que les reprendre.

4. Les réponses publiques et privées


Avant l’installation d’un proxy inverse, nous avons également vu que les
navigateurs (Firefox, Google Chrome, etc.) disposaient d’un cache.
Le protocole HTTP distingue le cache d’un navigateur, dit privé, du cache d’un proxy
inverse, dit public, car ce dernier peut être partagé par plusieurs clients.
La directive max-age de l’en-tête Cache-Control concerne tous les caches tandis que
la directive s-max-age concerne seulement le cache public (proxy inverse).

5. L’expiration
Le modèle d’expiration permet de définir le délai ou la date à laquelle la copie en
cache ne sera plus valable.
a. L’en-tête Expires
L’en-tête Expires spécifie la date d’expiration de la page :
namespace App\Controller;

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

class DefaultController extends AbstractController


{
/**
* @Route("/")
*/
public function index()
{
$response = new Response(
'Cette réponse expire le 10 mai 2019 à 18 heures.'
);

$date = new \DateTime('2019-05-10 18:00:00');

$response->setExpires($date);

return $response;
}
}

La page est mise en cache par le proxy inverse ainsi que par le client.
b. Les directives max-age et s-max-age
L’en-tête HTTP Cache-Control contient, entre autres, les directives max-age et s-
max-age. Pour configurer ces deux directives, l’objet Response de Symfony fournit
les méthodes setMaxAge() et setSharedMaxAge() :
namespace App\Controller;

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

class DefaultController extends AbstractController


{
/**
* @Route("/")
*/
public function index()
{
$response = new Response(
'Cette réponse expire 30 secondes après sa génération.'
);

$response->setMaxAge(30);

// seulement pour le proxy inverse


// $response->setSharedMaxAge(30);

return $response;
}
}

c. L’annotation @Cache
Ajouter les en-têtes de cache à une réponse est parfois fastidieux, comme lorsque la
réponse utilise Twig :
namespace App\Controller;

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

class DefaultController extends AbstractController


{
/**
* @Route("/")
*/
public function index()
{
$response = new Response();

$response->setMaxAge(30);

// seulement pour le proxy inverse


// $response->setSharedMaxAge(30);
return $this->render(
'default/index.html.twig',
array(),
$response
);
}
}

Ci-dessus, l’annotation @Template ne peut être utilisée, car elle créerait une réponse
sans les en-têtes, mais grâce à l’annotation @Cache, le code est simplifiable :
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;

class DefaultController extends AbstractController


{
/**
* @Route("/")
* @Template()
* @Cache(maxage=30)
*/
public function index()
{
return array();
}
}

L’annotation @Cache accepte les attributs suivants :


 maxage : configure la directive max-age de l’en-tête Cache-Control.
 smaxage : configure la directive s-max-age de l’en-tête Cache-Control.
 expires : définit l’en-tête Expires.
 vary : définit l’en-tête Vary.
 public : indique si la réponse est publique (avec true) ou privée (false).
6. La validation
L’expiration a l’avantage d’être très rapide à mettre en place, mais elle manque
souvent de précision. Il existe une méthode capable de ne pas regénérer le contenu
d’une réponse jusqu’au moment exact où il doit changer.
Cette technique s’appelle la validation. Elle diffère de l’expiration par le fait qu’elle
nécessite toujours un appel à l’application, mais cette dernière crée une nouvelle
réponse seulement si celle en cache n’est plus valide.
Les en-têtes utilisés pour la validation sont Last-Modified et ETag, le premier se
basant sur une date et le second sur une chaîne de caractères.
L’expiration a priorité sur la validation : si une réponse a un s-max-age de
30 secondes et un ETag, et qu’un utilisateur redemande la ressource moins de 30
secondes après la génération de la copie en cache, l’application ne sera pas
interrogée.
a. Par date avec Last-Modified
L’exemple suivant utilise l’en-tête Last-Modified pour une entité Doctrine Article :
namespace App\Controller;

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

class DefaultController extends AbstractController


{
/**
* @Route("/voir/{id}")
*/
public function voir(Article $article)
{
$response = new Response();
$response->setPublic();
$response->setLastModified(
$article->getDateModification()
);

if ($response->isNotModified($this->getRequest())) {
return $response;
}

return $this->render(
'article/voir.html.twig',
array('article' => $article),
$response
);
}
}

La réponse est publique ; elle peut donc être mise en cache par le proxy inverse.
Lors des requêtes suivantes, le proxy inverse ajoute l’en-tête HTTP If-Modified-
Since à la requête du client avant de la transférer à l’application. Le contenu de cet
en-tête est la valeur de Last-Modified pour la réponse en cache dans le proxy inverse.
L’application n’a pas à générer la réponse complète car elle compare la date de
dernière modification de l’article avec la date fournie par le proxy inverse.
Si les dates sont les mêmes ($response->isNotModified($request) renvoie true), la
réponse peut être retournée immédiatement. Symfony renvoie une réponse de statut
304, au contenu vide, et le proxy inverse transmet sa copie en cache au client.
En clair, une nouvelle réponse est générée seulement après modification de l’article.
b. Par empreinte avec l’en-tête ETag
Voici un exemple semblable au précédent, à la différence près que la date de dernière
modification est remplacée par un Etag, une empreinte (ou hash) basée sur le titre et
le contenu de l’article :
namespace App\Controller;

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

class DefaultController extends AbstractController


{
/**
* @Route("/voir/{id}")
*/
public function voir(Article $article)
{
$response = new Response();
$response->setPublic();
$response->setEtag(md5(
$article->getTitre().$article->getContenu()
));

if ($response->isNotModified($this->getRequest())) {
return $response;
}

return $this->render(
'article/voir.html.twig',
array('article' => $article),
$response
);
}
}

7. Les ESI
Souvent, les pages de sites web sont complexes et il est nécessaire d’avoir
différentes stratégies de cache pour les différentes sections de la page.
Par exemple, la page d’un article peut utiliser la stratégie de validation (sur la date de
dernière modification), alors qu’une section avec les derniers tweets du compte du site
devrait se mettre à jour toutes les heures.
Les Edge Side Includes (ou ESI) sont un langage de balisage pour assembler des
pages dynamiquement. Le contenu provient potentiellement de plusieurs sources.
Concrètement, l’application insère des balises ESI dans la page. Ces balises
contiennent une URL, l’emplacement où peut être récupérée la section de page
pertinente. Le proxy inverse, en rencontrant ces balises, est capable d’aller chercher le
contenu et de le substituer à la balise.
Varnish et HttpCache de Symfony prennent en charge les spécifications ESI, ce qui
n’est pas le cas de Nginx.
a. Activation
Le paramètre esi de la configuration du FrameworkBundle doit être activé :
# config/packages/framework.yaml
framework:
# ...
esi: { enabled: true }

Varnish
Avec Varnish, il faut effectuer quelques modifications supplémentaires :
 Ajouter l’instruction set req.http.Surrogate-Capability = "abc=ESI/1.0"; dans
la subroutine vcl_recv de votre fichier de configuration Varnish.
 Sur ce même fichier, dans la subroutine vcl_fetch, ajouter les instructions
suivantes :
if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
set beresp.do_esi = true;
remove beresp.http.Surrogate-Control;
}

b. Générer une balise ESI


Symfony contient des fonctions Twig pour générer des balises ESI depuis la vue.
Sans URL
L’action cible de l’ESI n’a pas obligatoirement besoin d’une route ; elle peut être
référencée comme ceci :
{% extends 'base.html.twig' %}

{% block body %}
Le contenu suivant a été généré grâce à un ESI :<br />

{{ render_esi(controller(
'App\\Controller\\DefaultController::esi',
{ mon_parametre: 'ma_valeur' }
)) }}

{% endblock %}

Le template ci-dessus affichera un ESI pour l’action suivante :


namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;

class DefaultController extends AbstactController


{
/**
* @Template()
* @Cache(smaxage=5)
*/
public function esi($mon_parametre)
{
return new Response(
'Mon paramètre vaut : ' . $mon_parametre
);
}
}

Cette action a sa propre stratégie de cache, indépendante de celle de la réponse


principale. Optionnellement, et comme c’est le cas ici, des paramètres peuvent
également être passés.
Avec URL
Une route peut être utilisée à la place d’un contrôleur :
{% extends 'base.html.twig' %}

{% block body %}
Le contenu suivant a été généré grâce à un ESI :<br />

{{ render_esi(url('ma_route', { mon_parametre: 'ma_valeur' })) }}

{% endblock %}
Précédent
Quiz
Suivant
L’autochargement des classes
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana
Quiz
Amélioration des performances
La mise en cache de pages
L’autochargement des classes
Le cache avec Doctrine
Le cache d’annotations
Les sessions
Autres optimisations
Test des performances d'un site web
Quiz
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

L’autochargement des classes


Nous savons que Symfony s’appuie sur Composer pour l’autochargement des classes
(cf. chapitre Architecture du framework - Le chargement automatique de classes).
Charger une classe n’est pas une action lourde atomiquement, mais cette action est
répétée des milliers de fois au cours d’une requête sur un projet Symfony.
Ainsi, mettre en cache le fichier associé à chacune des classes utilisées par votre
projet est une optimisation intéressante.

1. Générer un classmap
Avec l’option -o de la commande dump-autoload, Composer regénère un chargeur
de classes plus performant : un tableau contenant chaque classe du projet, associée à
son emplacement physique, est créé.
Ce dernier est utilisé par le chargeur de classes pour trouver très rapidement le fichier
à inclure pour charger une classe donnée.
Voici la commande en question :
composer dump-autoload -o

Si votre projet comporte beaucoup de classes et/ou beaucoup de dépendances, le


tableau à charger pour chaque exécution de script pourrait s’avérer lourd. Si vous êtes
très pointilleux sur les ressources utilisées par votre site, vous serez peut-être intéressé
par la technique suivante.
Précédent
La mise en cache de pages
Suivant
Le cache avec Doctrine
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana
Quiz
Amélioration des performances
La mise en cache de pages
L’autochargement des classes
Le cache avec Doctrine

 1. Les différents types de cache


 2. Configuration

Le cache d’annotations
Les sessions
Autres optimisations
Test des performances d'un site web
Quiz
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Le cache avec Doctrine


1. Les différents types de cache
Doctrine dispose de trois types de cache.
Le cache des métadonnées
Les métadonnées sont toutes les informations de mapping de vos entités. Ces données
ne sont pas amenées à changer une fois l’application déployée en production. Elles
pourront donc être mises en cache pour une durée indéterminée.
Le cache des requêtes
Il est possible de mettre en cache le processus de transformation des requêtes DQL en
requêtes SQL. Tant qu’une requête DQL n’est pas modifiée, son équivalent en SQL
sera toujours le même. Il est donc recommandé d’utiliser le cache des requêtes en
production.
Le cache des résultats
Le cache des résultats permet de sauvegarder le résultat d’une requête, de manière à
éviter d’effectuer de trop nombreuses requêtes vers la base de données.
Il est configuré grâce à la méthode useResultCache() de l’objet Query de Doctrine :
$query = $manager->createQuery(
'SELECT u FROM App:Utilisateur u'
);

$query->useResultCache(true, 60);

Le premier argument indique que le cache de résultats doit être activé et le


second définit la durée de vie (en secondes) durant laquelle les résultats en cache sont
valables. Si le second argument est omis, les résultats seront mis en cache
indéfiniment.
2. Configuration
Dans le fichier de configuration Doctrine de votre environnement de
production (config/packages/prod/doctrine.yaml), les différents caches de
Doctrine sont configurés, le cache Doctrine est donc actif par défaut. Il faut
commenter ces lignes si vous ne souhaitez pas utiliser de cache pour Doctrine :
doctrine:
orm:
auto_generate_proxy_classes: false
metadata_cache_driver:
type: pool
pool: doctrine.system_cache_pool
query_cache_driver:
type: pool
pool: doctrine.system_cache_pool
result_cache_driver:
type: pool
pool: doctrine.result_cache_pool
Précédent
L’autochargement des classes
Suivant
Le cache d’annotations
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana
Quiz
Amélioration des performances
La mise en cache de pages
L’autochargement des classes
Le cache avec Doctrine
Le cache d’annotations
Les sessions
Autres optimisations
Test des performances d'un site web
Quiz
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Le cache d’annotations
Si vous utilisez les annotations pour des tâches comme la définition de routes ou de
règles de validation, vous serez sûrement intéressé par cette optimisation.
Comme nous l’avons déjà expliqué (cf. chapitre Routage et contrôleur -
Définition des routes), le support des annotations n’est pas natif au langage PHP, il est
fourni par Doctrine et se base sur l’API d’introspection de PHP. Cette dernière n’est
pas destinée à être utilisée en production, mais plutôt destinée à des outils d’analyse
de code ou des tests.
C’est pour cette raison que, par défaut, Doctrine met en cache les annotations dans des
fichiers. Ce système de cache peut toutefois être optimisé.
# config/framework.yaml

framework:
annotations:
cache: mon_cache_doctrine

# config/services.yaml

services:
memcached:
class: Memcached
arguments: ['ma_connexion']
calls:
- [addServer, ["localhost", 11211]]
mon_cache_doctrine:
class: Doctrine\Common\Cache\MemcachedCache
calls:
- [setMemcached, ["@memcached"]]

Partager un service de cache Doctrine


Si vous définissez, comme nous venons de le faire, un service avec une classe de
cache de Doctrine (espace de noms Doctrine\Common\Cache), nous vous
conseillons de le partager avec les autres systèmes de cache de Doctrine (résultats,
métadonnées et requêtes) :
# config/packages/prod/doctrine.yaml

doctrine:
orm:
metadata_cache_driver
type: service
id: mon_cache_doctrine
result_cache_driver:
type: service
id: mon_cache_doctrine
query_cache_driver:
type: service
id: mon_cache_doctrine

# ...

Cela permet d’instancier un seul objet de cache pour tous ces systèmes.
Précédent
Le cache avec Doctrine
Suivant
Les sessions
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres
Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana
Quiz
Amélioration des performances
La mise en cache de pages
L’autochargement des classes
Le cache avec Doctrine
Le cache d’annotations
Les sessions
Autres optimisations
Test des performances d'un site web
Quiz
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Les sessions
Par défaut, les sessions sont enregistrées dans des fichiers. Ce comportement est
facilement modifiable et voici comment sauvegarder les sessions en mémoire partagée
grâce à Memcache :
# config/framework.yaml

framework:
# ....
session:
handler_id: ma_session_memcache

# config/services.yaml
services:
memcached:
class: Memcached
arguments: ['ma_connexion']
calls:
- [addServer, ["localhost", 11211]]
ma_session_memcache:
class:
Symfony\Component\HttpFoundation\Session\Storage\Handler\Memcached
SessionHandler
arguments: [@memcached]

Il existe d’autres systèmes de sauvegarde (appelés « handlers »). Pour un listing


complet, reportez-vous à l’adresse URL
suivante
: https://symfony.com/doc/4.4/components/http_foundation/session_configuration.ht
ml
Précédent
Le cache d’annotations
Suivant
Autres optimisations
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana
Quiz
Amélioration des performances
La mise en cache de pages
L’autochargement des classes
Le cache avec Doctrine
Le cache d’annotations
Les sessions
Autres optimisations

 1. Choix de sa SAPI PHP


 a. Qu’est-ce qu’une SAPI ?
 b. Module du serveur
 c. CGI
 d. FastCGI
 e. Conclusion
 2. Mise en cache d’OPCodes
 a. Les OPCodes
 b. Une étape lourde
 c. La mise en cache
 3. La compression des réponses
 a. Compression gzip
 b. Précompression
 4. Optimisation des images
 a. Validation
 b. Expiration
 c. Autres techniques

Test des performances d'un site web


Quiz
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Autres optimisations
Les optimisations présentées jusqu’à présent sont pour la plupart spécifiques à
Symfony. Néanmoins, un projet sous ce framework n’échappe guère aux
optimisations plus génériques.
La suite de ce chapitre ne concernant pas spécifiquement Symfony, notre
approche sera orientée sur les concepts et la théorie, dans l’objectif de rester concis.
Nous vous suggérerons tout de même des outils adaptés tout au long de nos
explications.

1. Choix de sa SAPI PHP


a. Qu’est-ce qu’une SAPI ?
Une SAPI (Server Application Programming Interface) définit le mode de
communication entre le système et un programme PHP.
PHP dispose de plusieurs SAPI car il peut être utilisé dans une multitude
d’environnements, chacun ayant ses spécificités. Ainsi, les programmes en ligne de
commande utilisent la SAPI CLI tandis que les programmes à destination du Web
utilisent la SAPI CGI ou FastCGI, par exemple.
Selon la SAPI utilisée, PHP n’est pas invoqué de la même manière et comporte des
différences à l’exécution. En ligne de commande, vous pouvez par exemple passer des
arguments à vos scripts et les récupérer dans la variable $argv. En environnement
web, les variables superglobales $_GET et $_POST contiennent des informations sur
la requête HTTP. Ici, nous allons nous intéresser à l’environnement web et présenter
les différentes manières d’intégrer PHP à un serveur web.
b. Module du serveur
Cette technique d’intégration a longtemps été la plus populaire. Elle consiste à
« charger » PHP directement dans les processus du serveur web. C’est ce que fait par
exemple le mod_php d’Apache.
Si ce type d’intégration offre de bonnes performances, il présente tout de même un
inconvénient majeur : la gestion des permissions. Vos scripts PHP étant exécutés au
sein d’Apache, ils auront les mêmes permissions que l’utilisateur d’Apache. Cette
contrainte a pour conséquence de rendre la gestion des droits difficile, notamment
dans un environnement mutualisé.
Pour contourner ce problème, certains développeurs choisissent la simplicité en
accordant à l’utilisateur d’Apache plus de droits que nécessaire (cela peut aller
jusqu’à l’ajout de cet utilisateur au groupe root) et créent malheureusement des failles
de sécurité.
c. CGI
L’interface CGI (Common Gateway Interface) est un ensemble de spécifications
définissant la manière dont un serveur HTTP et un script dit « CGI » communiquent
entre eux pour générer une réponse à partir d’une requête d’un client.
Avec cette interface, le serveur web exécute un script CGI pour chaque requête, et le
script CGI renvoie la réponse à expédier au client ayant émis la requête. Les échanges
se font au travers de variables d’environnement et des flux standards.
Comme un script (php-cgi) est exécuté pour chaque requête, cette SAPI n’est pas très
performante et supporte généralement mal la montée en charge. De plus, le même
problème de permissions qu’avec le module du serveur se pose. Cependant, avec CGI,
il est très facilement corrigeable, grâce à des outils comme SuPHP ou SuExec.
d. FastCGI
Le protocole FastCGI est une extension de CGI ayant la particularité de redéfinir les
interactions entre le serveur web et le script CGI, de manière à améliorer les
performances de l’architecture.
Avec le protocole FastCGI, le script CGI ne doit être lancé qu’une fois ; les
communications entre ce dernier et le serveur web se font au travers d’un socket de
type TCP/IP ou UNIX.
PHP-FPM (FastCGI Process Manager) est une SAPI de PHP qui implémente le
protocole FastCGI. Avec cette SAPI, il n’y a plus de script CGI, mais un
gestionnaire de processus. Ce gestionnaire est chargé de maintenir un « pool » (ferme)
de processus PHP, appelés « workers » (travailleurs). Il transfère chaque requête à un
« worker » libre, récupère la réponse et la transmet au serveur web en respectant le
protocole FastCGI.
Ce gestionnaire entre le serveur web et les processus PHP garantit une maîtrise des
ressources utilisées ; il dispose de nombreuses directives de configuration capables,
entre autres, de spécifier l’utilisateur sous lequel doivent tourner les processus PHP
d’un pool donné.
e. Conclusion
Nous vous conseillons dans la mesure du possible, d’utiliser le protocole FastCGI au
travers de la SAPI PHP-FPM. Elle offre de très bonnes performances ainsi qu’une
sécurité optimale grâce à une gestion fine des permissions.

2. Mise en cache d’OPCodes


Le code machine (ou langage machine) est le seul langage que le processeur de votre
ordinateur est capable de traiter. Ce code contient des instructions binaires : une suite
de 0 et de 1.
Votre ordinateur ne peut donc pas directement exécuter des scripts qui utilisent des
langages comme le PHP ou le C, par exemple. Il y a forcément une étape
supplémentaire avant l’exécution.
Dans le cas du langage C, le script doit être compilé manuellement par le
développeur. En sortie, il récupère un exécutable (un fichier qui contient du code
machine) prêt à être exécuté par l’ordinateur.
Mais qu’en est-il du PHP ?
Contrairement au C, le PHP ne nécessite pas de phase de compilation manuelle par le
développeur. On dit de PHP qu’il est un langage interprété, en opposition aux
langages compilés.
En réalité, vos scripts PHP passent pourtant par une étape de compilation, elle est
simplement « automatique » : on parle de compilation à la volée.
Pour être tout à fait exact et sémantiquement correct, il est en fait ici question de «
semi-compilation » à la volée. Le code résultant de la compilation n’est pas du
langage machine, mais un langage intermédiaire : les OPCodes.
a. Les OPCodes
À chaque exécution d’un script PHP, votre code est transformé en OPCodes. Ce sont
des instructions de bas niveau qui sont très proches du langage machine.
Ces instructions sont ensuite exécutées par une machine virtuelle appelée le Zend
Engine.
b. Une étape lourde
Le problème posé par ce fonctionnement est que les OPCodes doivent être générés à
chaque exécution d’un même script. Cette phase de génération est assez lourde, car
elle implique des tâches coûteuses, comme l’analyse de votre code.
c. La mise en cache
Il existe une multitude de systèmes de cache d’OPCodes qui permettent de stocker en
mémoire partagée les OPCodes générés pour un script PHP.
Ainsi, lors des prochaines exécutions, le code PHP n’est pas recompilé, les
OPCodes sont directement récupérés depuis la mémoire partagée.
Cette technique de mise en cache permet un gain en performances très important,
nous vous recommandons de toujours l’utiliser en environnement de production.
Opcache
Opcache est le système de cache d’OPCodes inclus par défaut dans PHP depuis la
version 5.5.
Il est activé par défaut. Nous vous recommandons néanmoins de parcourir sa
documentation sur le site officiel de PHP pour ajuster la configuration selon vos
besoins : http://www.php.net/manual/fr/opcache.configuration.php

3. La compression des réponses


Diminuer la taille des réponses envoyées par votre application permet aux clients de
les récupérer plus rapidement, les données transitant sur le réseau étant allégées.
a. Compression gzip
Une des techniques pour réduire la taille d’une réponse est la compression gzip : avant
l’envoi, le serveur se charge de compresser la réponse, le client, de son côté, la
décompresse à la réception.
Avec Apache, vous devrez utiliser le module mod_deflate, ce sera le
module HttpGzipModule pour Nginx.
La compression gzip ne doit être appliquée qu’aux réponses avec du contenu textuel
(HTML, CSS, JavaScript, XML, etc.). Les contenus binaires (images, vidéos, PDF,
etc.) sont pour la plupart déjà compressés, les recompresser ne ferait que gâcher des
cycles CPU sans engendrer une réduction de taille de la réponse, elle pourrait même
être augmentée.
Note aux utilisateurs de Varnish
Si vous utilisez Varnish en tant que proxy inverse, vous devriez déléguer la
compression gzip à ce dernier plutôt qu’à votre serveur web. Cela vous mettra à l’abri
des cycles de compression/décompression inutiles. C’est notamment le cas lors de
l’utilisation d’Edge Side Includes : si la réponse est compressée par le serveur web,
Varnish devra la décompresser avant de pouvoir y lire les balises ESI.
b. Précompression
Pour un gain en taille optimal, nous vous conseillons d’effectuer
une précompression. En plus de compresser vos fichiers HTML, CSS et JavaScript
avec gzip, vous pouvez les précompresser en amont au niveau applicatif.
Cette précompression consiste à retirer certains caractères inutiles, comme des
espaces excédentaires ou des commentaires.
Pour les fichiers CSS et JavaScript, nous avons déjà vu qu’Assetic pouvait les
« minifier » grâce aux filtres.
Pour le contenu HTML, vous pouvez utiliser le tag Twig spaceless dans vos
templates.

4. Optimisation des images


Parmi les différents types de ressources servies par un site web, les images sont bien
souvent les plus volumineuses. Travailler sur l’optimisation des images est donc un
facteur important pour améliorer les performances de son application.
a. Validation
La plupart des serveurs web placent par défaut pour les images (ou plus largement
pour les fichiers statiques) les en-têtes de validation ETag et Last-Modified. La
valeur de l’ETag est une somme de contrôle (basée par exemple sur le nombre
d’octets et le nombre i-node du fichier), l’en-tête Last-Modified contient quant à lui la
date de dernière modification du fichier. Ce comportement par défaut permet d’éviter
des temps de réponse désastreux, en minimisant l’utilisation de la bande passante.
b. Expiration
Il est possible d’optimiser ses images plus efficacement grâce au modèle
d’expiration en plus de la validation. Les modules mod_expires (pour Apache)
ou ngx_http_headers_module (pour Nginx) permettent la génération des en-têtes
d’expiration Expires et Cache-Control pour vos images.
c. Autres techniques
Il existe également d’autres techniques pour optimiser ses images :
 Il est souvent possible de réduire la taille d’une image sans en affecter la qualité,
il existe des applications ou même des sites internet capables d’effectuer ce type
d’optimisation.
 L’utilisation de sprites peut également s’avérer intéressante. Un sprite est la
fusion de plusieurs images en une seule ; elles sont mises les unes à côté des
autres, telles les pièces d’un même puzzle. À l’affichage, des règles CSS sont
appliquées sur le sprite pour montrer uniquement la partie souhaitée à
l’utilisateur. Les sprites sont par exemple utiles pour des icônes ou des images qui
changent au passage du pointeur de la souris.
 Veillez à choisir le bon format pour vos images ; il est généralement admis que le
format JPEG est plutôt adapté aux photographies tandis que le
format PNG convient mieux aux éléments graphiques d’un site web.
Précédent
Les sessions
Suivant
Test des performances d'un site web
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana
Quiz
Amélioration des performances
La mise en cache de pages
L’autochargement des classes
Le cache avec Doctrine
Le cache d’annotations
Les sessions
Autres optimisations
Test des performances d'un site web

 1. Côté serveur
 a. Apache Bench
 b. Xhprof
 2. Côté client

Quiz
Internationalisation des applications Symfony
Développer une API REST avec Symfony
Annexes

Test des performances d’un site


web
Lors de la phase de développement, il est parfois délicat de juger de l’efficacité des
optimisations qui sont mises en place. Il n’est pas impossible que certaines d’entre
elles, une fois déployées en production, n’apportent aucun gain en performance, voire
fassent augmenter les temps de réponse de l’application.

1. Côté serveur
a. Apache Bench
Apache Bench est un outil qui mesure la capacité de votre serveur web à monter en
charge. Il permet de solliciter votre serveur web avec un grand nombre de requêtes.
Si vous utilisez le serveur web Apache, Apache Bench devrait déjà être installé sur
votre machine.
Si vous n’utilisez pas Apache et que vous êtes sur Ubuntu/Debian, vous pouvez le
récupérer en installant le paquet apache2-utils. Si vous êtes sur un autre système
d’exploitation, vous devrez installer Apache.
Utilisation
La commande suivante sollicite votre site web pendant 30 secondes (option -t) avec
un niveau de concurrence de 100 (option -c) :
ab -t 30 -c 100 http://www.mon-projet.demo/

Le niveau de concurrence indique le nombre de requêtes à soumettre en même temps.


Ici, le serveur traite 100 requêtes simultanément : pour chaque réponse renvoyée par
le serveur, Apache Bench soumet une nouvelle requête, de manière à ce qu’il soit
constamment en train de traiter 100 requêtes.
b. Xhprof
Xhprof est une extension PHP qui a été créée par Facebook. Elle fournit des
informations détaillées sur l’exécution d’un script PHP, fonction par fonction.
Grâce à Xhprof, vous pouvez générer des tableaux récapitulatifs indiquant, entre
autres, le nombres d’invocations et le temps d’exécution de chaque fonction/méthode
de vos scripts.
Cette extension vous permet de détecter d’éventuels points de blocage, des zones de
votre application gourmandes en ressources qui devraient être optimisées.

2. Côté client
L’outil Google PageSpeed Insights constitue un intéressant baromètre pour jauger
les performances d’une application web. Il vous suffit de vous rendre à l’adresse
suivante et de renseigner l’URL de votre
site : http://developers.google.com/speed/pagespeed/insights/
Cet outil agit comme un « client intelligent » ; il effectue une requête vers votre site et
analyse le contenu de la réponse, si elle est compressée, etc. Il affiche un compte-
rendu détaillé ainsi que des pistes d’optimisation à explorer.
Précédent
Autres optimisations
Suivant
Quiz
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana
Quiz
Amélioration des performances
La mise en cache de pages
L’autochargement des classes
Le cache avec Doctrine
Le cache d’annotations
Les sessions
Autres optimisations
Test des performances d'un site web
Quiz
Internationalisation des applications Symfony
Introduction

 1. Culture, internationalisation et régionalisation


 a. La culture (Locale)
 b. Internationalisation
 c. Régionalisation
 2. L’internationalisation dans Symfony

Détecter la culture d'un utilisateur


Activation des traductions
Les routes et les traductions
Les fichiers de traductions
Traduction d'un message
Quiz
Développer une API REST avec Symfony
Annexes

Introduction
1. Culture, internationalisation et régionalisation
Tout site web contient beaucoup de données textuelles qui sont destinées à être lues
par l’utilisateur. Si l’on retirait tout le code informatique de votre projet, il resterait
tout de même une partie non négligeable de messages, textes et contenus en français,
éparpillés entre vos templates, contrôleurs et bases de données, par exemple.
Si vous souhaitez transformer votre site web exclusivement en français en un site web
multilingue, les couches de l’application à modifier sont donc nombreuses. La tâche
peut rapidement devenir ardue, voire rendre votre projet illisible, si vous ne choisissez
pas les bons outils.
Heureusement, Symfony dispose d’un composant chargé de gérer les traductions d’un
site avec souplesse et simplicité. Avant de le découvrir, il convient tout de même de
définir certains termes.
a. La culture (Locale)
La culture (en anglais Locale) est un identifiant qui représente principalement la
langue de l’utilisateur, mais potentiellement d’autres paramètres linguistiques, comme
le pays.
La valeur de l’identifiant de culture est libre. Cependant, Symfony recommande
l’utilisation du code langue ISO639-1, puis optionnellement, d’un underscore et d’un
code pays ISO3166 Alpha-2.
Voici quelques exemples valides d’identifiants de culture :
 fr : un utilisateur français.
 fr_CA : un utilisateur français au Canada.
 en_US : un utilisateur anglais aux États-Unis.
 de : un utilisateur allemand.
b. Internationalisation
L’internationalisation (souvent abrégée i18n, car le mot commence par i, continue
avec 18 caractères et finit par n) désigne l’ensemble des processus qui préparent une
application à être traduite en plusieurs langues.
C’est ce concept que nous allons principalement détailler au cours de ce chapitre.
c. Régionalisation
La régionalisation (localization en anglais, souvent abrégée l10n) consiste à créer
une version d’un site (qui a été préalablement internationalisé) pour une culture
donnée.
Concrètement, cette étape consiste à créer des fichiers avec du texte traduit, ne
nécessite que peu de connaissances informatiques et est souvent réalisée par un
traducteur.

2. L’internationalisation dans Symfony


Il existe deux principales situations pour lesquelles vous aurez besoin du système de
traduction de Symfony :
 Intégration de bundle tiers : certains bundles tiers comportent du texte destiné à
être lu par les utilisateurs de l’application. Il est souvent par défaut en anglais,
mais beaucoup de bundles ont des fichiers de traduction prêts à l’emploi.
 Création d’un site web multilingue : cette situation est plus complexe à mettre en
place. Pour ce faire, vous devrez internationaliser et régionaliser votre
application.
Précédent
Quiz
Suivant
Détecter la culture d'un utilisateur
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana
Quiz
Amélioration des performances
La mise en cache de pages
L’autochargement des classes
Le cache avec Doctrine
Le cache d’annotations
Les sessions
Autres optimisations
Test des performances d'un site web
Quiz
Internationalisation des applications Symfony
Introduction
Détecter la culture d'un utilisateur

 1. Les techniques
 a. Négociation de contenu
 b. Par l’URL
 2. En pratique

Activation des traductions


Les routes et les traductions
Les fichiers de traductions
Traduction d'un message
Quiz
Développer une API REST avec Symfony
Annexes

Détecter la culture d’un utilisateur


1. Les techniques
a. Négociation de contenu
Le protocole HTTP supporte un mécanisme appelé la négociation de contenu. Il
permet de retourner différentes représentations (on parle de variantes) d’une ressource
en fonction d’un certain nombre de critères, dont la culture de l’utilisateur. Ces
critères sont envoyés dans des en-têtes HTTP au moment de la requête du client.
Cette technique peut cependant nuire au référencement de votre site ; elle est
d’ailleurs déconseillée par Google, qui préconise plutôt une URL distincte pour
chaque langue d’une page donnée.
b. Par l’URL
La seconde technique consiste à utiliser une URL par langue.
La culture de l’utilisateur peut être placée de différentes manières dans une URL ;
voici quelques exemples d’URL pour une page d’un site web en italien :
 http://mon-projet.it/ma-page : utilisation d’un domaine national de
premier niveau (ou ccTLD). Contrairement au domaine de premier niveau
générique (comme .com, .org, .net, etc.), le ccTLD est associé à un pays. Cette
structure d’URL est néanmoins assez contraignante ; elle vous force à acquérir un
nouveau nom de domaine pour chaque nouvelle langue que vous voulez
supporter. C’est pour cette raison qu’elle est plutôt réservée aux sites web à gros
budget.
 http://it.mon-projet.com/ma-page : utilisation d’un sous-domaine en complément
d’un domaine de premier niveau générique (ou gTLD). C’est le sous-domaine qui
contient la culture. Cette technique a l’avantage d’être plus économique que celle
précédemment évoquée.
 http://mon-projet.com/it/ma-page : plutôt qu’un sous-domaine, il est
possible d’utiliser un sous-répertoire. Cette technique est souvent plus simple à
mettre en place que celle du sous-domaine, qui peut nécessiter des modifications
de configuration supplémentaires (DNS et/ou serveur).

2. En pratique
Au cours de ce chapitre, nous utiliserons la technique de l’URL. C’est ce que
recommande également Symfony. Nous verrons d’ailleurs que le framework dispose
d’un paramètre spécial de routage très utile pour l’internationalisation : _locale.
Précédent
Introduction
Suivant
Activation des traductions
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana
Quiz
Amélioration des performances
La mise en cache de pages
L’autochargement des classes
Le cache avec Doctrine
Le cache d’annotations
Les sessions
Autres optimisations
Test des performances d'un site web
Quiz
Internationalisation des applications Symfony
Introduction
Détecter la culture d'un utilisateur
Activation des traductions

 1. Le composant translator
 2. Configuration du framework

Les routes et les traductions


Les fichiers de traductions
Traduction d'un message
Quiz
Développer une API REST avec Symfony
Annexes

Activation des traductions


1. Le composant translator
Pour mettre en place le système de traduction de Symfony, il est d’abord
nécessaire d’installer le composant translator. Si vous avez créé votre application en
utilisant le modèle d’application (symfony/website-skeleton) alors il est déjà installé ;
dans le cas contraire, il faut exécuter la recette Flex appropriée en utilisant la
commande :
composer require symfony/translation
2. Configuration du framework
Le système de traduction apporté par le composant translator fournit le fichier de
configuration config/packages/translation.yaml ; son contenu par défaut est le
suivant :
# config/packages/translation.yaml
framework:
default_locale: en
translator:
default_path: '%kernel.project_dir%/translations'
fallbacks:
en

On y trouve la locale par défaut (default_locale) ainsi que le répertoire par défaut des
traductions (default_path). Ce répertoire est le répertoire translations situé à la racine
de votre projet.
Une première adaptation importante de ce fichier consiste évidemment à ajuster la
locale par défaut de votre application : fr est une bonne valeur si vous souhaitez que
votre application soit nativement en français.
Il est également important de noter le paramètre fallbacks, qui permet d’indiquer une
liste de langues vers laquelle Symfony va se rabattre si jamais une traduction dans la
locale par défaut n’est pas trouvée.
# config/packages/translation.yaml
framework:
default_locale: fr
translator:
default_path: '%kernel.project_dir%/translations'
fallbacks:
en

Les traductions sont maintenant activées pour votre site. Pour l’instant, elles vous
permettent uniquement de traduire automatiquement en français le texte de bundles
tiers ainsi que d’avoir les messages d’erreur par défaut du composant validator.
Précédent
Détecter la culture d'un utilisateur
Suivant
Les routes et les traductions
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana
Quiz
Amélioration des performances
La mise en cache de pages
L’autochargement des classes
Le cache avec Doctrine
Le cache d’annotations
Les sessions
Autres optimisations
Test des performances d'un site web
Quiz
Internationalisation des applications Symfony
Introduction
Détecter la culture d'un utilisateur
Activation des traductions
Les routes et les traductions
Les fichiers de traductions
Traduction d'un message
Quiz
Développer une API REST avec Symfony
Annexes

Les routes et les traductions


Nous avons précédemment expliqué que pour créer un site web multilingue, il fallait
renseigner la culture de l’utilisateur dans l’URL. Symfony rend cela possible grâce au
paramètre spécial de routage _locale :
home:
path: /{_locale}/home
controller: App\DefaultController::home
requirements:
_locale: en|fr

Ici, nous définissons une route multilingue pour l’action home d’un de nos
contrôleurs. Cette action sera accessible par deux URL :
 http://mon-projet.local/en/home
 http://mon-projet.local/fr/home
En accédant à l’une ou l’autre de ces routes, Symfony place la culture de
l’utilisateur sur en ou fr, pour respectivement anglais et français.
Précédent
Activation des traductions
Suivant
Les fichiers de traductions
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana
Quiz
Amélioration des performances
La mise en cache de pages
L’autochargement des classes
Le cache avec Doctrine
Le cache d’annotations
Les sessions
Autres optimisations
Test des performances d'un site web
Quiz
Internationalisation des applications Symfony
Introduction
Détecter la culture d'un utilisateur
Activation des traductions
Les routes et les traductions
Les fichiers de traductions

 1. Organisation et règles de nommage


 a. Règle de nommage
 2. Outillage pour la création des fichiers de traduction
 a. Afficher la liste des traductions manquantes
 b. Générer un fichier de traduction

Traduction d'un message


Quiz
Développer une API REST avec Symfony
Annexes

Les fichiers de traductions


1. Organisation et règles de nommage
Les fichiers de traductions doivent être enregistrés dans le répertoire translations/ du
projet et doivent suivre des règles de nommage strictes.
a. Règle de nommage
Les noms des fichiers de traductions suivent une certaine
syntaxe : domaine.locale.format.
locale
La locale correspond à la culture des traductions contenues dans le fichier. Chaque
fichier ne peut contenir qu’une seule culture. En clair, vous ne pouvez pas définir des
traductions anglaises et françaises dans un même fichier.
format
Les fichiers de traductions de Symfony peuvent utiliser différents formats. Pour les
traductions des routes, nous avons utilisé YAML car c’est le plus simple à configurer,
mais il existe aussi XLIFF et PHP.
Le XLIFF est une extension du langage XML spécifique à la régionalisation ; ce
format est plus standard que le YAML mais beaucoup plus verbeux. Une fois que
vous serez familiarisé avec les traductions, nous vous recommandons de troquer le
YAML pour le XLIFF.
domaine
Le domaine est une manière d’organiser ses traductions ; il est en quelque sorte
l’équivalent de l’espace de noms pour les classes PHP : il permet principalement
d’éviter les conflits.
Le domaine par défaut est messages ; nous verrons plus loin comment spécifier le
domaine au moment de la traduction d’un message.
Exemples d’emplacements de fichiers de traductions
Pour illustrer nos précédentes explications, voici quelques exemples d’emplacements
valides pour vos fichiers de traductions :
 translations/routes.fr.yaml : les traductions de messages pour le
domaine routes et la culture fr ; le contenu est en YAML.
 translations/messages.en.yaml : les traductions de messages pour le
domaine messages et la culture en, toujours en YAML.

2. Outillage pour la création des fichiers de traduction


Afin de vous aider à la création des fichiers de traduction dans les différentes langues,
il est possible d’utiliser la commande de la console translation:update. Cette
commande dispose de plusieurs options, pour vous permettre de visualiser les
messages restant à traduire ou, plus intéressant, pour générer vos fichiers de
traduction.
La commande translation:update recherche les traductions manquantes dans :
 Les templates Twig stockés dans le répertoire templates/ (ou dans tout autre
répertoire défini dans les options de
configuration twig.default_path et twig.paths) ;
 Tout fichier/classe PHP qui injecte ou récupère le service translator et
effectue des appels à la méthode trans().
a. Afficher la liste des traductions manquantes
Avec l’option --dump-messages, la commande translation:update permet de lister
les messages qui ne disposent pas de traduction dans une langue donnée. Ainsi, pour
afficher tous les messages non traduits en français, vous pouvez utiliser :
php bin/console translation:update --dump-messages fr

Cette vérification s’applique aussi aux composants (bundles) que vous utilisez.
b. Générer un fichier de traduction
L’option --force de la commande translation:update permet la génération de fichiers
de traduction pour votre application. Par défaut, elle génère des fichiers au format
XLIFF, mais il est possible d’agir sur le format de sortie grâce à l’option --output-
format. Par exemple, pour générer le fichier de traduction en français au format
YAML pour l’application, on utilisera la commande :
php bin/console translation:update --force --output-format=yaml fr
En utilisant l’option --help, vous en apprendrez plus sur les autres options de cette
commande.
Précédent
Les routes et les traductions
Suivant
Traduction d'un message
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana
Quiz
Amélioration des performances
La mise en cache de pages
L’autochargement des classes
Le cache avec Doctrine
Le cache d’annotations
Les sessions
Autres optimisations
Test des performances d'un site web
Quiz
Internationalisation des applications Symfony
Introduction
Détecter la culture d'un utilisateur
Activation des traductions
Les routes et les traductions
Les fichiers de traductions
Traduction d'un message

 1. Le service translator
 2. Les paramètres de substitution (placeholders)
 3. Utilisation dans les templates Twig

Quiz
Développer une API REST avec Symfony
Annexes

Traduction d’un message


1. Le service translator
Le service translator sert à traduire des messages. Voici comment il doit être utilisé
depuis une action :
public function bonjour()
{
$message = $this->get('translator')->trans('Bonjour');

return new Response($message.'!');


}

Le service translator pourrait également être injecté en paramètre de l’action :


...
use Symfony\Contracts\Translation\TranslatorInterface;

...

public function bonjour(TranslatorInterface $translator)


{
$message = $translator->trans('Bonjour');
return new Response($message.'!');
}

Pour traduire le message en anglais par exemple, il faut créer le fichier de


traductions suivant :
# translations/messages.en.yaml

Bonjour: Hello
Si vous ouvrez cette page avec la culture en dans l’URL, vous verrez « Hello! »
s’afficher à l’écran.
Si ce n’est pas le cas, essayez de vider votre cache.
Le fichier utilise le domaine messages (et donc le fichier messages.en.yaml) car c’est
le domaine par défaut. Voici comment en utiliser un autre au moment de la
traduction :
$this->get('translator')->trans('Bonjour', array(), 'le_domaine');

Ici, c’est le domaine le_domaine qui sera pris en compte. Nous allons
maintenant nous pencher sur le rôle du tableau vide passé en deuxième paramètre.

2. Les paramètres de substitution (placeholders)


Les paramètres de substitution permettent de créer des messages dynamiques. Ils
sont semblables aux paramètres de substitution des routes.
Imaginons que vous souhaitiez afficher un message de bienvenue personnalisé sur
votre site, comme par exemple « Bonjour Miguel, bienvenue sur notre site. ». Le
prénom Miguel étant spécifique à l’utilisateur, il doit être modifié pour chaque
utilisateur du site. Il serait bien sûr possible de séparer le message à traduire en deux
parties : « Bonjour » et « bienvenue sur notre site » et de rassembler les morceaux en
ajoutant la partie dynamique au milieu, mais le code ne serait pas très lisible.
Pour ce type de cas, nous vous conseillons d’utiliser les paramètres de substitution :
public function bonjour(TranslatorInterface $translator)
{
$message = $translator->trans(
'Bonjour %nom%, bienvenue sur notre site.',
array('%nom%' => 'Miguel')
);

return new Response($message);


}

Le fichier de traduction contient alors :


# translations/messages.en.yaml

Bonjour %nom%, bienvenue sur notre site.: Hello %nom%, welcome to


our website.

Si la culture de l’utilisateur courant est en, le message est traduit, malgré sa partie
dynamique.

3. Utilisation dans les templates Twig


Dans les templates Twig, le tag trans est capable de traduire un message :
{% trans %}Bonjour{% endtrans %}

Pour traduire un message avec un paramètre de substitution, il faut qu’une variable du


même nom existe dans le contexte :
{% set nom = 'Miguel' %}
{% trans %}Bonjour %nom%, bienvenue sur notre site.{% endtrans %}!

Si vous souhaitez utiliser un domaine différent de « messages », vous devez le


spécifier comme ceci :
{% trans from "mon_domaine" %}Mon message{% endtrans %}
Précédent
Les fichiers de traductions
Suivant
Quiz
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana
Quiz
Amélioration des performances
La mise en cache de pages
L’autochargement des classes
Le cache avec Doctrine
Le cache d’annotations
Les sessions
Autres optimisations
Test des performances d'un site web
Quiz
Internationalisation des applications Symfony
Introduction
Détecter la culture d'un utilisateur
Activation des traductions
Les routes et les traductions
Les fichiers de traductions
Traduction d'un message
Quiz
Développer une API REST avec Symfony
Introduction à REST et concepts fondamentaux

 1. Les concepts de REST


 a. Les ressources
 b. Le changement d’état d’une ressource
 2. Architecture et protocole HTTP
 3. Les Single-Page Applications

La gestion du format JSON


Mise en place d’une API REST
Les objets de requête et de réponse
Tester une API REST
Quiz
Annexes

Introduction à REST et concepts


fondamentaux
Les applications web modernes utilisent presque systématiquement des appels à des
fonctionnalités distantes. Cette approche permet notamment de pouvoir bénéficier de
fonctionnalités riches et réutilisables mises à disposition sur Internet par des éditeurs.
Ainsi, intégrer une carte géographique, utiliser des fonctionnalités de paiement en
ligne ou encore intégrer les réseaux sociaux dans une application ou un site web est
largement facilité.
Les concepts sous-jacents sont ceux des services web. Historiquement articulés autour
du langage XML, cette approche s’est simplifiée avec l’arrivée des services en
architecture REST, permettant de s’affranchir de la lourdeur d’XML. Les services en
architecture REST permettent de mettre à disposition de véritables API utilisables et
invocables en HTTP sur Internet.

1. Les concepts de REST


REST est un acronyme signifiant REpresentational State Transfer. L’idée est de
placer une application dans un état de représentation de ses ressources en fonction de
l’action demandée. Cette architecture de construction d’application s’appuie sur le
protocole HTTP et sur la notion de ressource.
REST n’est pas à proprement parler un standard dans la mesure ou le W3C (World
Wide Web Consortium) ne le définit pas comme tel. Par contre, il utilise des standards
existants du W3C, tels que :
 le protocole HTTP ;
 les URL ;
 les types MIME.
a. Les ressources
REST part du principe qu’Internet est constitué de ressources uniques,
chacune d’entre elles étant associée à une URL elle-même unique. Ainsi, à l’URL
https://www.meteo.fr/paris se trouve associée une ressource représentant la météo à
Paris. Cette ressource peut prendre la forme physique d’une page HTML, d’un script
PHP ou bien d’une route Symfony.
b. Le changement d’état d’une ressource
Ainsi définies, les ressources sont sollicitées grâce à des requêtes HTTP. Ces requêtes
sont associées à une URL (pour identifier la ressource) ainsi qu’à une méthode
HTTP (on parle aussi de verbe HTTP). La méthode HTTP permet d’identifier
l’action que vous souhaitez faire faire à la ressource et donc de changer son état de
représentation. Selon que vous utilisez la méthode GET ou bien POST, l’état de la
ressource n’est pas le même.
C’est là toute la clé des architectures REST.

2. Architecture et protocole HTTP


Dans le contexte des architectures REST, le protocole HTTP n’est donc pas utilisé
pour échanger des pages entre le navigateur et un serveur web, mais pour échanger
des données applicatives entre une application cliente (qui pourrait être un navigateur)
et une application web hébergée sur un serveur ; cette application sert d’API.
Ces données applicatives peuvent être exprimés dans différents formats (texte brut,
XML, JSON…) et peuvent être transmises :
 dans la requête : quand l’application cliente souhaite envoyer des données vers
l’API, pour créer une ressource par exemple ;
 dans la réponse : quand l’API renvoie un résultat suite à une invocation de
l’application cliente.

D’un point de vue des technologies, l’application cliente peut revêtir plusieurs formes.
Il peut s’agir d’une application mobile sur un smartphone ou une tablette, d’une
application de bureau déployée sur un poste de travail utilisateur ou bien encore d’un
site web distant.
3. Les Single-Page Applications
Une grande majorité des applications et sites web actuels utilisent une approche un
peu différente de l’approche MVC évoquée tout au long de cet ouvrage ; ce sont les
Single-Page Applications. Le principe repose sur l’usage d’une structure HTML
unique pour afficher toutes les informations. Cette structure HTML est
dynamiquement modifiée par du code JavaScript en fonction des actions de
l’utilisateur. Le changement de page est donc transparent pour l’utilisateur (il ne
constate pas de rafraîchissement complet des pages), car seules les données devant
être mises à jour sont modifiées dans la structure HTML.
Ces Single-Page Applications utilisent massivement des frameworks basés sur le
langage JavaScript afin d’optimiser le développement côté client : on parle de
développement FrontEnd.
Ainsi toute la logique de modification de cette structure HTML est pilotée par des
frameworks tels qu’Angular, VueJS ou React. Ils se chargent également d’apporter
des éléments d’interface graphique évolués, simplifiant ainsi la conception des pages.
Afin d’obtenir les données, ces frameworks vont solliciter la plupart du temps une
API REST mise à disposition sur un serveur web, comme illustré précédemment, ce
sont donc des clients à part entière.
Des composants émettent des requêtes HTTP à destination de l’API et reçoivent des
réponses encapsulant les données (souvent en JSON), ces données servant ensuite à
mettre à jour la structure HTML de la page.

Précédent
Quiz
Suivant
La gestion du format JSON
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants
 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana
Quiz
Amélioration des performances
La mise en cache de pages
L’autochargement des classes
Le cache avec Doctrine
Le cache d’annotations
Les sessions
Autres optimisations
Test des performances d'un site web
Quiz
Internationalisation des applications Symfony
Introduction
Détecter la culture d'un utilisateur
Activation des traductions
Les routes et les traductions
Les fichiers de traductions
Traduction d'un message
Quiz
Développer une API REST avec Symfony
Introduction à REST et concepts fondamentaux
La gestion du format JSON

 1. Présentation du format JSON


 a. Représentation des données en JSON
 b. Types de données
 c. Structures
 2. Le support de JSON en PHP
 3. Et dans Symfony ?

Mise en place d’une API REST


Les objets de requête et de réponse
Tester une API REST
Quiz
Annexes

La gestion du format JSON


Le format JSON est une constituante indispensable des architectures REST. Bien que
ces architectures puissent utiliser n’importe quel format d’échange, JSON n’en reste
pas moins celui qui est largement plébiscité grâce à sa simplicité et à sa légèreté.

1. Présentation du format JSON


Le format JSON (JavaScript Object Notation) est un format de données textuel
inspiré de la notation des objets JavaScript, créé à partir de 2002 par Douglas
Crockford. L’objectif du format JSON est de faciliter les échanges de données
applicatives sur le Web, échanges qui étaient auparavant systématiquement basés sur
XML. Ce dernier étant jugé trop verbeux et donc consommateur de bande
passante, JSON constitue une alternative plus légère pour ces échanges.
a. Représentation des données en JSON
JSON représente les données sous forme d’objets. Ils sont constitués d’attributs
exprimés grâce à des paires clé/valeur, la clé correspondant au nom de l’attribut et la
valeur à la donnée associée. Cette donnée peut être un numérique, une chaîne de
caractères, un autre objet ou bien un tableau.
Les clés sont des chaînes de caractères et il est nécessaire de les exprimer entre
guillemets.
Les valeurs sont séparées des clés par le caractère deux points (:) et chaque couple
clé/valeur est séparé du suivant par une virgule (,).
Exemple de structure :
...
"clé1": "Valeur de clé1",
"clé2": "Valeur de clé2"
...

b. Types de données
Les types de données définis par JSON sont :
 les chaînes de caractères : elles sont exprimées en Unicode et entre guillemets,
 les numériques : ce sont des nombres entiers ou décimaux,
 les booléens : ils sont exprimés avec les mots réservés true et false,
 les objets : ils représentent la base de la notation de JSON,
 les tableaux : ce sont des ensembles de valeurs des types précédents.
c. Structures
Les paires de clé/valeur de JSON sont exprimées entre accolades ({}), c’est la
manière de représenter un objet. Ainsi, si l’on souhaite exprimer les données d’un
objet personne qualifié par un nom, un prénom et un âge, on peut utiliser la structure
suivante :
{
"nom": "DUPONT",
"prenom": "Robert",
"age": 56
}

Dans cet exemple, les valeurs pour le nom et le prénom sont des chaînes de
caractères, l’âge est un entier.
Les tableaux sont, quant à eux, exprimés entre crochets ([]), comme par exemple :
...
"clé1": [ "Première valeur de clé1", "Deuxième valeur de clé1" ]
...

Bien évidemment, les valeurs de tableaux peuvent elles-mêmes être des tableaux ou
bien des objets. Prenons l’exemple d’une structure complexe qui représenterait un
objet client possédant des attributs sur son identité, ainsi qu’un attribut comptes ayant
comme valeur un tableau d’objets compte.
On pourrait représenter la structure de la manière suivante :
{
"id": 1,
"nom": "DUPONT",
"prenom": "Robert",
"adresse": "40 rue de la Paix",
"codePostal": "75000",
"ville": "Paris",
"comptes": [
{
"numero": 245646786,
"solde": 8300.0
},
{
"numero": 263434345,
"solde": 20100.0
}
]
}

2. Le support de JSON en PHP


Dans le langage PHP, la manipulation de données en JSON s’articule essentiellement
autour de deux fonctions : json_encode() et json_decode().
La fonction json_encode() permet de transformer une structure de données PHP en
flux JSON. Elle s’appuie sur des données simples (chaînes de caractères, entiers,
réels) ou plus souvent sur des tableaux associatifs ou des objets.
La fonction json_decode() permet l’inverse : elle crée une structure de données PHP
à partir d’un flux JSON. Selon la valeur de son deuxième paramètre, cette structure
est un tableau associatif ou bien un objet créé à partir de la stdClass de PHP.
Bien que ces fonctions soient très utiles en PHP, elles n’en restent pas moins limitées.
En effet, PHP ne propose pas nativement de gestion d’une réponse HTTP au format
JSON ; ces fonctions ne font que gérer des flux JSON. Elles n’ont donc que peu
d’intérêt dans une application Symfony.

3. Et dans Symfony ?
Dans Symfony le support de JSON est natif. Il permet aussi bien de manipuler des
flux que de gérer des requêtes et des réponses dans ce format. Symfony utilise pour
cela un sérialiseur.
Un sérialiseur (serializer en anglais) est un outil logiciel permettant de réaliser des
opérations de sérialisation et désérialisation.
La sérialisation est le mécanisme permettant de transformer en flux de données la
structure d’un objet logiciel résidant en mémoire afin de pouvoir stocker ce flux dans
un fichier ou bien de le transporter sur le réseau. La désérialisation est l’opération
inverse : on construit l’état d’un objet en mémoire à partir d’un flux de données
provenant d’un fichier ou bien du réseau.
Symfony vient avec un sérialiseur par défaut ; ce dernier est capable de gérer les
formats XML, YAML, CSV et évidemment JSON.
Précédent
Introduction à REST et concepts fondamentaux
Suivant
Mise en place d’une API REST
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments
Tout déplier | Tout replier
Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana
Quiz
Amélioration des performances
La mise en cache de pages
L’autochargement des classes
Le cache avec Doctrine
Le cache d’annotations
Les sessions
Autres optimisations
Test des performances d'un site web
Quiz
Internationalisation des applications Symfony
Introduction
Détecter la culture d'un utilisateur
Activation des traductions
Les routes et les traductions
Les fichiers de traductions
Traduction d'un message
Quiz
Développer une API REST avec Symfony
Introduction à REST et concepts fondamentaux
La gestion du format JSON
Mise en place d’une API REST

 1. Le service serializer
 a. Sérialiser des données
 b. Désérialiser des données
 2. Adaptation des contrôleurs

Les objets de requête et de réponse


Tester une API REST
Quiz
Annexes

Mise en place d’une API REST


La mise en place d’une API REST avec Symfony est une chose relativement
aisée dans la mesure où le framework propose nativement tous les outils
nécessaires. Nous avons déjà vu au cours de cet ouvrage le développement des
contrôleurs et de leurs actions et comment les associer à des URL ; cela permet de
structurer l’API.
De plus, la prise en charge du format JSON pour l’échange des données nous permet
de facilement gérer ce format dans la requête comme dans la réponse.

1. Le service serializer
Avant de nous permettre de manipuler des données en JSON, il est nécessaire
d’installer le sérialiseur Symfony. Ce sérialiseur sera ensuite disponible sous forme
d’un service injectable dans les classes de votre application.
Pour installer ce service nommé serializer, il faut exécuter une recette Flex avec la
commande suivante :
composer require symfony/serializer-pack

Une fois installé, le service est injectable dans les actions de contrôleur via
l’interface Symfony\Component\Serializer\SerializerInterface :
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Serializer\SerializerInterface;

class DefaultController extends AbstractController


{
public function index(SerializerInterface $serializer)
{
...
}
}

a. Sérialiser des données


Pour sérialiser des données en JSON, nous allons utiliser la méthode serialize() du
service serializer. Cette méthode prend en premier paramètre la variable représentant
les données à sérialiser et en second paramètre le format de sérialisation. On pourra
ainsi facilement sérialiser en JSON une entité Doctrine récupérée en amont, comme
dans l’exemple de code ci-dessous :
public function index(SerializerInterface $serializer)
{
$article = // Récupération d'une entité Article avec Doctrine...
$jsonArticle = $serializer->serialize($article, 'json');
}

b. Désérialiser des données


Pour réaliser l’opération inverse, on utilise la méthode deserialize() du même service.
Cette méthode prend trois paramètres :
 le flux de données à désérialiser ;
 le type d’objet dans lequel désérialiser les données ;
 le format de données reçu.
Par exemple, en supposant que le flux de données JSON entrant soit représentatif
d’une entité Article, nous pouvons désérialiser le flux de la manière suivante :
public function index(SerializerInterface $serializer)
{
$json = // Récupération du flux JSON entrant...
$article = $serializer->deserialize(
$json,
Article::class,
'json'
);
}

La variable $article est alors une référence sur un objet de type Article.

2. Adaptation des contrôleurs


Les contrôleurs que nous avons développés jusqu’à présent avaient tous pour
objectif final de faire le rendu d’une vue. Dans le contexte d’une API REST, ils vont
devoir retourner un flux de données au travers d’un objet Symfony\Component\
HttpFoundation\Response, il n’y aura donc pas d’usage de la
méthode render() pour la génération d’un template Twig.
Concernant la mise en place de l’API, nous allons ici évidemment utiliser
l’annotation @Route afin d’associer les actions à des URL. L’attribut methods sera
systématiquement présent afin de spécifier quelle méthode HTTP devra être utilisée
pour solliciter l’action.
/**
* @Route(
* "/api/articles",
* name="api_article_tous",
* methods={"GET"}
* )
*/
public function tous(): Response
{
...
}

Tout le principe d’exposition de fonctionnalités dans une API REST repose donc sur
l’usage de cette annotation.
Précédent
La gestion du format JSON
Suivant
Les objets de requête et de réponse
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana
Quiz
Amélioration des performances
La mise en cache de pages
L’autochargement des classes
Le cache avec Doctrine
Le cache d’annotations
Les sessions
Autres optimisations
Test des performances d'un site web
Quiz
Internationalisation des applications Symfony
Introduction
Détecter la culture d'un utilisateur
Activation des traductions
Les routes et les traductions
Les fichiers de traductions
Traduction d'un message
Quiz
Développer une API REST avec Symfony
Introduction à REST et concepts fondamentaux
La gestion du format JSON
Mise en place d’une API REST
Les objets de requête et de réponse

 1. Le contenu et les en-têtes de requête


 2. Manipulation de la réponse avec Response et JsonResponse
 a. Renvoyer une réponse au format JSON
 3. Les codes de réponse HTTP dans une API REST
 a. Problématique de l’état de la réponse
 b. Expression de la réponse avec HTTP
 c. Mise en œuvre

Tester une API REST


Quiz
Annexes

Les objets de requête et de réponse


Les objets Symfony représentant la requête et la réponse HTTP ont déjà été largement
présentés et utilisés dans cet ouvrage. Pour rappel, ils sont respectivement des
types Symfony\Component\HttpFoundation\Request et Symfony\Component\
HttpFoundation\Response.
Dans les actions des contrôleurs REST, la requête est disponible sous forme d’un
paramètre injecté par Symfony dans la méthode, et la réponse constitue le type de
retour de ces méthodes, comme dans le fragment de code suivant :
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

...

public function creer(Request $request, SerializerInterface


$serializer): Response
{
...
}

L’objet de requête n’est pas systématiquement nécessaire. En effet, il n’est utile que
lorsque des données seront transportées dans le corps de la requête, typiquement sur
des actions liées aux méthodes HTTP PUT et POST.
1. Le contenu et les en-têtes de requête
Lors de la manipulation des données entrantes, il est nécessaire de pouvoir
récupérer les en-têtes et le corps de la requête.
Les en-têtes contiennent notamment le type de données envoyé par le client. Cette
information est accessible via la méthode getContentType().
Dans le cas où des données JSON sont transmises dans la requête, elles le sont dans le
corps ; ce contenu est accessible avec la méthode getContent().
Voici comment pourrait être structuré le code d’une action recevant un flux JSON en
HTTP POST :
/**
* @Route(
* "/api/article",
* name="api_article_creer",
* methods={"POST"}
* )
*/
public function creer(
Request $request, SerializerInterface $serializer
): Response
{
// On contrôle le format de données entrant...
if($request->getContentType() ==='application/json') {
// On récupère le contenu de la requête...
$data = $request->getContent();
// On désérialise en JSON...
$article = $serializer->deserialize(
$data,
Article::class,
"json"
);

// Renvoi d'une réponse...


} else {
// Renvoi d'un code d'erreur...
}
}
2. Manipulation de la réponse avec Response et
JsonResponse
Envoyer une réponse dans notre API REST Symfony consiste simplement à créer une
instance de la classe Symfony\Component\HttpFoundation\Response, à y
encapsuler les données à retourner, puis à retourner l’objet en fin d’action. C’est
Symfony qui se charge de transformer l’objet en flux de réponse HTTP.
Lors de la construction d’un objet de réponse, nous passons au constructeur :
 le contenu à renvoyer ;
 le code de réponse HTTP ;
 les en-têtes de la réponse, notamment le type MIME du contenu renvoyé.
Par exemple, suite à la création réussie d’une ressource, nous renvoyons un message
en JSON avec le code HTTP 200 (OK) ; cela se fait avec le code suivant :
use Symfony\Component\HttpFoundation\Response;

...

/**
* @Route(
* "/api/article/{id}",
* name="api_article_lire",
* methods={"GET"}
* )
*/
public function lire($id): Response
{
$article = // Récupération d'un article par son id...

$articleJSON = $this->serializer->serialize($article, "json");

return new Response(


$articleJSON,
Response::HTTP_OK,
[ 'Content-Type' => 'application/json' ]
);
}
La classe Response de Symfony possède des constantes correspondant aux différents
codes de réponse HTTP possibles.
a. Renvoyer une réponse au format JSON
Bien que l’exemple de code précédent soit tout à fait correct, il peut être amélioré. En
effet, dans la mesure où nous renvoyons systématiquement des données au format
JSON, il est assez répétitif de spécifier à chaque fois le type MIME renvoyé.
Pour simplifier l’envoi d’une réponse au format JSON, Symfony propose une classe
qui hérite de la classe Response, la classe Symfony\Component\HttpFoundation\
JsonResponse. Cette classe part du principe que le contenu retourné est
systématiquement du JSON et elle positionne automatiquement l’en-tête Content-
Type à la valeur application/json. De plus, la classe JsonResponse encode
automatiquement les données en JSON. Il n’est donc plus nécessaire de passer par le
sérialiseur pour préparer les données.
Le code précédent peut donc être simplifié comme ceci :
use Symfony\Component\HttpFoundation\JsonResponse;

...

/**
* @Route(
* "/api/article/{id}",
* name="api_article_lire",
* methods={"GET"}
* )
*/
public function lire($id): Response
{
$article = // Récupération d'un article par son id...

return new JsonResponse(


$article, // L'objet est sérialisé automatiquement !
Response::HTTP_OK
);
}

3. Les codes de réponse HTTP dans une API REST


a. Problématique de l’état de la réponse
D’un point de vue d’un client d’une API REST, le contenu renvoyé dans la
réponse est évidemment l’information principalement attendue. Cependant des
problèmes peuvent survenir lors de l’invocation d’un traitement sur l’API ; cela peut
être dû à des informations erronées transmises par le client ou bien à une erreur
d’exécution côté serveur.
Classiquement, dans une application PHP, on génère des exceptions pour notifier du
problème. Dans le cas d’une API REST, cela n’est pas possible, tout simplement
parce que le protocole HTTP ne permet pas de renvoyer des exceptions.
b. Expression de la réponse avec HTTP
L’expression du bon fonctionnement du traitement ou, au contraire d‘une
erreur, devra passer par l’utilisation des codes de réponse HTTP.
Un résultat d’exécution correct pour être exprimé avec les codes HTTP de type 2XX.
Les codes de réponse HTTP de type 4XX représentent des erreurs liées au client alors
que les codes de type 5XX représentent des erreurs liées au serveur (dans ce contexte,
à l’application Symfony). En voici quelques-uns pour rappel :
c. Mise
Code Message Explication
en
œuvre
200 OK Requête traitée avec succès. La réponse
dépendra de la méthode de requête utilisée. Lorsque
les
actions
201 Created Requête traitée avec succès et création d’un
des
document.

204 No Content Requête traitée avec succès mais pas


d’information à renvoyer.

400 Bad Request La syntaxe de la requête est erronée.

401 Unauthorized Une authentification est nécessaire pour


accéder à la ressource.

402 Payment Paiement requis pour accéder à la ressource.


Required

403 Forbidden Les droits d’accès ne permettent pas au client


d’accéder à la ressource.

404 Not Found Ressource non trouvée.

405 Method Not Méthode de requête non autorisée.


Allowed

500 Internal Server Erreur interne du serveur.


Error

501 Not Fonctionnalité réclamée non supportée par le


Implemented serveur.

503 Service Service temporairement indisponible ou en


Unavailable maintenance.
contrôleurs invoquent des services applicatifs, il y a de forts risques que des
exceptions soient à gérer. Dans le cas où tout se passe correctement, on renvoie le flux
de réponse attendu associé à un code 2XX.
Dans le cas où une exception est levée, il faut au contraire prévoir une réponse
associée à un message d’erreur, ainsi qu’un code HTTP approprié de
type 4XX ou 5XX.
Prenons le scénario suivant : une action d’un contrôleur est sollicitée en HTTP GET
sur l’URI /article/{id}, elle renvoie un article en fonction de la valeur d’id transmise
dans l’URL. Voici le code permettant d’implémenter le bon fonctionnement de ce
traitement ainsi que le cas où l’article n’est pas trouvé :
/**
* @Route(
* "/article/{id}",
* methods={"GET"}
* )
*/
public function lire(int $id): Response
{
try {
$article = // Récupération de l'article par son id via
// service...
return new JsonResponse(
$article,
Response::HTTP_OK
);
}
catch(\Exception $e) {
return new JsonResponse(
[ 'message' => $e->getMessage() ],
Response::NOT_FOUND
);
}
}

En cas d’exception, on renvoie un code d’erreur 404 (NOT FOUND) associé à un


message JSON contenant le message de l’exception d’origine ; dans ce cas, la
structure renvoyée serait de cette forme :
{
"message": "Article introuvable !"
}
Précédent
Mise en place d’une API REST
Suivant
Tester une API REST
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI
Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana
Quiz
Amélioration des performances
La mise en cache de pages
L’autochargement des classes
Le cache avec Doctrine
Le cache d’annotations
Les sessions
Autres optimisations
Test des performances d'un site web
Quiz
Internationalisation des applications Symfony
Introduction
Détecter la culture d'un utilisateur
Activation des traductions
Les routes et les traductions
Les fichiers de traductions
Traduction d'un message
Quiz
Développer une API REST avec Symfony
Introduction à REST et concepts fondamentaux
La gestion du format JSON
Mise en place d’une API REST
Les objets de requête et de réponse
Tester une API REST

 1. Les limites du navigateur web


 2. Les outils
 a. Postman
 b. SOAP UI

Quiz
Annexes

Tester une API REST


1. Les limites du navigateur web
Dans le cadre du développement d’une application web traditionnelle utilisant des
vues, il est assez simple de constater le rendu des pages grâce à son navigateur web.
Pour une API REST c’est différent. En effet, nous pourrons toujours l’utiliser pour
tester les actions qui réagissent sur des requêtes HTTP de type GET, mais pour les
requêtes POST, PUT et DELETE c’est compliqué, le navigateur n’est pas prévu pour
cela.
Bien sûr, nous pouvons créer des tests fonctionnels s’appuyant sur PHPUnit et les
objets Client et Crawler, comme évoqué dans le chapitre Tester son
application Symfony, mais il est tout de même pratique de pouvoir consulter les
réponses renvoyées par l’API dans les phases de développement. Nous allons pour
cela utiliser des outils dédiés au test d’API REST.

2. Les outils
Les outils de test des API REST ne sont ni plus ni moins que des générateurs de
requêtes HTTP permettant de recevoir et d’explorer des réponses HTTP. En véritables
outils pour le développeur, ils permettent de regrouper les requêtes en projets ainsi
que de spécifier les éventuels en-têtes et contenu de ces requêtes.
a. Postman
Postman est un outil développé par Google et utilisable gratuitement. Il nécessite un
compte Google pour être utilisé ce qui permet de sauvegarder ses projets et de pouvoir
y accéder depuis partout.
Postman peut être utilisé en ligne si votre application est déjà déployée et
disponible sur Internet, ou bien il peut être téléchargé et installé localement sur votre
machine de développement. Le site officiel de Postman est disponible à
l’adresse https://www.postman.com. Le téléchargement de l’application autonome se
fait à partir de https://www.postman.com/downloads/.
Une fois téléchargée et installée, l’application vous demande de vous connecter grâce
à votre compte Google. Quand cela est fait, l’interface de Postman apparaît.
Postman organise les différentes requêtes de test dans des collections, il faut donc
commencer par créer une collection pour votre API. Pour cela, cliquez sur
l’icône Collections dans la barre située à gauche, puis sur le petit bouton + ; la
nouvelle collection apparaît et vous pouvez la nommer à votre guise.

Création et nommage d’une nouvelle collection.


Une fois la collection créée, il faut ajouter une première requête. Pour cela,
cliquez sur le lienAdd a request situé dans la collection ; la nouvelle requête apparaît.
Vous pouvez la nommer et la configurer en précisant :
 la méthode HTTP ;
 l’URL ;
 éventuellement le contenu de la requête.
Voici un exemple de construction de requête HTTP POST avec des données JSON
ainsi que la réponse obtenue après avoir cliqué sur le bouton Send :
Il suffit ensuite de procéder de la même manière pour la création des autres requêtes
de test de votre API. Postman est un outil incontournable pour les développeurs d’API
REST, et ce quel que soit le langage de programmation sous-jacent.
b. SOAP UI
Même si Postman fait référence dans le domaine du test d’API REST, d’autres outils
peuvent être utilisés. Parmi eux, il y a SOAP UI.
Historiquement dédié au test des services web XML (basés sur le protocole SOAP) il
permet également de tester les API REST. SOAP UI est open source (et gratuit). Site
officiel : https://www.soapui.org
Précédent
Les objets de requête et de réponse
Suivant
Quiz
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments
Tout déplier | Tout replier
Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana
Quiz
Amélioration des performances
La mise en cache de pages
L’autochargement des classes
Le cache avec Doctrine
Le cache d’annotations
Les sessions
Autres optimisations
Test des performances d'un site web
Quiz
Internationalisation des applications Symfony
Introduction
Détecter la culture d'un utilisateur
Activation des traductions
Les routes et les traductions
Les fichiers de traductions
Traduction d'un message
Quiz
Développer une API REST avec Symfony
Introduction à REST et concepts fondamentaux
La gestion du format JSON
Mise en place d’une API REST
Les objets de requête et de réponse
Tester une API REST
Quiz
Annexes
Créer une commande pour la console

 1. La configuration d’une commande


 2. Les objets input et output
 3. Le Service Container
 4. Commande d’exemple

Envoyer des e-mails grâce à SwiftMailer


Travailler avec les sessions
Déployer une application symfony

Créer une commande pour


la console
Pour intégrer vos propres commandes à la console Symfony, vous devez les placer au
sein d’un dossier Command de vos bundles. Le nom du fichier doit se terminer par
« Command » (par exemple, MeteoCommand) et étendre la classe Symfony\Bundle\
FrameworkBundle\Command\ContainerAwareCommand.
Les commandes comportent deux méthodes principales : configure() et execute().

1. La configuration d’une commande


La méthode configure() définit, entre autres, le nom, la description, les arguments et
les options de la commande.
<?php

namespace App\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

class DemoCommand extends ContainerAwareCommand


{
protected function configure()
{
$this
->setName('eni:command')
->setDescription('Description de la commande.')
->addArgument('mon_argument')
->addOption('mon_option')
;
}

protected function execute(InputInterface $input,


OutputInterface $output)
{
$output->writeln('Ceci est une commande de test.');
}
}
Ignorons pour l’instant le contenu de la méthode execute(). La
méthode configure() spécifie le nom de la commande, sa description, ainsi qu’un
argument et une option. Ceci a un impact sur la manière dont elle peut être lancée.
Toutes les instructions suivantes sont valides :
php bin/console eni:command
php bin/console eni:command valeur_argument
php bin/console eni:command valeur_argument --mon-option

2. Les objets input et output


La méthode execute() comporte la logique de la commande. Elle est invoquée avec
deux arguments, un objet input et un objet output. Ces derniers sont comparables à la
requête et à la réponse du composant HttpFoundation, mais dans l’environnement
CLI.
L’objet input contient les informations liées à l’entrée standard (STDIN). Il contient
aussi des informations comme les arguments et options passés lors de l’invocation de
la commande. L’objet output est une représentation de la sortie de la commande
(STDOUT et STDERR).
Voici comment récupérer arguments et options avec l’objet input :
namespace App\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

class DemoCommand extends ContainerAwareCommand


{
protected function configure()
{
$this
->setName('eni:command')
->setDescription('Description de la commande.')
->addArgument('mon_argument')
->addOption('mon_option')
;
}
protected function execute(InputInterface $input,
OutputInterface $output)
{
if ($valeur = $input->getArgument('mon_argument')) {
$output->writeln('Mon argument vaut '. $valeur);
}

if ($input->hasOption('mon_option')) {
$output->writeln('Mon option a été définie.');
}
}
}

Nous utilisons également l’objet output pour écrire dans la sortie standard : la
méthode writeln() écrit, en ajoutant un saut de ligne automatiquement à la fin.
Pour écrire dans la sortie d’erreur standard, vous devez procéder comme ci-dessous :
namespace App\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

class DemoCommand extends ContainerAwareCommand


{
protected function configure()
{
$this
->setName('eni:command')
->setDescription('Description de la commande.')
;
}

protected function execute(InputInterface $input,


OutputInterface $output)
{
$output->getErrorOutput()->writeln('Mon erreur.');
}
}

3. Le Service Container
Tout comme les contrôleurs, les commandes sont capables d’accéder au
Service Container. Ceci vous permet de travailler avec vos services, donc de persister
des données en base de données, d’écrire des logs, etc.
Pour accéder au Service Container, vous devez invoquer la
méthode getContainer() de la classe parente de votre commande (à savoir Symfony\
Bundle\FrameworkBundle\Command\ContainerAwareCommand) :
namespace App\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

class DemoCommand extends ContainerAwareCommand


{
protected function configure()
{
$this
->setName('eni:command')
->setDescription('Description de la commande.')
;
}

protected function execute(InputInterface $input,


OutputInterface $output)
{
$container = $this->getContainer();
$doctrine = $container->get('doctrine');
// persister des données...
// ...
}
}

4. Commande d’exemple
À titre d’exemple, voici une commande affichant les températures minimale et
maximale à Paris, grâce à l’API météorologique de Yahoo
(http://developer.yahoo.com/weather/) :
namespace App\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class MeteoCommand extends ContainerAwareCommand


{
protected function configure()
{
$this
->setName('eni:meteo:paris')
->setDescription('Affiche les températures minimale
et maximale de la journée pour Paris.')
;
}

protected function execute(InputInterface $input,


OutputInterface $output)
{
$rss =
simplexml_load_file('http://weather.yahooapis.com/forecastrss?
w=615702&u=c');

$aujourdhui = $rss
->xpath('/rss/channel/item/yweather:forecast[1]')
;

$output->writeln(sprintf(
'Aujourd\'hui, la température oscille entre %s et %s
degrés.',
(string) $aujourdhui[0]['low'],
(string) $aujourdhui[0]['high']
));

}
}
Précédent
Quiz
Suivant
Envoyer des e-mails grâce à SwiftMailer
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana
Quiz
Amélioration des performances
La mise en cache de pages
L’autochargement des classes
Le cache avec Doctrine
Le cache d’annotations
Les sessions
Autres optimisations
Test des performances d'un site web
Quiz
Internationalisation des applications Symfony
Introduction
Détecter la culture d'un utilisateur
Activation des traductions
Les routes et les traductions
Les fichiers de traductions
Traduction d'un message
Quiz
Développer une API REST avec Symfony
Introduction à REST et concepts fondamentaux
La gestion du format JSON
Mise en place d’une API REST
Les objets de requête et de réponse
Tester une API REST
Quiz
Annexes
Créer une commande pour la console
Envoyer des e-mails grâce à SwiftMailer

 1. Le protocole SMTP
 2. Le transport
 a. Le transport smtp
 b. Le transport sendmail
 3. Envoi d’un e-mail

Travailler avec les sessions


Déployer une application symfony

Envoyer des e-mails grâce


à SwiftMailer
L’édition standard de Symfony intègre par défaut une librairie pour l’envoi d’e-
mails : SwiftMailer.
Nous allons voir au cours de cette annexe comment SwiftMailer peut faciliter l’envoi
d’e-mails depuis vos applications Symfony.

1. Le protocole SMTP
Avant de rentrer dans le vif du sujet avec la configuration et l’utilisation de
SwiftMailer, il est important de présenter le protocole SMTP.
Le protocole SMTP (Simple Mail Transfer Protocol) est le protocole de
communication standard utilisé pour l’envoi d’e-mails. Son objectif est de définir les
mécanismes par lesquels une application (appelée MUA, pour Mail User Agent)
soumet un e-mail à un serveur de messagerie (appelé MTA, pour Mail Transfer
Agent).
Pour envoyer des e-mails, votre application a donc besoin d’établir une
communication avec un serveur de messagerie.
Si vous ne disposez pas de votre propre serveur de messagerie (ce qui est fort
probable), rassurez-vous, nous verrons qu’il existe une multitude de serveurs de
messagerie sur lesquels vous pourrez vous connecter gratuitement.

2. Le transport
Voici la configuration par défaut du bundle SwiftMailer stockée dans le
fichier config/packages/mailer.yaml :
framework
mailer:
dsn: '%env(MAILER_DSN)%'

Comme vous pouvez le constater, la directive de configuration fait référence à la


variable d’environnement Symfony MAILER_DSN. Cette variable doit être définie
dans le fichier .env (ou l’une de ses variantes selon l’environnement). La
variable MAILER_DSN permet d’indiquer les propriétés de connexion au serveur de
messagerie sous la forme suivante :
MAILER_DSN=smtp://user:pass@smtp.example.com:port

a. Le transport smtp
Le transport smtp est le plus simple à mettre en place. Avec lui, votre application
communique directement avec le serveur de messagerie (au travers d’un socket
TCP/IP et en respectant le protocole SMTP).
La directive host spécifie l’hôte du serveur de messagerie. Elle vaut par défaut
127.0.0.1 (localhost). En la laissant telle quelle, SwiftMailer s’attend à trouver un
serveur de messagerie sur la machine courante.
Si ce n’est pas le cas, vous pouvez très facilement configurer une communication avec
un serveur de messagerie tiers. Pour cela, il existe deux principales solutions :
les fournisseurs d’accès à Internet (FAI) ou les services de messagerie. En effet,
ces derniers proposent des serveurs de messagerie à leurs clients/utilisateurs.
Fournisseurs d’accès à Internet (FAI)
La plupart des fournisseurs d’accès à Internet mettent à disposition de leur clientèle
un serveur de messagerie. C’est par exemple le cas si vous êtes abonnés chez Free.
Vous pouvez alors utiliser ces informations pour construire la vaiable
MAILER_DSN.
Services de messagerie
Il est également possible d’intégrer SwiftMailer à des services de messagerie tels que
Gmail ou Yahoo.
b. Le transport sendmail
Sendmail est un serveur de messagerie qui a la particularité d’offrir une interface plus
simple que le protocole SMTP. Il est principalement utilisé sur les plateformes UNIX.
Pour envoyer un e-mail avec Sendmail, il suffit d’invoquer la commande sendmail :
echo "Voici mon message." | sendmail destinataire@example.com

Ici, le programme sendmail envoie un e-mail au destinataire


destinataire@example.com avec le contenu « Voici mon message. », récupéré depuis
l’entrée standard.
Bien que nous n’utilisions pas le protocole SMTP directement, en arrière-plan,
Sendmail doit être correctement configuré pour gérer les envois d’e-mails avec le
protocole SMTP.
Malgré son nom, le transport Sendmail ne se limite pas au seul serveur de
messagerie Sendmail. Beaucoup de serveurs de messagerie possèdent un exécutable
disposant de la même interface que Sendmail. Ainsi, si vous installez, par exemple le
serveur de messagerie Postfix sur un système Linux, vous aurez accès à un exécutable
de remplacement de Sendmail. Vous pourrez exécuter la même commande d’envoi
que nous avons présentée précédemment.
Voici la configuration de SwiftMailer pour utiliser le transport sendmail :
MAILER_DSN=sendmail://default

Le chemin vers l’exécutable n’est pas configurable, SwiftMailer s’attend à le trouver


à l’emplacement par défaut : /usr/sbin/sendmail.

3. Envoi d’un e-mail


Une fois votre transport configuré, vous êtes prêt à envoyer des e-mails. Un envoi d’e-
mail comporte deux étapes :
 La création de l’e-mail, un objet de type Swift_Message.
 L’envoi de cet objet, grâce au service mailer.
Voici un exemple d’action qui envoie un e-mail à destinataire@example.com :
public function index()
{
$message = \Swift_Message::newInstance()
->setSubject('Bonjour')
->setFrom('expediteur@example.com')
->setTo('destinataire@example.com')
->setBody('Voici mon message.')
;

$this->get('mailer')->send($message);

return array();
}
La méthode setSubject() de l’objet Swift_Message indique le sujet de l’e-mail, la
méthode setFrom() spécifie l’expéditeur, setTo() contient le destinataire, tandis
que setBody() comporte le corps du message.
Les e-mails peuvent être envoyés depuis n’importe quelle partie de votre
application. Vous pouvez injecter le service mailer dans vos autres services qui ont
besoin d’envoyer des e-mails. Au niveau de vos commandes personnalisées, vous
pouvez récupérer le service mailer de cette manière : $this->getContainer()-
>get(’mailer’)
Précédent
Créer une commande pour la console
Suivant
Travailler avec les sessions
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana
Quiz
Amélioration des performances
La mise en cache de pages
L’autochargement des classes
Le cache avec Doctrine
Le cache d’annotations
Les sessions
Autres optimisations
Test des performances d'un site web
Quiz
Internationalisation des applications Symfony
Introduction
Détecter la culture d'un utilisateur
Activation des traductions
Les routes et les traductions
Les fichiers de traductions
Traduction d'un message
Quiz
Développer une API REST avec Symfony
Introduction à REST et concepts fondamentaux
La gestion du format JSON
Mise en place d’une API REST
Les objets de requête et de réponse
Tester une API REST
Quiz
Annexes
Créer une commande pour la console
Envoyer des e-mails grâce à SwiftMailer
Travailler avec les sessions

 1. Introduction
 2. Intégration des sessions dans Symfony
 3. Configuration du gestionnaire de sauvegarde
 a. Avec PHP
 b. Avec Symfony
 4. Les messages « flash »

Déployer une application symfony

Travailler avec les sessions


1. Introduction
Une session est un mécanisme permettant de conserver des données tout au long de la
navigation d’un utilisateur.
Le protocole HTTP peut être défini comme étant sans état (stateless) : chaque requête
d’un utilisateur est traitée indépendamment par le serveur web, sans liaison avec les
précédentes. Néanmoins, grâce aux cookies, il est possible de conserver des
informations entre plusieurs requêtes d’un utilisateur. En rencontrant un cookie dans
une réponse, le navigateur se chargera de le renvoyer pour les prochaines requêtes.
Pour une multitude de raisons relatives à la sécurité d’un site web, il est cependant
déconseillé de stocker toutes les données d’un utilisateur dans des cookies. Si des
données sont sensibles, elles ne doivent pas être communiquées au navigateur : il est
préférable de les conserver sur votre serveur web.
D’un point de vue fonctionnel, avec un système de sessions, l’application renvoie un
cookie qui contient un identifiant, ce dernier étant par exemple le nom d’un fichier
stocké sur le serveur web. C’est ce fichier qui contient les informations de session.
PHP supporte nativement les sessions. Si vous n’êtes pas encore familier avec celles-
ci, veuillez vous reporter à la documentation officielle de PHP.

2. Intégration des sessions dans Symfony


Avec Symfony, vous ne devez pas utiliser la variable superglobale $_SESSION ou
les fonctions comme session_start() : les interactions avec la session se font
exclusivement au travers d’un service appelé session.
Voici comment récupérer et enregistrer des variables en session depuis un contrôleur :
$session = $this->getRequest()->getSession();
// ou $session = $this->get('session');

// enregistre une valeur en session


$session->set('ma_clé', 'ma_valeur');

// récupère une valeur


$maCle = $session->get('ma_clé');

3. Configuration du gestionnaire de sauvegarde


Il existe deux manières de configurer votre gestionnaire de sauvegarde de sessions.
a. Avec PHP
La première méthode consiste à configurer vos sessions avec PHP (dans votre fichier
php.ini).
Les principales directives de configuration
sont session.save_handler et session.save_path, correspondant respectivement au
gestionnaire de sauvegarde et au chemin de sauvegarde.
Le gestionnaire de sauvegarde par défaut de PHP est files ; il enregistre les sessions
dans des fichiers. Si vous utilisez des extensions comme Memcached, vous pouvez
configurer la valeur memcached, par exemple.
Pour indiquer à Symfony que vous souhaitez utiliser le gestionnaire de sauvegarde
défini dans votre configuration de PHP, vous devez placer la directive de
session handler_id à null dans la configuration du framework :
# config/packages/framework.yaml

framework:
...
session:
handler_id: null
cookie secure: auto
cookie samesite: lax
...

b. Avec Symfony
Configurer son gestionnaire de sauvegarde avec Symfony permet de rendre
l’application plus portable. Vous aurez la garantie que, quel que soit l’environnement
où votre application est déployée, les sessions seront stockées comme vous le
souhaitez.
Voici comment configurer un gestionnaire de sauvegarde utilisant des fichiers :
# config/packages/framework.yaml

framework:
session:
handler_id: session.handler.native_file
save_path: "/tmp/sessions"

Ici, les sessions sont sauvegardées dans le répertoire /tmp/sessions.


La directive handler_id accepte un nom de service ; la classe de ce service doit
implémenter l’interface PHP SessionHandlerInterface.
Le service session.handler.native_file est enregistré par défaut dans le
Service Container. Si vous souhaitez utiliser un autre gestionnaire, vous devrez créer
votre propre service. Symfony dispose de classes de gestionnaires de sauvegarde
définies au sein de l’espace de noms Symfony\Component\HttpFoundation\
Session\Storage\Handler ; vous pouvez vous baser sur l’une de celles-ci pour créer
votre service de gestion de sauvegarde. C’est ce que nous avons fait au cours du
chapitre sur les performances (cf. chapitre Amélioration des performances - Les
sessions).

4. Les messages « flash »


Les sessions de Symfony contiennent une fonctionnalité très pratique : les messages «
flash ». Leur but est de configurer un message à afficher après une redirection.
/**
* @Template
*/
public function index()
{
$form = // ...

$form->handleRequest($this->getRequest());
if ($form->isValid()) {

// traitement du formulaire...

$this->get('session')->getFlashBag()->add(
'info',
'Votre demande a bien été envoyée.'
);

return $this->redirect($this->generateUrl('accueil'));
}

return array('form' => $form->createView());


}

L’exemple ci-dessus contient le code d’une action chargée de traiter un formulaire


donné, disons un formulaire de contact (cela a peu d’importance).
Si le formulaire est valide, après l’avoir traité, nous enregistrons un message flash :
« Votre demande a bien été envoyée. ».
Ce message a également une catégorie (« info »). Les catégories sont libres, vous
pouvez donc choisir celles que vous voulez (« erreur », « remarque », etc.). Elles vous
permettent de personnaliser l’affichage du message selon sa catégorie : un message
d’erreur sera encadré en rouge, une remarque sera placé dans une info-bulle, etc.
Après l’enregistrement du message, nous effectuons une redirection. Lors de la
prochaine requête, le message pourra être récupéré depuis la session, que ce soit au
niveau de l’action ou du template.
Action
$messagesFlash = $this
->get('session')
->getFlashBag()
->get('info')
;

foreach ($messagesFlash as $messageFlash) {


// $messageFlash contient un message de catégorie « info »...
}

Template
En pratique, vous aurez plutôt besoin de les afficher au niveau des templates :
{% for flashMessage in app.session.flashbag.get('info') %}
<div class="info">
{{ flashMessage }}
</div>
{% endfor %}

Pour faciliter vos développements, nous vous recommandons d’intégrer le support de


messages flash de manière générique au niveau de votre layout principal plutôt
qu’individuellement sur chaque template.
Précédent
Envoyer des e-mails grâce à SwiftMailer
Suivant
Déployer une application symfony
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Accueil
Explorer
Cours et livres

Symfony 4 (LTS)
-
Développez des sites web PHP structurés et performants

 Sommaire
 Notes
 Compléments

Tout déplier | Tout replier


Informations générales
Introduction
Mise en place d’un projet Symfony
Architecture du framework
Le modèle de conception MVC
Architecture de Symfony
Symfony Flex
Les environnements
Le chargement automatique de classes
La console
Les outils pour le débogage
Quiz
Routage et contrôleur
Fonctionnement du routage dans Symfony
Définition des routes
Configurer le path
Routage par nom de domaine
Le contrôleur
Quiz
L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
L’injection de dépendances
Le Service Container
Créer un service et configurer ses injections
Le chargement automatique de services
Créer des services réutilisables et distribuables
Quiz
Les templates avec Twig
Présentation et concepts
Twig
Les gabarits de pages (layouts) et les blocks
Le langage Twig
Les filtres et les fonctions
La gestion des ressources statiques (images, feuilles de style, scripts JS…)
Quiz
Accéder aux bases de données avec Doctrine
Présentation et concepts
Installer et configurer Doctrine
Définition des entités et de leur mapping
Manipulation des entités avec l’EntityManager
Récupérer des entités
Fonctionnalités avancées
Quiz
La gestion des événements applicatifs
Concepts et écoute d’événement applicatifs
Les événements du Kernel
Les événements de la console
Quiz
Les formulaires
Un composant MVC
Fonctionnement du composant
Les types de champs de formulaire
Créer des formulaires réutilisables
Validation des données
Personnaliser le rendu - thèmes de formulaires
Quiz
La sécurité dans une application Symfony
Présentation et concepts de sécurité
Authentification
Utilisateurs et rôles
Autorisations
Quiz
Tester son application Symfony
Introduction au test logiciel
Les tests unitaires avec PHPUnit
Les tests fonctionnels
Quiz
Journalisation et surveillance avec Symfony
Générer des journaux avec Monolog
Le monitoring avec Prometheus et Grafana
Quiz
Amélioration des performances
La mise en cache de pages
L’autochargement des classes
Le cache avec Doctrine
Le cache d’annotations
Les sessions
Autres optimisations
Test des performances d'un site web
Quiz
Internationalisation des applications Symfony
Introduction
Détecter la culture d'un utilisateur
Activation des traductions
Les routes et les traductions
Les fichiers de traductions
Traduction d'un message
Quiz
Développer une API REST avec Symfony
Introduction à REST et concepts fondamentaux
La gestion du format JSON
Mise en place d’une API REST
Les objets de requête et de réponse
Tester une API REST
Quiz
Annexes
Créer une commande pour la console
Envoyer des e-mails grâce à SwiftMailer
Travailler avec les sessions
Déployer une application symfony

 1. Le déploiement
 2. Faut-il déployer par FTP ?
 3. Les différentes étapes
 4. Capistrano et Capifony
 a. Installation
 b. Configuration
 c. Déploiement
 5. Fonctionnalités avancées

Déployer une application symfony


1. Le déploiement
Déployer une application consiste à la mettre à disposition des utilisateurs finaux. Si
une version précédente de l’application existe déjà, le déploiement se charge de
remplacer cette ancienne version par la version (plus récente) que vous souhaitez
déployer.
Cette étape est délicate et peut être redoutée par certains développeurs : contrairement
aux autres étapes de la vie d’une application (développement, tests, intégration
continue, etc.), le déploiement ne laisse aucune place à l’erreur.
Si le processus de déploiement d’une application n’est pas assez robuste, le moindre
imprévu, la moindre erreur de manipulation, peut mettre en péril l’application de
production durant de nombreuses heures, engendrant stress et panique pour la
personne chargée de restaurer le site web défaillant le plus rapidement possible.
2. Faut-il déployer par FTP ?
Il n’est pas rare d’entendre des personnes dire « déployer par FTP est une
mauvaise pratique, il faut déployer via un logiciel de gestion de versions, comme
Git ».
Il est vrai que déployer son application via le protocole FTP est souvent
inapproprié, mais, au-delà du moyen de copie de l’application vers le serveur de
production, c’est plutôt la manière dont l’application de production est remplacée par
la version déployée qui importe. Il est tout à fait possible d’utiliser le protocole FTP
pour effectuer un déploiement sûr et robuste.
Un déploiement de qualité doit réunir les critères suivants :
 Être transactionnel : si, pour une quelconque raison, le déploiement de la nouvelle
version venait à échouer, la version précédente peut être immédiatement
restaurée.
 Être isolé : le déploiement ne doit pas être un simple copier - écraser sur le
dossier du site web. C’est seulement à la fin de la copie des fichiers et des tâches
de déploiement que le remplacement de l’ancienne version doit avoir lieu.
 Être automatique : les différentes actions inhérentes au déploiement doivent être
exécutées automatiquement, et non manuellement par la personne chargée de
déployer. C’est notamment pour cette raison que le déploiement par FTP est peu
recommandé ; il faut par exemple penser à exclure le contrôleur frontal de
développement avant la copie, ou se connecter sur le serveur de production pour
lancer quelques commandes (comme le vidage du cache), voire effectuer des
modifications en base de données (nouvelles colonnes, etc.). Toutes ces tâches
complexifient le déploiement et si l’une d’entre elles est oubliée, le site web de
production peut « tomber ». Idéalement, le déploiement est initié par une seule et
unique commande.

3. Les différentes étapes


Il n’existe pas de liste exhaustive des tâches à effectuer pour un déploiement car ces
dernières dépendent de votre application et de son architecture.
Voici cependant un exemple typique des différentes actions à réaliser pour un
déploiement :
 Copier le code vers le serveur de production.
 Installer les dépendances du projet (composer install). Cette commande de
Composer peut également se charger de certaines actions, vider le cache ou
installer les ressources publiques, par exemple (voir la section scripts du
fichier composer.json).
 Potentiellement mettre à jour la structure de la base de données.
 Vider vos systèmes de caches partagés (par exemple, Memcached).
Comme nous l’avons précédemment expliqué, ces actions doivent être idéalement
effectuées dans un endroit isolé, pour que l’ancien site web de production reste
accessible pendant le déploiement.
4. Capistrano et Capifony
Capistrano est un outil de déploiement transactionnel, isolé et automatique. Comme
ces différents critères correspondent à ce que nous recherchons pour nos
déploiements, nous allons apprendre à utiliser cet outil.
a. Installation
Capistrano est un projet écrit en langage Ruby. Avant de l’installer, vous devez vous
procurer RubyGems. RubyGems est au langage Ruby ce que Composer est au PHP,
un gestionnaire de paquets/dépendances.
Vous pouvez installer Capistrano, au choix, sur votre machine de développement ou
sur votre serveur de production.
Nous vous conseillons d’installer Capistrano sur un environnement Unix. Si vous
utilisez Windows pour développer, vous pouvez effectuer l’installation sur votre
serveur de production sous Linux.
Ouvrez un terminal et exécutez les commandes suivantes :
sudo apt-get install rubygems
sudo gem install capifony

Nous installons Capifony et non Capistrano. Capifony est un outil de déploiement


basé sur Capistrano, mais spécialement conçu pour les projets Symfony.
b. Configuration
Avant d’aborder la configuration du déploiement, attardons-nous sur sa
méthodologie.
Avec Capifony, sur votre serveur de production, votre site n’est plus un répertoire
unique avec le contenu du projet, la structure est quelque peu différente.
Il y a un dossier releases, avec des sous-dossiers, et chacun d’entre eux contient une
version de votre site. Parmi ces versions, nous retrouvons la version de production
actuelle. Les autres sont des anciennes versions.
Un dossier shared contient des fichiers et dossiers partagés entre les
différentes releases.
Enfin, un lien symbolique (symlink) nommé current pointe sur la version actuelle de
production (un sous-dossier du dossier releases).
Pour mieux appréhender cette structure, rien de tel que de la pratique. Nous allons
programmer un déploiement en local, c’est-à-dire que la commande de déploiement
sera exécutée directement depuis le serveur de production. Vous devez donc avoir
installé RubyGems et Capifony sur ce dernier.
Connectez-vous en SSH sur votre serveur de production. Ce dernier doit contenir les
sources de votre projet à déployer (versionnées avec Git par exemple) au sein d’un
dossier. Nous appellons ce dossier /chemin/vers/projet/a/deployer.
Naviguez jusqu’à /chemin/vers/projet/a/deployer, puis initialisez-y Capifony :
capifony .

Cette commande a créé deux fichiers : app/config/deploy.rb et Capfile. La


configuration de votre déploiement ira dans le fichier app/config.deploy.rb.
Modifiez votre fichier app/config/deploy.rb pour y avoir :
set :application, "Mon projet"
set :domain, "localhost"

set :deploy_to, "/chemin/vers/repertoire/de/deploiement"


set :app_path, "app"

set :repository, "file:///chemin/vers/projet/a/deployer"


set :scm, :git
set :use_composer, true

set :model_manager, "doctrine"

role :web, domain


role :app, domain
role :db, domain, :primary => true

set :use_sudo, false


set :keep_releases, 2

set :shared_files, [app_path + "/config/parameters.yml"]


set :shared_children, [app_path + "/logs"]

# Décommenter pour des logs complets


# logger.level = Logger::INFO

# Pour supprimer les releases inutiles après chaque déploiement


# after "deploy:update", "deploy:cleanup"

Cette configuration programme les déploiements au niveau du


répertoire /chemin/vers/repertoire/de/deploiement.
Exécutez la commande suivante :
cap deploy:setup
Le répertoire /chemin/vers/repertoire/de/deploiement contient dorénavant la
structure que nous avons expliquée précédemment (releases, current et shared).
Modifiez /chemin/vers/repertoire/de/deploiement/shared/app/config/
parameters.yml de manière à y placer les paramètres de container pour votre serveur
de production (database_user, etc.).
c. Déploiement
Toute la partie « Configuration » ne doit être effectuée qu’une seule fois.
Dorénavant, vous pourrez déployer votre projet en une seule commande. Pour cela,
retournez sur le répertoire /chemin/vers/projet/a/deployer et lancez-y l’instruction
suivante :
cap deploy

Félicitations ! Votre projet a été déployé


dans /chemin/vers/repertoire/de/deploiement/releases. Le lien
symbolique current pointe maintenant sur la version fraîchement déployée.
Votre serveur web doit pointer sur le lien symbolique current. Ainsi, vous
n’aurez pas à modifier la configuration de ce dernier à chaque déploiement.

5. Fonctionnalités avancées
Cet exemple de déploiement en local depuis le serveur de production a uniquement
valeur d’initiation.
Capifony dispose de nombreuses options de configuration, permettant notamment des
déploiements vers plusieurs machines ou la gestion des migrations de bases de
données. Pour plus d’informations, reportez-vous à la documentation officielle
de Capifony (https://everzet.github.io/capifony/) et/ou à la documentation
de DoctrineMigrationsBundle.
Précédent
Travailler avec les sessions
Conditions générales d'utilisationCopyright - © Editions ENI||Politique de protection des données du groupe
ENI

Vous aimerez peut-être aussi