100% found this document useful (1 vote)
809 views336 pages

Symfony Cookbook 2.3 FR

This document is the contents page for "The Cookbook for Symfony 2.3". It lists over 60 sections that provide guidance on how to accomplish various tasks with Symfony, such as asset management, bundle organization, forms, security, logging, and more. Each section includes a brief title describing the task covered. The contents are distributed under an open license allowing for sharing and modification with attribution. Users are invited to report any errors or issues.

Uploaded by

Baccouch Taieb
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
809 views336 pages

Symfony Cookbook 2.3 FR

This document is the contents page for "The Cookbook for Symfony 2.3". It lists over 60 sections that provide guidance on how to accomplish various tasks with Symfony, such as asset management, bundle organization, forms, security, logging, and more. Each section includes a brief title describing the task covered. The contents are distributed under an open license allowing for sharing and modification with attribution. Users are invited to report any errors or issues.

Uploaded by

Baccouch Taieb
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 336

The Cookbook

for Symfony 2.3


generated on June 26, 2014
The Cookbook (2.3)
This work is licensed under the Attribution-Share Alike 3.0 Unported license (http://creativecommons.org/
licenses/by-sa/3.0/).
You are free to share (to copy, distribute and transmit the work), and to remix (to adapt the work) under the
following conditions:
Attribution: You must attribute the work in the manner specified by the author or licensor (but
not in any way that suggests that they endorse you or your use of the work).
Share Alike: If you alter, transform, or build upon this work, you may distribute the resulting work
only under the same, similar or a compatible license. For any reuse or distribution, you must make
clear to others the license terms of this work.
The information in this book is distributed on an as is basis, without warranty. Although every precaution
has been taken in the preparation of this work, neither the author(s) nor SensioLabs shall have any liability to
any person or entity with respect to any loss or damage caused or alleged to be caused directly or indirectly by
the information contained in this work.
If you find typos or errors, feel free to report them by creating a ticket on the Symfony ticketing system
(http://github.com/symfony/symfony-docs/issues). Based on tickets and users feedback, this book is
continuously updated.
Contents at a Glance
Comment utiliser Assetic pour grer vos ressources..............................................................................6
Comment minifier les JavaScripts et les feuilles de style avec YUI Compressor .................................... 11
Comment utiliser Assetic et les fonctions Twig pour optimiser les images ........................................... 13
Comment appliquer un filtre Assetic une extension de fichier spcifique .......................................... 16
Comment utiliser les bonnes pratiques pour structurer vos Bundles.................................................... 18
Comment utiliser l'hritage de bundle pour surcharger certaines parties d'un bundle........................... 23
Comment surcharger n'importe quelle partie d'un bundle .................................................................. 26
Comment supprimer le AcmeDemoBundle........................................................................................ 29
Comment exposer une configuration smantique pour un Bundle ...................................................... 32
Comment utiliser Varnish pour acclrer mon site Web ..................................................................... 41
Comment Matriser et Crer de nouveaux Environnements ................................................................ 43
Comment surcharger la structure de rpertoires par dfaut de Symfony .............................................. 48
Comment configurer les paramtres externes dans le conteneur de services......................................... 51
Comment stocker les sessions dans la base de donnes grce PdoSessionStorage .............................. 54
Comment utiliser le routeur Apache .................................................................................................. 57
Comment crer une commande pour la Console................................................................................ 60
Comment utiliser la Console ............................................................................................................. 63
Comment gnrer des URLs et envoyer des emails depuis la Console.................................................. 65
Comment activer les logs dans la commande console ......................................................................... 67
Comment personnaliser les pages d'erreur ......................................................................................... 71
Comment dfinir des contrleurs en tant que Services........................................................................ 73
Comment optimiser votre environnement pour le debuggage ............................................................. 75
Dployer une application Symfony2 .................................................................................................. 77
Comment grer les uploads de fichier avec Doctrine........................................................................... 81
Comment utiliser les extensions Doctrine: Timestampable, Sluggable, Translatable, etc...................... 89
Comment enregistrer des listeners ( couteurs en franais) et des souscripteurs d'vnement........... 90
Comment utiliser la couche DBAL de Doctrine .................................................................................. 92
Comment gnrer des Entits partir d'une base de donnes existante ............................................... 94
Comment travailler avec plusieurs gestionnaires d'entits et connexions ............................................. 98
Comment dfinir des fonctions DQL personnalises ........................................................................ 101
Comment dfinir des Relations avec des Classes Abstraites et des Interfaces ..................................... 102
Comment implmenter un simple formulaire de cration de compte................................................. 105
Comment envoyer un Email ............................................................................................................ 111
Comment utiliser Gmail pour envoyer des Emails ............................................................................ 113
Comment travailler avec les Emails pendant le Dveloppement ........................................................ 114
Comment utiliser le Spool d'Email.............................................................................................. 116
PDF brought to you by
generated on June 26, 2014
Contents at a Glance | iii
Comment mettre en place des filtres avant et aprs un processus donn............................................ 118
Comment tendre une Classe sans utiliser l'Hritage ........................................................................ 123
Comment personnaliser le Comportement d'une Mthode sans utiliser l'Hritage ............................. 126
Comment personnaliser le rendu de formulaire................................................................................ 128
Comment utiliser les Convertisseurs de Donnes ............................................................................. 140
Comment modifier dynamiquement les formulaires en utilisant les vnements ................................ 146
Comment imbriquer une Collection de Formulaires......................................................................... 157
Comment Crer un Type de Champ de Formulaire Personnalis ...................................................... 169
Comment crer une extension de type de formulaire ........................................................................ 174
Comment rduire la duplication de code avec "inherit_data" ............................................................ 180
Comment tester unitairement vos formulaires.................................................................................. 183
Comment configurer des donnes vierges pour une classe de Formulaire .......................................... 188
Comment utiliser la fonction submit() pour grer les soumissions de formulaires.............................. 190
Comment utiliser l'Option de Champ de Formulaire Virtual .......................................................... 193
Comment utiliser Monolog pour crire des Logs .............................................................................. 196
Comment configurer Monolog pour envoyer les erreurs par Email.................................................... 200
Comment loguer des messages dans diffrents fichiers ..................................................................... 202
Comment crer un Collecteur de Donnes personnalis ................................................................... 204
Comment dclarer un nouveau Format de Requte et un Type Mime ............................................... 207
Comment forcer les routes toujours utiliser HTTPS ou HTTP........................................................ 209
Comment autoriser un caractre / dans un paramtre de route .................................................... 211
Comment configurer une redirection vers une autre route sans contrleur personnalis .................... 212
Comment utiliser des mthodes HTTP autres que GET et POST dans les routes ............................... 213
Comment utiliser des paramtres du conteneur de services dans vos routes ...................................... 215
Comment charger les utilisateurs depuis la base de donnes (le fournisseur d'Entit)......................... 217
Comment ajouter la fonctionnalit de login Se souvenir de moi ................................................... 227
Comment implmenter votre propre Voteur pour ajouter des adresses IP sur une liste noire.............. 230
Comment utiliser les Access Control Lists (ACLs) ( liste de contrle d'accs en franais) ............... 233
Comment utiliser les concepts d'ACL avancs.................................................................................. 237
Comment forcer HTTPS ou HTTP pour des URLs Diffrentes ......................................................... 241
Comment personnaliser votre formulaire de login............................................................................ 242
Comment scuriser n'importe quel service ou mthode de votre application ..................................... 246
Comment crer un Fournisseur d'Utilisateur personnalis ................................................................ 250
Comment crer un Fournisseur d'Authentification Personnalis ....................................................... 255
Comment changer le comportement par dfaut du chemin cible....................................................... 264
Comment utiliser le Serializer .......................................................................................................... 266
Comment crer un listener ( couteur en franais) d'vnement............................................... 268
Comment travailler avec les champs d'applications ( scopes en anglais) ........................................ 271
Comment travailler avec les Passes de Compilation dans les Bundles ................................................ 275
Exemple de Session Proxy ............................................................................................................... 276
Faire que la Locale soit "persistente" durant la session de l'utilisateur................................................ 278
Configurer le Dossier o les Fichiers pour les Sessions sont Enregistrs............................................. 280
Combler une application legacy avec les sessions de Symfony........................................................... 282
En quoi Symfony2 diffre de Symfony1............................................................................................ 284
Comment injecter des variables dans tous les modles (i.e. Variables Globales)................................. 290
Comment utiliser et enregistrer des chemins Twig namespacs......................................................... 292
Comment utiliser PHP plutt que Twig dans les templates ............................................................... 294
iv | Contents at a Glance
Contents at a Glance | 4
Comment crire une Extension Twig personnalise.......................................................................... 299
Comment rendre un template sans passer par un contrleur............................................................. 302
Comment simuler une authentification HTTP dans un Test Fonctionnel .......................................... 304
Comment simuler une authentification avec un token dans un test fonctionnel................................. 305
Comment tester les interactions de multiples clients......................................................................... 307
Comment utiliser le Profiler dans un test fonctionnel ....................................................................... 309
Comment tester du code interagissant avec une base de donnes...................................................... 311
Comment tester les dpts Doctrine ................................................................................................ 314
Comment personnaliser le processus de bootstrap avant les tests...................................................... 316
Comment crer une Contrainte de Validation Personnalise............................................................. 318
Comment crer des web services SOAP l'intrieur d'un contrleur Symfony2 ................................. 322
Comment crer et stocker un projet Symfony2 dans git .................................................................... 326
Comment crer et stocker un projet Symfony2 dans Subversion ....................................................... 330
PDF brought to you by
generated on June 26, 2014
Contents at a Glance | v
Listing 1-1
Chapter 1
Comment utiliser Assetic pour grer vos
ressources
Assetic associe deux concepts majeurs : les ressources et les filtres. Les ressources sont des fichiers comme
les feuilles de style, les JavaScript et les images. Les filtres peuvent tre appliqus ces fichiers avant qu'ils
ne soient servis au navigateur. Cela permet de grer sparment les fichiers ressources qui sont stocks
par l'application des fichiers qui sont rellement prsents l'utilisateur.
Sans Assetic, vous servez directement les fichiers qui sont stocks dans votre application :
1 <script src="{{ asset('js/script.js') }}" type="text/javascript" />
Mais avec Assetic, vous pouvez manipuler ces ressources de la manire dont vous le dsirez (ou les
charger de n'importe o) avant de les servir. Cela signifie que vous pouvez :
Minifier et combiner toutes vos CSS ou vos fichiers JavaScript
Excuter tous (ou juste une partie) vos fichiers CSS ou JS en passant par des compilateurs
comme LESS, SASS ou CoffeeScript.
Optimiser vos images
Ressources
Utiliser Assetic plutt que servir les fichiers directement offre de nombreux avantages. Les fichiers n'ont
pas besoin d'tre stocks l o il seront servis, et peuvent provenir de plusieurs sources, notamment d'un
bundle.
Vous pouvez utiliser Assetic pour vos fichiers CSS ou Javascript Le principe est identique entre les deux
l'exception d'une syntaxe qui diffre lgrement.
Inclure des fichiers Javascript
Pour inclure des fichiers Javascript, utilisez le tag javascript dans n'importe quel template. On va
en gnral s'en servir dans le block javascripts, si vous utilisez les noms de block par dfaut de la
Distribution Standard de Symfony :
PDF brought to you by
generated on June 26, 2014
Chapter 1: Comment utiliser Assetic pour grer vos ressources | 6
Listing 1-2
Listing 1-3
Listing 1-4
1
2
3
{% javascripts '@AcmeFooBundle/Resources/public/js/*' %}
<script type="text/javascript" src="{{ asset_url }}"></script>
{% endjavascripts %}
Vous pouvez aussi inclure vos feuilles de style: voir Inclure des fichiers CSS.
Dans cet exemple, tous les fichiers du dossier Resources/public/js/ du bundle AcmeFooBundle vont
tre chargs et servis depuis un autre endroit. Le tag rellement affich pourrait ressembler :
1 <script src="/app_dev.php/js/abcd123.js"></script>
C'est un point cl: Une fois que vous avez laiss Assetic grer vos ressources, les fichiers sont servis depuis
un autre endroit. Ceci pourra provoquer des problmes pour les fichiers CSS contenant des chemins
relatifs pour leurs images. Voir Corriger les chemins CSS avec le filtre cssrewrite.
Inclure des fichiers CSS
Pour vos feuilles de styles CSS, vous pouvez utiliser la mme mthodologie mais avec le tag stylesheets.
Si vous utilisez les block par dfaut de la Distribution Standard, ce tag prendra place dans un block
stylesheets :
1
2
3
{% stylesheets 'bundles/acme_foo/css/*' filter='cssrewrite' %}
<link rel="stylesheet" href="{{ asset_url }}" />
{% endstylesheets %}
Mais comme Assetic modifie les chemins de vos ressources, les images de fond (ou autres) qui utilisent
des chemins relatifs se retrouveront casss, sauf si vous utilisez le filtre cssrewrite.
Remarquez que dans le premier exemple qui inclut les fichiers javascripts, vous faites rfrence aux
fichiers avec un chemin comme suit : @AcmeFooBundle/Resources/public/file.js, mais dans
celui-ci, vous faites rfrence aux fichiers CSS avec leur vrai chemin public : bundles/acme_foo/
css. Vous pouvez utiliser l'un ou l'autre. Sachez juste qu'il existe un problme connu qui peut faire
chouer le filtre cssrewrite avec la syntaxe @AcmeFooBundle.
Corriger les chemins CSS avec le filtre cssrewrite
Vu que Assetic gnre de nouvelles URLs pour vos ressource, tous les chemins relatifs dans vos fichiers
CSS vont tre casss. Pour corriger a, assurez-vous d'utiliser le filtre cssrewrite avec votre tag
stylesheets. Il va parser votre CSS et corriger les chemins pour prendre en compte le nouvel
emplacement.
Vous pouvez voir un exemple dans la section prcdente :
Quand vous utilisez le filtre cssrewrite, ne faites pas appel vos CSS avec la syntaxe
@AcmeFooBundle. Pour plus de dtails, voir la note dans la section du dessus.
PDF brought to you by
generated on June 26, 2014
Chapter 1: Comment utiliser Assetic pour grer vos ressources | 7
Listing 1-5
Listing 1-6
Combiner des ressources
Vous pouvez aussi combiner plusieurs fichiers en un seul. Cela aide rduire le nombre de requtes
HTTP, ce qui est trs important pour les performances. Cela vous permet aussi de maintenir les fichiers
plus facilement en les dcoupants en petites parties plus faciles grer. Cela peut tre un plus pour la
rusabilit de votre projet puisque vous pouvez facilement sparer les fichiers spcifiques au projet des
fichiers qui peuvent tre rutiliss dans d'autres applications, mais toujours les servir comme un fichier
unique :
1
2
3
4
5
6
{% javascripts
'@AcmeFooBundle/Resources/public/js/*'
'@AcmeBarBundle/Resources/public/js/form.js'
'@AcmeBarBundle/Resources/public/js/calendar.js' %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
En environnement de dev, chaque fichier est toujours servi individuellement pour que vous puissiez
dbugguer plus facilement. Cependant, en environnement de prod (ou plus prcisment, quand l'option
debug est false), ils seront affichs dans une unique balise script qui contiendra le contenu de tous vos
fichiers JavaScript.
Si vous dcouvrez Assetic et essayez d'utiliser votre application en environnement de prod (en
utilisant le contrleur app.php), vous verrez probablement que vos CSS et JS plantent. Pas de
panique ! C'est fait exprs. Pour plus de dtails sur l'utilisation d'Assetic en environnement de prod,
lisez Exporter les ressources.
Et combiner les fichiers ne s'applique pas uniquement vos fichiers. Vous pouvez aussi utiliser Assetic
pour combiner les ressources tierces, comme jQuery, vos fichiers dans un fichier unique :
1
2
3
4
5
{% javascripts
'@AcmeFooBundle/Resources/public/js/thirdparty/jquery.js'
'@AcmeFooBundle/Resources/public/js/*' %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
Filtres
Une fois qu'elles sont gres par Assetic, vous pouvez appliquer des filtres vos ressources avant
qu'elles ne soient servies. Cela inclut les filtres qui compressent vos ressources pour rduire la taille des
fichiers (pour de meilleures performances). D'autres filtres peuvent compiler des fichiers CoffeeScript
en JavaScript ou convertir vos fichiers SASS en CSS. En fait, Assetic possde une longue liste de filtres
disponibles.
Plusieurs de ces filtres ne font pas le travail directement, mais utilisent des bibliothques tierces pour faire
le gros du travail. Cela signifie que vous devrez souvent installer une bibliothque tierce pour utiliser un
filtre. Le grand avantage d'utiliser Assetic pour faire appel ces bibliothques (plutt que de les utiliser
directement) est qu'au lieu de les excuter la main aprs avoir modifi les fichiers, Assetic prendra
tout en charge pour vous, et supprimera dfinitivement cette tape du processus de dveloppement et de
dploiement.
Pour utiliser un filtre, vous aurez d'abord besoin de le spcifier dans la configuration d'Assetic. Ajouter
un filtre dans la configuration ne signifie pas qu'il est utilis, mais juste qu'il est prt l'tre (vous allez
l'utiliser ci-dessous).
Par exemple, pour utiliser le JavaScript YUI Compressor, la configuration suivante doit tre ajoute :
PDF brought to you by
generated on June 26, 2014
Chapter 1: Comment utiliser Assetic pour grer vos ressources | 8
Listing 1-7
Listing 1-8
Listing 1-9
Listing 1-10
1
2
3
4
5
# app/config/config.yml
assetic:
filters:
yui_js:
jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar"
Maintenant, pour vraiment utiliser le filtre sur un groupe de fichiers JavaScript, ajoutez ce code dans votre
template :
1
2
3
{% javascripts '@AcmeFooBundle/Resources/public/js/*' filter='yui_js' %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
Vous pouvez trouver un guide plus dtaill sur la configuration et l'utilisation des filtres Assetic ainsi que
des informations sur le mode debug d'Assetic en lisant Comment minifier les JavaScripts et les feuilles de
style avec YUI Compressor.
Contrler l'URL utilise
Si vous le souhaitez, vous pouvez contrler les URLs gnres par Assetic. Cela se fait dans le template,
et le chemin est relatif par rapport la racine publique :
1
2
3
{% javascripts '@AcmeFooBundle/Resources/public/js/*' output='js/compiled/main.js' %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
Symfony contient galement une mthode pour le cache busting (technique empchant la mise
en cache), o l'URL gnre par Assetic contient un paramtre qui peut tre incrment, via la
configuration, chaque dploiement. Pour plus d'informations, lisez l'option de configuration
assets_version.
Exporter les ressources
En environnement de dev, Assetic gnre des chemins vers des fichiers CSS et JavaScript qui n'existent
pas physiquement sur votre ordinateur. Mais ils sont nanmoins affichs car un contrleur interne de
Symfony ouvre les fichiers et sert leur contenu (aprs avoir excut tous les filtres).
Cette manire dynamique de servir des ressources traites est gniale car cela signifie que vous pouvez
immdiatement voir les modifications que vous apportez vos fichiers. Mais l'inconvnient est que cela
peut parfois tre un peu lent. Si vous utilisez beaucoup de filtres, cela peut tre carrment frustrant.
Heureusement, Assetic fournit un moyen pour exporter vos ressources vers des fichiers rels au lieu de
les gnrer dynamiquement.
Exporter les ressources en environnement de prod
En environnement de prod, vos fichiers JS et CSS sont reprsents chacun par une balise unique. En
d'autres termes, plutt que de voir chacun des fichiers JavaScript que vous incluez dans votre code source,
vous verrez quelque chose comme ceci :
PDF brought to you by
generated on June 26, 2014
Chapter 1: Comment utiliser Assetic pour grer vos ressources | 9
Listing 1-11
Listing 1-12
Listing 1-13
Listing 1-14
Listing 1-15
1 <script src="/app_dev.php/js/abcd123.js"></script>
De plus, ce fichier n'existe pas vraiment et n'est pas non plus affich dynamiquement par Symfony (car
les ressources sont en environnement de dev). C'est fait exprs : laisser Symfony gnrer ces fichiers
dynamiquement en production serait tout simplement trop lent.
Au lieu de cela, chaque fois que vous excutez votre application dans l'environnement de prod (et par
consquent, chaque fois que vous dployez), vous devriez excuter la commande suivante :
1 $ php app/console assetic:dump --env=prod --no-debug
Cela gnrera et crira physiquement chaque fichier dont vous avez besoin (ex /js/abcd123.js). Si vous
mettez jour vos ressources, vous aurez besoin de relancer cette commande pour regnrer vos fichiers.
Exporter les ressources en environnement de dev
Par dfaut, chaque chemin de ressource gnr en environnement de dev est pris en charge
dynamiquement par Symfony. Cela n'a pas d'inconvnient (vous pouvez voir vos changements
immdiatement), sauf que les ressources peuvent tre lentes charger. Si vous trouvez que vos ressources
sont chargs trop lentement, suivez ce guide.
Premirement, dites Symfony de ne plus essayer de traiter ces fichiers dynamiquement. Apportez les
modifications suivantes dans le fichier config_dev.yml :
1
2
3
# app/config/config_dev.yml
assetic:
use_controller: false
Ensuite, puisque Symfony ne gnre plus ces fichiers pour vous, vous aurez besoin de les exporter
manuellement. Pour ce faire, lancez la commande suivante :
1 $ php app/console assetic:dump
Elle crit physiquement tous les fichiers de ressource dont vous avez besoin pour l'environnement de
dev. Le gros inconvnient est que vous devrez faire cela chaque fois que vous modifiez une ressource.
Heureusement, en passant l'option --watch, la commande regnrera automatiquement les ressources
modifies :
1 $ php app/console assetic:dump --watch
Lancer cette commande en environnement de dev peut gnrer un florilge de fichiers. Pour conserver
votre projet bien organis, il peut tre intressant de mettre les fichiers gnrs dans un rpertoire spar
(ex /js/compiled) :
1
2
3
{% javascripts '@AcmeFooBundle/Resources/public/js/*' output='js/compiled/main.js' %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
PDF brought to you by
generated on June 26, 2014
Chapter 1: Comment utiliser Assetic pour grer vos ressources | 10
Listing 2-1
Chapter 2
Comment minifier les JavaScripts et les feuilles
de style avec YUI Compressor
Yahoo! fournit un excellent utilitaire pour minifier les JavaScripts et les feuilles de style pour qu'elles
soient plus rapides charger, YUI Compressor
1
. Grce Assetic, vous pourrez tirer profit de cet outil trs
facilement.
Tlchargez le JAR YUI Compressor
YUI Compressor est crit en Java est distribu sous forme de JAR. Tlchargez le JAR
2
sur le site de
Yahoo! et enregistrez le sous app/Resources/java/yuicompressor.jar.
Configurez les filtres YUI
Maintenant vous devez configurer les deux filtres Assetic dans votre application, l'un pour minifier les
JavaScripts avec YUI Compressor et l'autre pour minifier les feuilles de style :
1
2
3
4
5
6
7
8
# app/config/config.yml
assetic:
# java: "/usr/bin/java"
filters:
yui_css:
jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar"
yui_js:
jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar"
1. http://developer.yahoo.com/yui/compressor/
2. http://yuilibrary.com/projects/yuicompressor/
PDF brought to you by
generated on June 26, 2014
Chapter 2: Comment minifier les JavaScripts et les feuilles de style avec YUI Compressor | 11
Listing 2-2
Listing 2-3
Listing 2-4
Les utilisateurs de Windows ne doivent pas oublier de mettre jour l'emplacement de Java. Dans
Windows8 x64 bit, il s'agit de C:\Program Files (x86)\Java\jre6\bin\java.exe par dfaut
Vous avez maintenant accs aux deux nouveaux filtres Assetic dans votre application : yui_css et
yui_js. Ils utiliseront YUI Compressor pour minifier respectivement les feuilles de style et les JavaScripts.
Minifiez vos Ressources
Maintenant YUI Compressor est configur, mais rien ne se passera tant que vous n'appliquez pas ces
filtres une ressource (asset). Puisque vos ressources font partie de la couche Vue, ce travail doit tre fait
dans vos templates :
1
2
3
{% javascripts '@AcmeFooBundle/Resources/public/js/*' filter='yui_js' %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
L'exemple ci-dessus part du principe que vous avez un bundle appel AcmeFooBundle et que vos
fichiers JavaScript se trouvent dans le rpertoire Resources/public/js dans votre bundle. Ce
n'est, en fait, pas trs important car vous pouvez inclure vos fichiers JavaScript o vous le voulez.
En rajoutant le filtre yui_js la ressource ci-dessus, vous devriez voir que les JavaScripts minifis sont
chargs beaucoup plus rapidement. Le mme procd peut tre utilis pour minifier vos feuilles de style.
1
2
3
{% stylesheets '@AcmeFooBundle/Resources/public/css/*' filter='yui_css' %}
<link rel="stylesheet" type="text/css" media="screen" href="{{ asset_url }}" />
{% endstylesheets %}
Dsactiver la minification en Mode Debug
Les JavaScripts et feuilles de styles minifis sont trs difficiles lire; et encore moins dbugguer. Pour
palier cela, Assetic vous permet de dsactiver un filtre lorsque votre application est en mode debug. Vous
pouvez faire cela en prfixant le nom du filtre dans votre template par un point d'interrogation : ?. Cela
indique Assetic de n'appliquer les filtres que si le mode debug n'est pas actif.
1
2
3
{% javascripts '@AcmeFooBundle/Resources/public/js/*' filter='?yui_js' %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
Plutt que d'ajouter le filtre vos balises assets, vous pouvez aussi l'activer de faon globale en
ajoutant l'attribut apply-to la configuration du filtre, par exemple apply_to: "\.js$" pour le
filtre yui_js. Pour que le filtre ne s'applique qu'en production, ajoutez le au fichier config_prod au
lieu du fichier de configuration commun. Pour plus de dtails sur comment appliquer des filtres en
fonction des extensions de fichiers, lisez Filtrer en se basant sur les extensions.
PDF brought to you by
generated on June 26, 2014
Chapter 2: Comment minifier les JavaScripts et les feuilles de style avec YUI Compressor | 12
Listing 3-1
Listing 3-2
Chapter 3
Comment utiliser Assetic et les fonctions Twig
pour optimiser les images
Parmi ses nombreux filtres, Assetic possde quatre filtres qui peuvent tre utiliss pour optimiser les
images la vole. Cela vous permet de tirer profit de tailles de fichiers rduites sans utiliser d'diteur
d'image pour rduire chaque image. Les rsultats sont mis en cache et peuvent tre rutiliss en
production pour qu'il n'y ait pas d'impact sur les performances pour vos utilisateurs finaux.
Utiliser Jpegoptim
Jpegoptim
1
est un utilitaire pour optimiser les fichiers JPEG. Pour l'utiliser avec Assetic, ajoutez le bout de
code suivant votre configuration Assetic :
1
2
3
4
5
# app/config/config.yml
assetic:
filters:
jpegoptim:
bin: path/to/jpegoptim
Notez que pour utiliser jpegoptim, il faut qu'il soit dj install sur votre systme. L'option bin
pointe vers le fichier binaire compil.
Il peut maintenant tre utilis dans un template :
1
2
3
4
{% image '@AcmeFooBundle/Resources/public/images/example.jpg'
filter='jpegoptim' output='/images/example.jpg' %}
<img src="{{ asset_url }}" alt="Example"/>
{% endimage %}
1. http://www.kokkonen.net/tjko/projects.html
PDF brought to you by
generated on June 26, 2014
Chapter 3: Comment utiliser Assetic et les fonctions Twig pour optimiser les images | 13
Listing 3-3
Listing 3-4
Listing 3-5
Listing 3-6
Listing 3-7
Supprimer toutes les donnes EXIF
Par dfaut, appliquer ce filtre ne supprime que certaines meta-informations du fichier. Les donnes
EXIF et les commentaires ne sont pas supprims, mais vous pouvez les supprimer en utilisant l'option
strip_all :
1
2
3
4
5
6
# app/config/config.yml
assetic:
filters:
jpegoptim:
bin: path/to/jpegoptim
strip_all: true
Rduire la qualit maximum
Le niveau de qualit du JPEGn'est pas modifi par dfaut. Vous pouvez rduire un peu la taille des images
en dfinissant un niveau de qualit maximum plus bas que le niveau actuel. Cela se fera videmment au
dtriment de la qualit de l'image :
1
2
3
4
5
6
# app/config/config.yml
assetic:
filters:
jpegoptim:
bin: path/to/jpegoptim
max: 70
Fonctions Twig : syntaxe courte
Si vous utilisez Twig, il est possible de faire tout ceci avec une syntaxe raccourcie en activant et en
utilisant une fonction spciale Twig. Commencez par ajouter la configuration suivante :
1
2
3
4
5
6
7
8
# app/config/config.yml
assetic:
filters:
jpegoptim:
bin: path/to/jpegoptim
twig:
functions:
jpegoptim: ~
Le template Twig peut maintenant tre modifi comme suit :
1 <img src="{{ jpegoptim('@AcmeFooBundle/Resources/public/images/example.jpg') }}"
alt="Example"/>
Vous pouvez spcifier le rpertoire cible dans la configuration de la manire suivante :
1
2
3
4
5
6
# app/config/config.yml
assetic:
filters:
jpegoptim:
bin: path/to/jpegoptim
twig:
PDF brought to you by
generated on June 26, 2014
Chapter 3: Comment utiliser Assetic et les fonctions Twig pour optimiser les images | 14
7
8
functions:
jpegoptim: { output: images/*.jpg }
PDF brought to you by
generated on June 26, 2014
Chapter 3: Comment utiliser Assetic et les fonctions Twig pour optimiser les images | 15
Listing 4-1
Listing 4-2
Chapter 4
Comment appliquer un filtre Assetic une
extension de fichier spcifique
Les filtres Assetic peuvent tre appliqus des fichiers individuels, des groupes de fichiers ou mme,
comme vous allez le voir ici, des fichiers qui ont une extension spcifique. Pour vous montrer comment
grer chaque cas, supposons que vous ayez le filtre Assetic CoffeeScript qui compile les fichiers
CoffeeScript en JavaScript.
La configuration principale contient juste les chemins vers coffee et node. Leurs valeurs par dfaut
respectives sont /usr/bin/coffee et /usr/bin/node:
1
2
3
4
5
6
# app/config/config.yml
assetic:
filters:
coffee:
bin: /usr/bin/coffee
node: /usr/bin/node
Filtrer un fichier unique
Vous pouvez maintenant compiler un fichier unique CoffeeScript en JavaScript depuis vos templates :
1
2
3
{% javascripts '@AcmeFooBundle/Resources/public/js/example.coffee' filter='coffee' %}
<script src="{{ asset_url }}" type="text/javascript"></script>
{% endjavascripts %}
C'est tout ce dont vous avez besoin pour compiler ce fichier CoffeeScript et le servir comme JavaScript
compil.
PDF brought to you by
generated on June 26, 2014
Chapter 4: Comment appliquer un filtre Assetic une extension de fichier spcifique | 16
Listing 4-3
Listing 4-4
Listing 4-5
Filtrer des fichiers multiples
Vous pouvez aussi combiner plusieurs fichiers CoffeeScript en un unique fichier en sortie :
1
2
3
4
5
{% javascripts '@AcmeFooBundle/Resources/public/js/example.coffee'
'@AcmeFooBundle/Resources/public/js/another.coffee'
filter='coffee' %}
<script src="{{ asset_url }}" type="text/javascript"></script>
{% endjavascripts %}
Les deux fichiers seront maintenant servis comme un unique fichier compil en JavaScript.
Filtrer en se basant sur les extensions
Un des plus grands avantages d'Assetic est de pouvoir rduire le nombre de ressources pour rduire
le nombre de requtes HTTP. Dans le but d'en tirer le plus grand avantage possible, il pourrait tre
intressant de combiner tous vos fichiers CoffeeScript et JavaScript ensembles puisqu'ils seront
finalement dlivrs comme JavaScript. Malheureusement, se contenter d'ajouter les fichiers JavaScript
aux fichiers combiner ne fonctionnera pas car le JavaScript ne passera pas la compilation CoffeeScript.
Ce problme peut tre vit en ajoutant l'option apply_to la configuration, ce qui vous permettra de
spcifier qu'un filtre devra toujours tre appliqu une extension de fichier particulire. Dans ce cas, vous
pouvez spcifier que le filtre Coffee s'applique tous les fichiers .coffee :
# app/config/config.yml
assetic:
filters:
coffee:
bin: /usr/bin/coffee
node: /usr/bin/node
apply_to: "\.coffee$"
Avec cela, vous n'avez plus besoin de spcifier le filtre coffee dans le template. Vous pouvez aussi lister
les fichiers JavaScript classique, chacun d'eux sera combin et dlivr en un unique fichier JavaScript
(seuls les fichiers .coffee passeront travers le filtre CoffeeScript) :
1
2
3
4
5
{% javascripts '@AcmeFooBundle/Resources/public/js/example.coffee'
'@AcmeFooBundle/Resources/public/js/another.coffee'
'@AcmeFooBundle/Resources/public/js/regular.js' %}
<script src="{{ asset_url }}" type="text/javascript"></script>
{% endjavascripts %}
PDF brought to you by
generated on June 26, 2014
Chapter 4: Comment appliquer un filtre Assetic une extension de fichier spcifique | 17
Chapter 5
Comment utiliser les bonnes pratiques pour
structurer vos Bundles
Un bundle est un rpertoire qui a une structure bien dfinie et qui peut hberger peu prs tout : des
classes aux contrleurs en passant par les ressources web. Mme si les bundles sont trs flexibles, vous
devriez suivre quelques unes des bonnes pratiques si vous voulez les distribuer.
Nom du Bundle
Un bundle est aussi un espace de noms PHP. Ce dernier doit suivre les standards
1
d'introprabilit
technique pour les espaces de noms PHP 5.3 et les noms de classes : il commence par un segment
vendor , suivi par zro ou plusieurs segments catgories, et il se termine par le nom raccourci de l'espace
de noms, qui doit finir par un suffixe Bundle.
Un espace de noms devient un bundle aussitt que vous lui ajoutez une classe bundle. Le nom de la classe
bundle doit suivre ces rgles simples :
Utiliser uniquement des caractres alphanumriques et des tirets bas ( underscore en
anglais) ;
Utiliser un nom en notation dite CamelCase ;
Utiliser un nom court et descriptif (pas plus de 2 mots) ;
Prfixer le nom avec la concatnation du vendor (et optionnellement l'espace de noms de la
catgorie) ;
Suffixer le nom avec Bundle.
Vous trouverez ci-dessous des espaces de noms de bundle et des noms de classes valides :
Espace de noms Nom de la Classe Bundle
Acme\Bundle\BlogBundle AcmeBlogBundle
Acme\Bundle\Social\BlogBundle AcmeSocialBlogBundle
1. http://symfony.com/PSR0
PDF brought to you by
generated on June 26, 2014
Chapter 5: Comment utiliser les bonnes pratiques pour structurer vos Bundles | 18
Listing 5-1
Espace de noms Nom de la Classe Bundle
Acme\BlogBundle AcmeBlogBundle
Par convention, la mthode getName() de la classe bundle devrait retourner le nom de la classe.
Si vous partagez publiquement votre bundle, vous devez utiliser le nom de la classe bundle comme
nom de dpt (AcmeBlogBundle et non pas BlogBundle par exemple).
Les Bundles du coeur de Symfony2 ne prfixent pas la classe Bundle avec Symfony et ajoutent
toujours un sous-espace de noms Bundle ; par exemple : FrameworkBundle
2
.
Chaque bundle possde un alias, qui est la version raccourcie en miniscules du nom du bundle en
utilisant des tirets bas (acme_hello pour AcmeHelloBundle, ou acme_social_blog pour
Acme\Social\BlogBundle par exemple). Cet alias est utilis pour renforcer l'unicit l'intrieur d'un
bundle (voir ci-dessous pour des exemples d'utilisation).
Structure de Rpertoires
La structure basique du rpertoire d'un bundle HelloBundle doit tre comme suit :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
XXX/...
HelloBundle/
HelloBundle.php
Controller/
Resources/
meta/
LICENSE
config/
doc/
index.rst
translations/
views/
public/
Tests/
Le(s) rpertoire(s) XXX reflte(nt) la structure de l'espace de noms du bundle.
Les fichiers suivants sont obligatoires :
HelloBundle.php ;
Resources/meta/LICENSE: La licence complte pour le code ;
Resources/doc/index.rst: Le fichier racine pour la documentation du bundle.
Ces conventions assurent que les outils automatiss puissent compter sur cette structure par dfaut
pour travailler.
2. http://api.symfony.com/2.3/Symfony/Bundle/FrameworkBundle/FrameworkBundle.html
PDF brought to you by
generated on June 26, 2014
Chapter 5: Comment utiliser les bonnes pratiques pour structurer vos Bundles | 19
La profondeur des sous-rpertoires devrait tre rduite au minimum pour les classes et fichiers les plus
utiliss (2 niveaux au maximum). Plus de niveaux peuvent tre dfinis pour les fichiers non-stratgiques
et moins utiliss.
Le rpertoire du bundle est en lecture seule. Si vous avez besoin d'crire des fichiers temporaires, stockez-
les dans le dossier cache/ ou log/ de l'application hbergeant votre bundle. Des outils peuvent gnrer
des fichiers dans la structure du rpertoire du bundle, mais uniquement si les fichiers gnrs vont faire
partie du dpt.
Les classes et fichiers suivants ont des emplacements spcifiques :
Type Rpertoire
Commandes Command/
Contrleurs Controller/
Extensions du Conteneur de Services DependencyInjection/
Listeners d'vnements EventListener/
Configuration Resources/config/
Ressources Web Resources/public/
Fichiers de traduction Resources/translations/
Templates Resources/views/
Tests Unitaires et Fonctionnels Tests/
Classes
La structure du rpertoire du bundle est utilise en tant que hirarchie d'espace de noms. Par exemple, un
contrleur HelloController est stock dans Bundle/HelloBundle/Controller/HelloController.php
et le nom complet qualifi de la classe est Bundle\HelloBundle\Controller\HelloController.
Tous les fichiers et classes doivent suivre les standards de codage de Symfony2 ( coding standards en
anglais).
Certaines classes devraient tre vues comme des faades et donc tre aussi courtes que possible, comme
les Commands , Helpers , Listeners et Controllers .
Les classes se connectant au dispatcher ( rpartiteur en franais) d'vnements devraient tre suffixes
avec Listener.
Les classes d'exceptions devraient tre stockes dans un sous-espace de noms Exception.
Vendors
Un bundle ne doit pas embarquer de bibliothques PHP tierces. Il devrait compter sur le chargement
automatique ( autoloading en anglais) standard de Symfony2 la place.
Un bundle ne devrait pas embarquer de bibliothques tierces crites en JavaScript, CSS, ou quelconque
autre langage.
PDF brought to you by
generated on June 26, 2014
Chapter 5: Comment utiliser les bonnes pratiques pour structurer vos Bundles | 20
Tests
Un bundle devrait venir avec un ensemble de tests crits avec PHPUnit et stocks dans le rpertoire
Tests/. Les tests devraient suivre les principes suivants :
La suite de tests doit tre excutable avec une simple commande phpunit lance depuis une
application ;
Les tests fonctionnels devraient tre utiliss uniquement pour tester la sortie de la rponse et
quelques informations de profilage si vous en avez ;
Les tests devraient couvrir au moins 95% de tout votre code.
Une suite de test ne doit pas contenir de script AllTests.php, mais doit reposer sur l'existence
d'un fichier phpunit.xml.dist.
Documentation
Toutes les classes et fonctions doivent contenir une PHPDoc complte.
Une documentation complte devrait aussi tre fournie dans le format reStructuredText, dans le
rpertoire Resources/doc/ ; le fichier Resources/doc/index.rst est l'unique fichier obligatoire et doit
tre le point d'entre de la documentation.
Contrleurs
En tant que bonne pratique, les contrleurs dans un bundle prvu pour tre distribu d'autres ne
doivent pas tendre la classe de base Controller
3
. Ils peuvent implmenter ContainerAwareInterface
4
ou tendre ContainerAware
5
la place.
Si vous jetez un oeil aux mthodes de la classe Controller
6
, vous verrez qu'elles ne sont que des
raccourcis pratiques pour faciliter la courbe d'apprentissage.
Routage
Si le bundle fournit des routes, elles doivent tre prfixes avec l'alias du bundle. Par exemple, pour un
AcmeBlogBundle , toutes les routes doivent tre prfixes avec acme_blog_.
Templates
Si un bundle fournit des templates, ils doivent utiliser Twig. Un bundle ne doit pas fournir de layout
principal, except s'il fournit une application entirement fonctionnelle.
3. http://api.symfony.com/2.3/Symfony/Bundle/FrameworkBundle/Controller/Controller.html
4. http://api.symfony.com/2.3/Symfony/Component/DependencyInjection/ContainerAwareInterface.html
5. http://api.symfony.com/2.3/Symfony/Component/DependencyInjection/ContainerAware.html
6. http://api.symfony.com/2.3/Symfony/Bundle/FrameworkBundle/Controller/Controller.html
PDF brought to you by
generated on June 26, 2014
Chapter 5: Comment utiliser les bonnes pratiques pour structurer vos Bundles | 21
Listing 5-2
Listing 5-3
Fichiers de Traduction
Si un bundle fournit des traductions de messages, ces dernires doivent tre dfinies au format XLIFF ; le
domaine devrait tre nomm aprs le nom du bundle (bundle.hello).
Un bundle ne doit pas craser les messages existants venant d'un autre bundle.
Configuration
Pour fournir plus de flexibilit, un bundle peut procurer des paramtres configurables en utilisant les
mcanismes intgrs de Symfony2.
Pour des paramtres de configuration simples, comptez sur les entres par dfaut de parameters de la
configuration de Symfony2. Les paramtres Symfony2 sont de simples paires cl/valeur ; une valeur tant
n'importe quelle valeur PHP valide. Chaque nom de paramtre devrait commencer avec l'alias du bundle,
bien que ceci ne soit qu'une suggestion de bonne pratique. Le reste du nom du paramtre va utiliser un
point (.) pour sparer les diffrentes parties (par exemple : acme_hello.email.from).
L'utilisateur final peut fournir des valeurs dans diffrents types de fichier de configuration :
1
2
3
# app/config/config.yml
parameters:
acme_hello.email.from: [email protected]
Rcuprez les paramtres de configuration dans votre code depuis le conteneur:
1 $container->getParameter('acme_hello.email.from');
Mme si ce mcanisme est assez simple, vous tes grandement encourag utiliser la configuration
smantique dcrite dans le cookbook.
Si vous dfinissez des services, ils devraient aussi tre prfixs avec l'alias du bundle.
En savoir plus grce au Cookbook
Comment exposer une configuration smantique pour un Bundle
PDF brought to you by
generated on June 26, 2014
Chapter 5: Comment utiliser les bonnes pratiques pour structurer vos Bundles | 22
Listing 6-1
Chapter 6
Comment utiliser l'hritage de bundle pour
surcharger certaines parties d'un bundle
Lorsque vous travaillerez avec des bundles tiers, vous allez probablement rencontrer une situation o
vous voudrez surcharger un fichier de ce bundle tiers en le remplacant par un fichier de l'un de vos
propres bundles. Symfony vous fournit une manire trs pratique de surcharger des fichiers comme des
contrleurs, des templates et d'autres fichiers prsents dans le dossier Resources/ d'un bundle.
Par exemple, supposons que vous installiez le FOSUserBundle
1
, mais que vous souhaitez surcharger son
template de base layout.html.twig, ainsi que l'un de ses contrleurs. Supposons aussi que vous ayez
votre propre AcmeUserBundle o vous voulez avoir les fichiers de substitution. Commencez par dclarer
le FOSUserBundle comme parent de votre bundle:
1
2
3
4
5
6
7
8
9
10
11
12
// src/Acme/UserBundle/AcmeUserBundle.php
namespace Acme\UserBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AcmeUserBundle extends Bundle
{
public function getParent()
{
return 'FOSUserBundle';
}
}
En effectuant ce simple changement, vous pouvez dsormais surcharger plusieurs parties du
FOSUserBundle en crant simplement un fichier ayant le mme nom.
Malgr le nom de la mthode, il n'y a pas de relation parent/enfant entre les bundles. Il s'agit juste
d'une manire d'tendre et de surcharger un bundle existant.
1. https://github.com/friendsofsymfony/fosuserbundle
PDF brought to you by
generated on June 26, 2014
Chapter 6: Comment utiliser l'hritage de bundle pour surcharger certaines parties d'un bundle | 23
Listing 6-2
Surcharger des contrleurs
Supposons que vous vouliez ajouter de la fonctionnalit registerAction du RegistrationController
rsidant dans le FOSUserBundle. Pour faire cela, crez simplement votre propre fichier
RegistrationController.php, surcharger la mthode originale du bundle, et changez sa fonctionnalit:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/Acme/UserBundle/Controller/RegistrationController.php
namespace Acme\UserBundle\Controller;
use FOS\UserBundle\Controller\RegistrationController as BaseController;
class RegistrationController extends BaseController
{
public function registerAction()
{
$response = parent::registerAction();
// do custom stuff
return $response;
}
}
Suivant le degr de changement de fonctionnalit dont vous avez besoin, vous pourriez appeler
parent::registerAction() ou alors remplacer compltement sa logique par la vtre.
Surcharger des contrleurs de cette faon fonctionne uniquement si le bundle rfre au contrleur
en utilisant la syntaxe standard FOSUserBundle:Registration:register dans les routes et
templates. Ceci est la bonne pratique.
Surcharger des ressources : templates, routage, validation, etc.
La plupart des ressources peuvent aussi tre surcharges en crant simplement un fichier au mme
emplacement que dans votre bundle parent.
Par exemple, il est trs courant d'avoir besoin de surcharger le template layout.html.twig du
FOSUserBundle afin qu'il utilise le layout de base de votre application. Comme le fichier rside
l'emplacement Resources/views/layout.html.twig dans le FOSUserBundle, vous pouvez crer votre
propre fichier au mme endroit dans le AcmeUserBundle. Symfony va compltement ignorer le fichier
tant dans le FOSUserBundle, et utiliser le vtre la place.
Il en va de mme pour les fichiers de routage, de configuration de la validation et pour les autres
ressources.
Surcharger des ressources fonctionne uniquement lorsque vous rfrez des ressources via la
mthode @FosUserBundle/Resources/config/routing/security.xml. Si vous rfrez des
ressources sans utiliser le raccourci @NomDuBundle, ces dernires ne peuvent alors pas tre
surcharges.
PDF brought to you by
generated on June 26, 2014
Chapter 6: Comment utiliser l'hritage de bundle pour surcharger certaines parties d'un bundle | 24
Les fichiers de traduction ne fonctionnent pas de la mme manire que celle dcrite ci-dessus. Tous
les fichiers de traduction sont accumuls dans un ensemble de groupements (un pour chaque
domaine). Symfony charge les fichiers de traduction des bundles en premier (dans l'ordre dans
lequel les bundles sont initialiss) et ensuite ceux de votre rpertoire app/Resources. Si la mme
traduction est spcifie dans deux ressources, c'est la traduction venant de la ressource charge en
dernier qui gagnera.
PDF brought to you by
generated on June 26, 2014
Chapter 6: Comment utiliser l'hritage de bundle pour surcharger certaines parties d'un bundle | 25
Chapter 7
Comment surcharger n'importe quelle partie
d'un bundle
Ce document est une rfrence succincte sur comment surcharger diffrentes partie d'un bundle tiers.
Templates
Pour des informations sur la surcharge de templates, lisez * La Surcharge de templates de Bundle *
Comment utiliser l'hritage de bundle pour surcharger certaines parties d'un bundle
Routage
Le routage n'est jamais import automatiquement dans Symfony2. Si vous voulez inclure les routes d'un
bundle, alors elles doivent tre importes manuellement un endroit de votre application (ex app/
config/routing.yml).
La manire la plus simple de surcharger les routes d'un bundle est de ne pas les importer du tout.
Plutt que d'importer les routes d'un bundle tiers, copiez simplement le fichier de routage dans votre
application, modifiez le, et importez le.
Contrleurs
En partant du principe que les bundles tiers n'utisent pas de contrleurs qui ne soient pas des services
(ce qui est presque toujours le cas), vous pouvez facilement surcharger les contrleurs grce l'hritage
de bundle. Pour plus d'informations, lisez Comment utiliser l'hritage de bundle pour surcharger certaines
parties d'un bundle.
PDF brought to you by
generated on June 26, 2014
Chapter 7: Comment surcharger n'importe quelle partie d'un bundle | 26
Listing 7-1
Listing 7-2
Listing 7-3
Services & Configuration
Pour surcharger/tendre un service, vous avez deux options. Premirement, vous pouvez redfinir la
valeur du paramtre qui contient le nom de la classe du service en spcifiant votre propre classe dans app/
config/config.yml. Bien sr, cela n'est possible que si le nom de la classe est dfini comme paramtre
dans la configuration du service du bundle. Par exemple, pour surcharger la classe utilis par le service
translator``de Symfony, vous pouvez surcharger le paramtre ``translator.class. Savoir
quel paramtre surcharger peut ncessiter un peu de recherche. Pour le Translator, le paramtre est dfini
et utilis dans le fichier Resources/config/translation.xml du FrameworkBundle :
1
2
3
# app/config/config.yml
parameters:
translator.class: Acme\HelloBundle\Translation\Translator
Deuximement, si la classe n'est pas spcifie dans les paramtres, si vous voulez vous assurer que la
classe est bien toujours surcharge lorsque votre bundle est utilis, ou si vous avez besoin de faire un peu
plus de modifications, vous devrez utiliser une passe de compilation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/Acme/DemoBundle/DependencyInjection/Compiler/OverrideServiceCompilerPass.php
namespace Acme\DemoBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class OverrideServiceCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('original-service-id');
$definition->setClass('Acme\DemoBundle\YourService');
}
}
Dans cet exemple, vous retrouvez la dfinition du service original, et vous changez son nom de classe en
votre propre nom de classe.
Lisez Comment travailler avec les Passes de Compilation dans les Bundles pour savoir comment utiliser les
passes de compilation. Si vous voulez faire plus que simplement surcharger la classe, comme par exemple
ajouter une mthode, vous ne pouvez utiliser que la mthode de la passe de compilation.
Entits et mapping
En cours...
Formulaires
Pour surcharger un type de formulaire, il faut l'enregistrer comme service (c'est--dire que vous devez le
tagger avec form.type ). Vous pourrez alors le surchargez comme vous surchargeriez n'importe quel
service, comme c'est expliqu dans Services & Configuration. Bien sr, cela ne fonctionnera que si le type
est appel par son alias, et non pas s'il est instanci. Exemple:
1 $builder->add('name', 'custom_type');
PDF brought to you by
generated on June 26, 2014
Chapter 7: Comment surcharger n'importe quelle partie d'un bundle | 27
Listing 7-4
au lieu de:
1 $builder->add('name', new CustomType());
Mtadonnes de Validation
En cours...
Traductions
En cours...
PDF brought to you by
generated on June 26, 2014
Chapter 7: Comment surcharger n'importe quelle partie d'un bundle | 28
Listing 8-1
Chapter 8
Comment supprimer le AcmeDemoBundle
La Standard Edition de Symfony2 est livr avec une dmo complte qui vit l'intrieur d'un bundle appel
AcmeDemoBundle. C'est un bon repre pour tout dmarrage d'un projet, mais vous aurez probablement
envie de finalement le retirer.
Cet article prsente AcmeDemoBundle comme exemple, mais vous pouvez suivre les mmes tapes
pour supprimer n'importe quel bundle.
1. Dsinscrire le bundle dans AppKernel
Pour dconnecter le bundle du framework, vous devez le supprimer de la mthode
AppKernel::registerBundles(). Le bundle se trouve normalement dans le tableau $bundles mais il
n'est enregistr que pour l'environnement de dveloppement. Vous pouvez le trouver l'interieur de
l'instruction if ci-dessous
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// app/AppKernel.php
// ...
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(...);
if (in_array($this->getEnvironment(), array('dev', 'test'))) {
// commentez ou supprimez cette ligne
// $bundles[] = new Acme\DemoBundle\AcmeDemoBundle();
// ...
}
}
}
PDF brought to you by
generated on June 26, 2014
Chapter 8: Comment supprimer le AcmeDemoBundle | 29
Listing 8-2
2. Supprimer la configuration du bundle
Maintenant que Symfony ne connat plus le bundle, vous devez supprimer toute configuration ou
configuration de routing qui se rfre au bundle dans le rpertoire app/config.
2.1 Supprimer le routing du bundle
Vous pouvez trouver le routing du AcmeDemoBundle dans le fichier app/config/routing_dev.yml.
Supprimez la notation _acme_demo en bas de ce fichier.
2.2 Supprimer la configuration du bundle
Certains bundles contiennent des configurations dans l'un des fichiers app/config/config*.yml.
Assurez-vous de supprimer la configuration associe ces fichiers. Vous pouvez rapidement reprer la
configuration d'un bundle en cherchant la chaine acme_demo (ou n'importe quel autre nom de bundle, i.e
fos_user pour le le FOSUserBundle).
Le AcmeDemoBundle n'a pas de configuration. Toutefois, le bundle est utilis dans la configuration de
scurit dans le fichier app/config/security.yml. Vous pouvez l'utiliser pour votre configuration de
scurit mais vous pouvez aussi la supprimer.
3. Supprimer le bundle depuis le systme de fichier
Maintenant que vous avez supprim toutes les rfrences du bundle dans votre application, vous pouvez
supprimer le bundle depuis le systme de fichier. Le bundle se trouve dans le rpertoire src/Acme/
DemoBundle. Vous devez supprimer ce dossier et vous pouvez aussi supprimer le dossier Acme.
Si vous ne connaissez pas o se trouve le bundle, vous pouvez utiliser la mthode getPath()
1
pour
rcuprer le chemin vers le bundle:
1 echo $this->container->get('kernel')->getBundle('AcmeDemoBundle')->getPath();
4. Supprimer l'integration dans d'autres bundles
Ceci ne concerne pas le bundle AcmeDemoBundle - aucun autre bundle ne dpend de lui, donc
pouvez sauter cette tape.
Certains bundles s'appuient sur d'autres, si vous supprimez l'un d'eux, l'autre bundle ne fonctionnera
probablement plus. Assurez-vous donc avant de supprimer un bundle qu'aucun autre bundle, tiers ou
votre propre bundle, ne dpend de ce bundle.
1. http://api.symfony.com/2.3/Symfony/Component/HttpKernel/Bundle/BundleInterface.html#getPath()
PDF brought to you by
generated on June 26, 2014
Chapter 8: Comment supprimer le AcmeDemoBundle | 30
Si un bundle s'appuie sur un autre, le plus souvent cela signifie que ce dernier utilise certains
services du bundle.
Rechercher la chaine de caractre de l'alias du bundle pourrait vous aider les reprer (i.e
acme_demo pour les bundles dpendant de AcmeDemoBundle).
Si un bundle tiers s'appuie sur un autre bundle, vous pouvez trouver ce bundle mentionn dans le
fichier composer.json se trouvant dans le dossier du bundle.
PDF brought to you by
generated on June 26, 2014
Chapter 8: Comment supprimer le AcmeDemoBundle | 31
Listing 9-1
Chapter 9
Comment exposer une configuration
smantique pour un Bundle
Si vous ouvrez le fichier de configuration de votre application (en gnral app/config/config.yml), vous
allez y trouver un certain nombre de diffrents espaces de noms de configuration, tel que framework,
twig, et doctrine. Chacun d'entre eux configure un bundle spcifique, vous permettant de paramtrer
des choses un haut niveau et ainsi de laisser le bundle effectuer tous les changements qui restent ; ces
derniers tant complexes et de bas niveau.
Par exemple, ce qui suit dit au FrameworkBundle d'activer l'intgration de formulaire, qui implique de
dfinir pas mal de services ainsi que l'intgration d'autres composants lis :
1
2
3
framework:
# ...
form: true
Lorsque vous crez un bundle, vous avez deux choix concernant la gestion de la configuration :
1. Configuration normale des services (facile) :
Vous pouvez spcifier vos services dans un fichier de configuration (par exemple
: services.yml) qui se trouve dans votre bundle et puis l'importer depuis la
configuration principale de votre application. Cela est vraiment facile, rapide et
totalement efficace. Si vous utilisez des paramtres, alors vous avez toujours la
flexibilit de personnaliser votre bundle depuis la configuration de votre application.
Lisez Importer la Configuration avec imports" pour plus de dtails.
2. Exposer la configuration smantique (avanc) :
C'est la manire dont la configuration est faite pour les bundles coeurs (comme
dcrit ci-dessus). L'ide de base est que, la place d'avoir l'utilisateur qui surcharge
les paramtres individuels, vous laissez l'utilisateur configurer seulement certaines
options spcifiquement cres. En tant que dveloppeur du bundle, vous analysez
donc cette configuration et chargez les services dans une classe Extension . Avec
cette mthode, vous n'aurez pas besoin d'importer quelconque ressource de
PDF brought to you by
generated on June 26, 2014
Chapter 9: Comment exposer une configuration smantique pour un Bundle | 32
Listing 9-2
configuration depuis la configuration principale de votre application : la classe
Extension peut grer tout cela.
La seconde option - que vous allez approfondir dans cet article - est beaucoup plus flexible, mais requiert
aussi plus de temps mettre en place. Si vous vous demandez quelle mthode vous devriez utiliser, c'est
probablement une bonne ide de commencer avec la mthode #1, et de changer pour la #2 si besoin est.
La seconde mthode possde plusieurs avantages spcifiques :
Beaucoup plus puissante que de dfinir simplement des paramtres : une valeur d'option
spcifique pourrait dclencher la cration de plusieurs dfinitions de services ;
Possibilit d'avoir une hirarchie dans la configuration ;
Fusion intelligente quand plusieurs fichiers de configuration (par exemple : config_dev.yml
et config.yml) se surchargent entre eux ;
Validation de la configuration (si vous utilisez une Classe de Configuration) ;
Autocompltion via l'IDE lorsque vous crez un XSD et que les dveloppeurs utilisent XML.
Surcharger les paramtres d'un bundle
Si un Bundle fournit une classe d'Extension, alors vous ne devriez gnralement pas surcharger l'un
de ses paramtres de conteneur de service. L'ide est que si une classe d'Extension est prsente,
chaque paramtre qui doit tre configurable devrait tre prsent dans la configuration rendue
accessible par cette classe. En d'autres termes, la classe d'extension dfinit tous les paramtres
publics de configuration supports pour lesquels une rtro-compatibilit sera maintenue.
Crer une classe d'Extension
Si vous choisissez d'exposer une configuration smantique pour votre bundle, vous aurez d'abord besoin
de crer une nouvelle classe Extension , qui va grer le processus. Cette classe devrait se trouver dans
le rpertoire DependencyInjection de votre bundle et son nom devrait tre construit en remplaant le
suffixe Bundle du nom de la classe du Bundle par Extension. Par exemple, la classe d'Extension de
AcmeHelloBundle serait nomme AcmeHelloExtension:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
namespace Acme\HelloBundle\DependencyInjection;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class AcmeHelloExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
// l o toute la logique principale est effectue
}
public function getXsdValidationBasePath()
{
return __DIR__.'/../Resources/config/';
}
public function getNamespace()
{
return 'http://www.example.com/symfony/schema/';
PDF brought to you by
generated on June 26, 2014
Chapter 9: Comment exposer une configuration smantique pour un Bundle | 33
Listing 9-3
Listing 9-4
Listing 9-5
22
23
}
}
Les mthodes getXsdValidationBasePath et getNamespace sont requises uniquement si le
bundle fournit un XSD optionnel pour la configuration.
La prsence de la classe prcdente signifie que vous pouvez maintenant dfinir un espace de noms de
configuration acme_hello dans n'importe quel fichier de configuration. L'espace de noms acme_hello
est construit sur la base du nom de la classe d'extension en enlevant le mot Extension et en passant le
reste du nom en minuscules avec des tirets bas (underscores). En d'autres termes, AcmeHelloExtension
devient acme_hello.
Vous pouvez immdiatement commencer spcifier la configuration sous cet espace de noms :
1
2
# app/config/config.yml
acme_hello: ~
Si vous suivez les conventions de nommage dcrites ci-dessus, alors la mthode load() du code de
votre extension est toujours appele tant que votre bundle est dclar dans le Kernel. En d'autres
termes, mme si l'utilisateur ne fournit aucune configuration (c-a-d que l'entre acme_hello
n'apparat mme pas), la mthode load() sera appele avec un tableau $configs vide comme
argument . Vous pouvez toujours fournir des valeurs par dfaut si vous le voulez.
Analyser le tableau $configs
Chaque fois qu'un utilisateur inclut l'espace de noms acme_hello dans un fichier de configuration, la
configuration incluse dans cet espace est ajoute un tableau de configurations et passe la mthode
load() de votre extension (Symfony2 convertit automatiquement XML et YAML en un tableau).
Prenez la configuration suivante :
1
2
3
4
# app/config/config.yml
acme_hello:
foo: fooValue
bar: barValue
Le tableau pass votre mthode load() ressemblera cela:
1
2
3
4
5
6
array(
array(
'foo' => 'fooValue',
'bar' => 'barValue',
)
)
Notez que ceci est un tableau de tableaux, et pas juste un unique tableau plat contenant les valeurs
de configuration. Cela est intentionnel. Par exemple, si acme_hello apparat dans un autre fichier de
configuration - disons config_dev.yml - avec des valeurs diffrentes, alors le tableau pass votre
mthode load() pourrait ressembler a:
PDF brought to you by
generated on June 26, 2014
Chapter 9: Comment exposer une configuration smantique pour un Bundle | 34
Listing 9-6
Listing 9-7
Listing 9-8
1
2
3
4
5
6
7
8
9
10
array(
array(
'foo' => 'fooValue',
'bar' => 'barValue',
),
array(
'foo' => 'fooDevValue',
'baz' => 'newConfigEntry',
),
)
L'ordre des deux tableaux dpend duquel est dfini en premier.
C'est alors votre travail de dcider comment ces configurations devraient tre fusionnes ensemble.
Par exemple, vous pourriez avoir les dernires valeurs crasant les prcdentes ou en quelque sorte les
fusionner ensemble.
Enfin, dans la section Classe de Configuration, vous apprendrez une manire rellement robuste de grer
cela. Mais pour le moment, vous pourriez simplement les fusionner manuellement:
1
2
3
4
5
6
7
8
9
public function load(array $configs, ContainerBuilder $container)
{
$config = array();
foreach ($configs as $subConfig) {
$config = array_merge($config, $subConfig);
}
// maintenant utilisez le tableau plat $config
}
Assurez-vous que la technique de fusion ci-dessus ait sens pour votre bundle. Ceci est juste un
exemple et vous devriez tre attentif ne pas l'utiliser aveuglment.
Utiliser la mthode load()
Dans la mthode load(), la variable $container fait rfrence un conteneur qui connat uniquement
cet espace de noms de configuration (c-a-d qu'il ne contient pas d'informations de service charges
depuis d'autres bundles). Le but de la mthode load() est de manipuler le conteneur, en ajoutant et en
configurant n'importe quelles mthodes ou services ncessaires votre bundle.
Charger des ressources de configuration externes
Une chose commune faire est de charger un fichier de configuration externe qui pourrait contenir
l'ensemble des services ncessaires votre bundle. Par exemple, supposons que vous ayez un fichier
services.xml contenant la plupart des configurations de service de votre bundle:
1
2
3
4
5
6
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\Config\FileLocator;
public function load(array $configs, ContainerBuilder $container)
{
// prparer votre variable $config
PDF brought to you by
generated on June 26, 2014
Chapter 9: Comment exposer une configuration smantique pour un Bundle | 35
Listing 9-9
Listing 9-10
Listing 9-11
7
8
9
10
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/
config'));
$loader->load('services.xml');
}
Vous pourriez mme faire ceci conditionnellement, en vous basant sur l'une des valeurs de configuration.
Par exemple, supposons que vous vouliez charger un ensemble de services seulement si une option
enabled est passe et dfinie comme true :
1
2
3
4
5
6
7
8
9
10
public function load(array $configs, ContainerBuilder $container)
{
// prparer votre variable $config
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/
config'));
if (isset($config['enabled']) && $config['enabled']) {
$loader->load('services.xml');
}
}
Configurer les services et dfinir les paramtres
Une fois que vous avez charg des configurations de service, vous pourriez avoir besoin de modifier la
configuration base sur certaines valeurs saisies. Par exemple, supposons que vous ayez un service dont le
premier argument est une chane de caractres type que le service va utiliser en interne. Vous voudriez
que ceci soit facilement configurable par l'utilisateur du bundle, donc dans votre fichier de configuration
du services (par exemple : services.xml), vous dfinissez ce service et utilisez un paramtre vide -
acme_hello.my_service_type - en tant que premier argument :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- src/Acme/HelloBundle/Resources/config/services.xml -->
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/
dic/services/services-1.0.xsd">
<parameters>
<parameter key="acme_hello.my_service_type" />
</parameters>
<services>
<service id="acme_hello.my_service" class="Acme\HelloBundle\MyService">
<argument>%acme_hello.my_service_type%</argument>
</service>
</services>
</container>
Mais pourquoi dfinir un paramtre vide et puis le passer votre service ? La rponse est que vous allez
dfinir ce paramtre dans votre classe d'extension, en vous basant sur les valeurs de configuration saisies.
Par exemple, supposons que vous souhaitiez autoriser l'utilisateur dfinir cette option type via une cl
nomme my_type. Pour effectuer cela, ajoutez ce qui suit la mthode load():
PDF brought to you by
generated on June 26, 2014
Chapter 9: Comment exposer une configuration smantique pour un Bundle | 36
Listing 9-12
1
2
3
4
5
6
7
8
9
10
11
12
13
public function load(array $configs, ContainerBuilder $container)
{
// prparer votre variable $config
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/
config'));
$loader->load('services.xml');
if (!isset($config['my_type'])) {
throw new \InvalidArgumentException('The "my_type" option must be set');
}
$container->setParameter('acme_hello.my_service_type', $config['my_type']);
}
Maintenant, l'utilisateur peut effectivement configurer le service en spcifiant la valeur de configuration
my_type :
1
2
3
4
# app/config/config.yml
acme_hello:
my_type: foo
# ...
Paramtres globaux
Lorsque vous configurez le conteneur, soyez conscient que vous avez disposition les paramtres globaux
suivants :
kernel.name
kernel.environment
kernel.debug
kernel.root_dir
kernel.cache_dir
kernel.logs_dir
kernel.bundle_dirs
kernel.bundles
kernel.charset
Tous les noms de paramtres et services commenant par un _ sont rservs pour le framework, et
aucun autre ne doit tre dfini par des bundles.
Validation et fusion avec une classe de configuration
Jusqu'ici, vous avez effectu la fusion de vos tableaux de configuration manuellement et avez verifi la
prsence de valeurs de configuration la main en utilisant la fonction PHP isset(). Un systme de
Configuration optionnel est aussi disponible et peut vous aider avec la fusion, la validation, les valeurs
par dfaut, et la normalisation des formats.
PDF brought to you by
generated on June 26, 2014
Chapter 9: Comment exposer une configuration smantique pour un Bundle | 37
Listing 9-13
Listing 9-14
La normalisation des formats se rfre au fait que certains formats - principalement XML - rsultent
en de lgres diffrences concernant les tableaux de configuration et que ces tableaux ont besoin
d'tre normaliss afin de correspondre avec tout le reste.
Pour profiter de ce systme, vous allez crer une classe de Configuration et construire un arbre qui
dfinit votre configuration dans cette classe:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/Acme/HelloBundle/DependencyInjection/Configuration.php
namespace Acme\HelloBundle\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('acme_hello');
$rootNode
->children()
->scalarNode('my_type')->defaultValue('bar')->end()
->end();
return $treeBuilder;
}
Ceci est un exemple trs simple, mais vous pouvez maintenant utiliser cette classe dans votre mthode
load() pour fusionner votre configuration et forcer la validation. Si n'importe quelle autre option que
my_type est passe, l'utilisateur sera notifi avec une exception disant qu'une option non-supporte a t
passe:
1
2
3
4
5
6
7
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
// ...
}
La mthode processConfiguration() utilise l'arbre de configuration que vous avez dfini dans la classe
de Configuration pour valider, normaliser et fusionner tous les tableaux de configuration ensemble.
La classe de Configuration peut tre bien plus complique que ce qui est montr ici, supportant des
noeuds de tableaux, des noeuds prototypes , une validation avance, une normalisation spcifique
XML et une fusion avance. Vous pouvez en lire plus ce propos dans the Config Component
documentation. Vous pouvez galement voir cela en action en effectuant un checkout d'une des classes
de Configuration coeurs, comme par exemple la Configuration du FrameworkBundle
1
ou la Configuration
du TwigBundle
2
.
1. https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
2. https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php
PDF brought to you by
generated on June 26, 2014
Chapter 9: Comment exposer une configuration smantique pour un Bundle | 38
Listing 9-15
Dump de la Configuration par Dfaut
New in version 2.1: La commande config:dump-reference a t ajoute dans Symfony 2.1
La commande config:dump-reference permet d'afficher sur la console la configuration yaml par dfaut
du bundle.
Tant que votre configuration de bundle est situe l'emplacement standard
(YourBundle\DependencyInjection\Configuration) et qu'elle n'a pas de __constructor(), cela
fonctionnera automatiquement. Si vous avez quelque chose de diffrent, votre classe Extension devra
surcharger la mthode Extension::getConfiguration(). Cette dernire devant retourner une instance
de votre Configuration.
Des commentaires et exemples peuvent tre ajouts vos noeuds de configuration en utilisant les
mthodes ->info() et ->example():
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// src/Acme/HelloBundle/DependencyExtension/Configuration.php
namespace Acme\HelloBundle\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('acme_hello');
$rootNode
->children()
->scalarNode('my_type')
->defaultValue('bar')
->info('ce que my_type configure')
->example('exemple de paramtre')
->end()
->end()
;
return $treeBuilder;
}
Ce texte apparat en tant que commentaires yaml sur la sortie de la commande config:dump-reference.
Conventions concernant les Extensions
Quand vous crez une extension, suivez ces conventions simples :
L'extension doit tre stocke dans le sous-espace de noms DependencyInjection ;
L'extension doit tre nomme d'aprs le nom du bundle et suffixe avec Extension
(AcmeHelloExtension pour AcmeHelloBundle) ;
L'extension devrait fournir un schma XSD.
Si vous suivez ces conventions simples, vos extensions seront dclares automatiquement par Symfony2.
Sinon, rcrivez la mthode build()
3
de Bundle dans votre bundle:
PDF brought to you by
generated on June 26, 2014
Chapter 9: Comment exposer une configuration smantique pour un Bundle | 39
Listing 9-16
1
2
3
4
5
6
7
8
9
10
11
12
13
// ...
use Acme\HelloBundle\DependencyInjection\UnconventionalExtensionClass;
class AcmeHelloBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
// dclare manuellement les extensions qui ne suivent pas les conventions
$container->registerExtension(new UnconventionalExtensionClass());
}
}
Dans ce cas, la classe d'extension doit aussi implmenter une mthode getAlias() et retourner un alias
unique nomm aprs le bundle (par exemple : acme_hello). Ceci est requis car le nom de la classe ne suit
pas les standards en se terminant par Extension.
De plus, la mthode load() de votre extension sera appele uniquement si l'utilisateur spcifie l'alias
acme_hello dans au moins un fichier de configuration. De nouveau, ceci est d au fait que la classe
d'Extension ne suit pas les standards dfinis plus haut, donc rien ne se fait automatiquement.
3. http://api.symfony.com/2.3/Symfony/Component/HttpKernel/Bundle/Bundle.html#build()
PDF brought to you by
generated on June 26, 2014
Chapter 9: Comment exposer une configuration smantique pour un Bundle | 40
Listing 10-1
Chapter 10
Comment utiliser Varnish pour acclrer mon
site Web
Du fait que le cache de Symfony2 utilise les en-ttes HTTP standards, le Symfony2 Reverse Proxy peut
facilement tre remplac par n'importe quel autre reverse proxy . Varnish est un acclrateur HTTP
puissant et open-source capable de dlivrer du contenu cach rapidement et incluant le support pour les
Edge Side Includes.
Configuration
Comme vu prcdemment, Symfony2 est suffisamment intelligent pour dtecter si il parle un reverse
proxy qui comprend ESI ou non. Cela fonctionne automatiquement lorsque vous utilisez le reverse
proxy de Symfony2, mais vous avez besoin d'une configuration spciale pour que cela fonctionne avec
Varnish. Heureusement, Symfony2 repose dj sur un autre standard crit par Akama (Architecture
Edge
1
). En consquence, les conseils de configuration dcrits dans ce chapitre peuvent tre utiles mme
si vous n'utilisez pas Symfony2.
Varnish supporte seulement l'attribut src pour les balises ESI (les attributs onerror et alt sont
ignors).
Tout d'abord, configurez Varnish afin qu'il annonce son support d'ESI en ajoutant un en-tte Surrogate-
Capability aux requtes transfres l'application backend :
1
2
3
sub vcl_recv {
set req.http.Surrogate-Capability = "abc=ESI/1.0";
}
Puis, optimisez Varnish afin qu'il analyse uniquement le contenu de la rponse lorsqu'il y a au moins une
balise ESI en vrifiant l'en-tte Surrogate-Control que Symfony2 ajoute automatiquement :
1. http://www.w3.org/TR/edge-arch
PDF brought to you by
generated on June 26, 2014
Chapter 10: Comment utiliser Varnish pour acclrer mon site Web | 41
Listing 10-2
Listing 10-3
1
2
3
4
5
6
7
8
9
10
sub vcl_fetch {
if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
unset beresp.http.Surrogate-Control;
// for Varnish >= 3.0
set beresp.do_esi = true;
// for Varnish < 3.0
// esi;
}
}
La compression avec ESI n'tait pas supporte dans Varnish jusqu' la version 3.0 (lire GZIP et
Varnish
2
). Si vous n'utilisez pas Varnish 3.0, mettez un serveur web devant Varnish afin qu'il
effectue la compression.
Invalidation du Cache
Vous ne devriez jamais avoir besoin d'invalider des donnes caches parce que l'invalidation est dj prise
en compte nativement dans les modles de cache HTTP (voir Invalidation du cache).
Nanmoins, Varnish peut tre configur pour accepter une mthode HTTP spcifique PURGE qui va
invalider le cache pour une ressource donne :
1
2
3
4
5
6
7
8
9
10
11
12
sub vcl_hit {
if (req.request == "PURGE") {
set obj.ttl = 0s;
error 200 "Purged";
}
}
sub vcl_miss {
if (req.request == "PURGE") {
error 404 "Not purged";
}
}
Vous devez protger la mthode HTTP PURGE d'une faon ou d'une autre afin d'viter que des
personnes purgent vos donnes caches.
2. https://www.varnish-cache.org/docs/3.0/phk/gzip.html
PDF brought to you by
generated on June 26, 2014
Chapter 10: Comment utiliser Varnish pour acclrer mon site Web | 42
Listing 11-1
Chapter 11
Comment Matriser et Crer de nouveaux
Environnements
Chaque application est une combinaison de code et d'un ensemble de configurations qui dicte comment
ce code doit fonctionner. La configuration peut dfinir la base de donnes tant utilise, si oui ou non
quelque chose doit tre mis en cache, ou quel point le logging doit tre dtaill. Dans Symfony2,
l'ide d' environnements repose sur le fait que la mme base de code peut tre excute en utilisant des
configurations diverses et varies. Par exemple, l'environnement dev devrait utiliser une configuration qui
rend le dveloppement facile et sympa, alors que l'environnement de prod devrait utiliser un ensemble de
configurations optimises pour la rapidit.
Environnements Diffrents, Fichiers de Configuration Diffrents
Une application Symfony2 typique dmarre avec trois environnements : dev, prod, et test. Comme nous
l'avons vu, chaque environnement reprsente simplement une faon d'excuter la mme base de code
avec des configurations diffrentes. Ainsi, cela ne devrait pas tre une surprise pour vous que chaque
environnement charge son propre fichier de configuration. Si vous utilisez le format de configuration
YAML, les fichiers suivants sont utiliss :
pour l'environnement dev : app/config/config_dev.yml
pour l'environnement prod : app/config/config_prod.yml
pour l'environnement test : app/config/config_test.yml
Cela fonctionne grce un simple standard utilis par dfaut dans la classe AppKernel :
1
2
3
4
5
6
7
8
// app/AppKernel.php
// ...
class AppKernel extends Kernel
{
// ...
PDF brought to you by
generated on June 26, 2014
Chapter 11: Comment Matriser et Crer de nouveaux Environnements | 43
Listing 11-2
Listing 11-3
Listing 11-4
Listing 11-5
9
10
11
12
13
public function registerContainerConfiguration(LoaderInterface $loader)
{
$loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml');
}
}
Comme vous pouvez le voir, lorsque Symfony2 est charg, il utilise l'environnement donn pour
dterminer quel fichier de configuration charger. Cela permet d'avoir des environnements multiples d'une
manire lgante, puissante et transparente.
Bien sr, dans la ralit, chaque environnement diffre seulement quelque peu des autres. Gnralement,
tous les environnements vont avoir en commun une mme configuration de base consquente. Ouvrez le
fichier de configuration dev et vous verrez facilement comment ceci est accompli :
1
2
3
imports:
- { resource: config.yml }
# ...
Pour partager une configuration commune, chaque fichier de configuration d'un environnement importe
en premier lieu un fichier de configuration central (config.yml). Le reste du fichier peut ainsi dvier de
la configuration par dfaut en surchargeant des paramtres individuels. Par exemple, par dfaut, la barre
d'outils web_profiler est dsactive. Cependant, en environnement dev, la barre d'outils est active en
modifiant la valeur par dfaut dans le fichier de configuration dev :
1
2
3
4
5
6
7
# app/config/config_dev.yml
imports:
- { resource: config.yml }
web_profiler:
toolbar: true
# ...
Excuter une Application dans Diffrents Environnements
Pour excuter l'application dans chaque environnement, chargez l'application en utilisant soit le
contrleur frontal app.php (pour l'environnement prod), soit app_dev.php (pour l'environnement dev) :
1
2
http://localhost/app.php -> environnement *prod*
http://localhost/app_dev.php -> environnement *dev*
Les URLs donnes supposent que votre serveur web est configur pour utiliser le rpertoire web/
de l'application en tant que racine. Lisez-en plus sur Installer Symfony2.
Si vous ouvrez l'un de ces fichiers, vous allez rapidement voir que l'environnement utilis pour chacun est
explicitement dfini :
1
2
3
4
<?php
require_once __DIR__.'/../app/bootstrap_cache.php';
require_once __DIR__.'/../app/AppCache.php';
PDF brought to you by
generated on June 26, 2014
Chapter 11: Comment Matriser et Crer de nouveaux Environnements | 44
Listing 11-6
Listing 11-7
5
6
7
8
9
use Symfony\Component\HttpFoundation\Request;
$kernel = new AppCache(new AppKernel('prod', false));
$kernel->handle(Request::createFromGlobals())->send();
Comme vous pouvez le voir, la cl prod spcifie que cet environnement va tre excut dans
l'environnement prod. Une application Symfony2 peut tre excute dans n'importe quel environnement
en utilisant ce code et en changeant la chane de caractres de l'environnement.
L'environnement de test est utilis lorsque vous crivez des tests fonctionnels et n'est pas
accessible directement dans le navigateur via un contrleur frontal. En d'autres termes, compar
aux autres environnements, il n'y a pas de fichier de contrleur frontal app_test.php.
Mode de Dbuggage
Quelque chose d'important - sans rapport avec le thme des environnements - est la cl false
la ligne 8 du contrleur frontal ci-dessus. Cette ligne spcifie si oui ou non l'application doit tre
excute en mode de dbuggage . Peu importe l'environnement, une application Symfony2 peut
tre excute avec le mode dbuggage activ ou dsactiv (true ou false). Cela affecte beaucoup
de choses dans l'application, comme par exemple si oui ou non les erreurs doivent tre affiches
ou si les fichiers de cache sont dynamiquement reconstruits chaque requte. Bien que ce ne soit
pas une condition requise, le mode de dbuggage est gnralement dfini comme true pour les
environnements dev et test, et comme false pour l'environnement prod.
En interne, la valeur du mode de dbuggage devient le paramtre kernel.debug utilis dans le
conteneur de service. Si vous regardez le fichier de configuration de l'application, vous verrez le
paramtre utilis pour, par exemple, activer ou dsactiver le logging quand vous utilisez le
DBAL de Doctrine :
1
2
3
4
doctrine:
dbal:
logging: "%kernel.debug%"
# ...
Crer un Nouvel Environnement
Par dfaut, une application Symfony2 possde trois environnements qui grent la plupart des cas.
Bien sr, comme un environnement n'est rien d'autre qu'une chane de caractres qui correspond un
ensemble de configurations, crer un nouvel environnement est assez facile.
Par exemple, supposons qu'avant un dploiement, vous ayez besoin d'effectuer des essais sur votre
application. Une manire de faire cela est d'utiliser presque les mmes paramtres qu'en production,
mais avec le web_profiler de Symfony2 activ. Cela permet Symfony2 d'enregistrer des informations
propos de votre application lorsque vous effectuez vos essais.
La meilleure manire d'accomplir ceci est grce un nouvel environnement nomm, par exemple,
benchmark. Commencez par crer un nouveau fichier de configuration :
1
2
# app/config/config_benchmark.yml
imports:
PDF brought to you by
generated on June 26, 2014
Chapter 11: Comment Matriser et Crer de nouveaux Environnements | 45
Listing 11-8
Listing 11-9
Listing 11-10
Listing 11-11
3
4
5
6
- { resource: config_prod.yml }
framework:
profiler: { only_exceptions: false }
Grce ce simple ajout, l'application supporte dsormais un nouvel environnement appel benchmark.
Ce nouveau fichier de configuration importe la configuration de l'environnement prod et la modifie. Cela
garantit que le nouvel environnement est identique l'environnement prod, except les changements
effectus explicitement ici.
Comme vous allez vouloir accder cet environnement via un navigateur, vous devriez aussi crer
un contrleur frontal pour lui. Copiez le fichier web/app.php vers web/app_benchmark.php et ditez
l'environnement afin qu'il contienne la valeur benchmark :
1
2
3
4
5
6
7
8
9
<?php
require_once __DIR__.'/../app/bootstrap.php';
require_once __DIR__.'/../app/AppKernel.php';
use Symfony\Component\HttpFoundation\Request;
$kernel = new AppKernel('benchmark', false);
$kernel->handle(Request::createFromGlobals())->send();
Le nouvel environnement est maintenant accessible via:
1 http://localhost/app_benchmark.php
Certains environnements, comme dev, n'ont jamais pour but d'tre accds par le public sur
quelconque serveur dploy. La raison de ceci est que certains environnements, pour des raisons de
dbuggage, pourraient donner trop d'informations propos de l'application ou de l'infrastructure
sous-jacente. Afin d'tre sr que ces environnements ne soient pas accessibles, le contrleur
frontal est gnralement protg des adresses IP externes grce au code suivant plac en haut du
contrleur :
1
2
3
if (!in_array(@$_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'))) {
die('You are not allowed to access this file. Check
'.basename(__FILE__).' for more information.');
}
Les environnements et le rpertoire de Cache
Symfony2 profite du cache de diffrentes manires : la configuration de l'application, la configuration du
routage, les templates Twig et encore plus sont cachs dans des objets PHP stocks dans des fichiers sur
le systme de fichiers.
Par dfaut, ces fichiers cachs sont largement stocks dans le rpertoire app/cache. Cependant, chaque
environnement cache son propre ensemble de fichiers :
PDF brought to you by
generated on June 26, 2014
Chapter 11: Comment Matriser et Crer de nouveaux Environnements | 46
1
2
app/cache/dev - rpertoire de cache pour l'environnement *dev*
app/cache/prod - rpertoire de cache pour l'environnement *prod*
Quelquefois, lorsque vous dbuggez, il pourrait tre utile d'inspecter un fichier cach pour comprendre
comment quelque chose fonctionne. Quand vous faites a, rappelez-vous de regarder dans le rpertoire
de l'environnement que vous tes en train d'utiliser (la plupart du temps dev lorsque vous dveloppez et
dbuggez). Bien que celui-ci puisse varier, le rpertoire app/cache/dev inclut ce qui suit :
appDevDebugProjectContainer.php - le conteneur de service cach qui reprsente la
configuration cache de l'application ;
appdevUrlGenerator.php - la classe PHP gnre sur la base de la configuration de routage et
utilise lors de la gnration d'URLs ;
appdevUrlMatcher.php - la classe PHP utilise pour la correspondance des routes - regardez
ici pour voir la logique des expressions rgulires compiles et utilises pour faire correspondre
les URLs entrantes aux diffrentes routes ;
twig/ - ce rpertoire contient tous les templates Twig cachs.
Vous pouvez facilement changer l'emplacement du rpertoire et son nom. Pour plus
d'informations, lisez l'article Comment surcharger la structure de rpertoires par dfaut de Symfony.
Aller plus loin
Lisez l'article sur Comment configurer les paramtres externes dans le conteneur de services.
PDF brought to you by
generated on June 26, 2014
Chapter 11: Comment Matriser et Crer de nouveaux Environnements | 47
Listing 12-1
Listing 12-2
Chapter 12
Comment surcharger la structure de
rpertoires par dfaut de Symfony
Symfony est fourni automatiquement avec une structure de rpertoires par dfaut. Vous pouvez
facilement surcharger cette structure de rpertoires et crer la vtre. La structure par dfaut est la suivante
:
1
2
3
4
5
6
7
8
9
10
11
12
app/
cache/
config/
logs/
...
src/
...
vendor/
...
web/
app.php
...
Surcharger le rpertoire cache
Vous pouvez surcharger le rpertoire de cache en surchargeant la mthode getCacheDir dans la classe
AppKernel de votre application:
1
2
3
4
5
6
7
// app/AppKernel.php
// ...
class AppKernel extends Kernel
{
// ...
PDF brought to you by
generated on June 26, 2014
Chapter 12: Comment surcharger la structure de rpertoires par dfaut de Symfony | 48
Listing 12-3
Listing 12-4
8
9
10
11
12
public function getCacheDir()
{
return $this->rootDir.'/'.$this->environment.'/cache/';
}
}
$this->rootDir est le chemin absolu vers le rpertoire app et $this->environment est l'environnement
actuel (c-a-d dev). Dans ce cas, vous avez chang l'emplacement du rpertoire cache pour qu'il devienne
app/{environment}/cache.
Vous devriez avoir un rpertoire cache diffrent pour chaque environnement, sinon certains effets
de bord pourraient survenir. Chaque environnement gnre ses propres fichiers de configuration
en cache, et donc, chacun a besoin de son propre rpertoire pour stocker ces fichiers.
Surcharger le rpertoire logs
Pour surcharger le rpertoire logs, procdez de la mme manire que pour le rpertoire cache. La seule
diffrence est que vous devez surcharger la mthode getLogDir:
1
2
3
4
5
6
7
8
9
10
11
12
// app/AppKernel.php
// ...
class AppKernel extends Kernel
{
// ...
public function getLogDir()
{
return $this->rootDir.'/'.$this->environment.'/logs/';
}
}
Ici, vous avez chang l'emplacement du rpertoire pour app/{environment}/logs.
Surcharger le rpertoire web
Si vous avez besoin de renommer ou de dplacer le rpertoire web, la seule chose dont vous devez vous
assurer et que le chemin vers le rpertoire app est toujours correct dans vos contrleurs frontaux app.php
et app_dev.php. Si vous renommez simplement le rpertoire, alors tout est dj bon. Mais si vous le
dplacez, vous aurez besoin de modifier les chemins dans ces fichiers:
1
2
require_once __DIR__.'/../Symfony/app/bootstrap.php.cache';
require_once __DIR__.'/../Symfony/app/AppKernel.php';
Certains serveurs mutualiss ont un rpertoire racine public_html. Renommer votre rpertoire
de web pour public_html est une manire de faire fonctionner votre projet Symfony sur votre
serveur mutualis. Une autre manire serait dployer votre application en dehors de la racine web,
supprimer le rpertoire public_html puis le remplacer par une lien symbolique vers le rpertoire
web de votre projet.
PDF brought to you by
generated on June 26, 2014
Chapter 12: Comment surcharger la structure de rpertoires par dfaut de Symfony | 49
Listing 12-5
Listing 12-6
Si vous utilisez le bundle AsseticBundle, vous devrez le configurer pour qu'il utilise le bon
rpertoire web :
1
2
3
4
5
6
# app/config/config.yml
# ...
assetic:
# ...
read_from: "%kernel.root_dir%/../../public_html"
Maintenant, vous devez juste exporter vos ressources pour que votre application puisse
fonctionner :
1 $ php app/console assetic:dump --env=prod --no-debug
PDF brought to you by
generated on June 26, 2014
Chapter 12: Comment surcharger la structure de rpertoires par dfaut de Symfony | 50
Listing 13-1
Chapter 13
Comment configurer les paramtres externes
dans le conteneur de services
Dans le chapitre doc:/cookbook/configuration/environments, Vous avez vu comment grer la
configuration de votre application. Parfois on aura cependant besoin de stocker certaines donnes hors
du code du projet, par exemple des mots de passe, ou des paramtres de configuration d'une base de
donnes. La flexibilit du conteneur de services Symfony vous le permet.
Variables d'environnement
Symfony va reprer toute variable d'environnement prfixe de SYMFONY__ et la stocker en tant que
paramtre dans le conteneur de services. les doubles tirets bas sont remplacs par un point, le point
n'tant pas un caractre permis dans un nom de variable d'environnement.
Par exemple, si vous utilisez Apache, les variables d'environnement peuvent tre dfinies par la
configuration VirtualHost suivante:
1
2
3
4
5
6
7
8
9
10
11
12
<VirtualHost *:80>
ServerName Symfony2
DocumentRoot "/path/to/symfony_2_app/web"
DirectoryIndex index.php index.html
SetEnv SYMFONY__DATABASE__USER user
SetEnv SYMFONY__DATABASE__PASSWORD secret
<Directory "/path/to/symfony_2_app/web">
AllowOverride All
Allow from All
</Directory>
</VirtualHost>
PDF brought to you by
generated on June 26, 2014
Chapter 13: Comment configurer les paramtres externes dans le conteneur de services | 51
Listing 13-2
Listing 13-3
Listing 13-4
Listing 13-5
L'exemple de configuration ci-dessus concerne Apache, l'aide de la directive SetEnv
1
. Cependant,
ceci fonctionnera pour tout serveur permettant la dfinition de variables d'environnement.
De mme, afin de permettre l'usage de variables d'environnement par la console (sans serveur), il
est ncessaire de dfinir lesdites variables comme des variables shell.
Sur un systme Unix:
1
2
$ export SYMFONY__DATABASE__USER=user
$ export SYMFONY__DATABASE__PASSWORD=secret
Maintenant que les variables d'environnement ont t dclares, elles seront prsentes dans la variable
globale $_SERVER de PHP. Symfony va donc automatiquement recopier les valeurs des variables
$_SERVER prfixes de SYMFONY__ en tant que paramtres du conteneur de services.
Vous pourrez ainsi faire rfrence ces paramtres si ncessaire.
1
2
3
4
5
6
doctrine:
dbal:
driver pdo_mysql
dbname: symfony2_project
user: "%database.user%"
password: "%database.password%"
Constantes
Le conteneur de services permet galement la dfinition de constantes PHP comme paramtres. Il suffit
de faire correspondre le nom de votre constante une cl de paramtre et de prciser le type constant.
1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<parameters>
<parameter key="global.constant.value"
type="constant">GLOBAL_CONSTANT</parameter>
<parameter key="my_class.constant.value"
type="constant">My_Class::CONSTANT_NAME</parameter>
</parameters>
</container>
Ceci ne fonctionne qu'avec une configuration XML. Si vous n'utilisez pas XML pour la
configuration, importez un fichier XML pour pouvoir le faire:
1
2
3
# app/config/config.yml
imports:
- { resource: parameters.xml }
1. http://httpd.apache.org/docs/current/env.html
PDF brought to you by
generated on June 26, 2014
Chapter 13: Comment configurer les paramtres externes dans le conteneur de services | 52
Listing 13-6
Listing 13-7
Diverses considrations
La directive imports peut tre utilise pour rcuprer des paramtres stocks ailleurs. L'import d'un
fichier PHP vous permet un maximum de flexibilit dans le conteneur. Le code suivant importe un fichier
parameters.php.
1
2
3
# app/config/config.yml
imports:
- { resource: parameters.php }
Un fichier de ressource peut tre de plusieurs types. La directive imports accepte des ressources de
type PHP, XML, YAML, INI, et closure.
Dans le fichier parameters.php, vous allez indiquer au conteneur de services les paramtres que vous
dsirez dfinir. Ceci est notamment utile lorsque d'importants lments de configuration sont disponibles
dans un format non standard. L'exemple ci-dessous importe des paramtres de configuration de base de
donnes pour Drupal dans le conteneur de services.
1
2
3
4
// app/config/parameters.php
include_once('/path/to/drupal/sites/default/settings.php');
$container->setParameter('drupal.database.url', $db_url);
PDF brought to you by
generated on June 26, 2014
Chapter 13: Comment configurer les paramtres externes dans le conteneur de services | 53
Listing 14-1
Chapter 14
Comment stocker les sessions dans la base de
donnes grce PdoSessionStorage
Par dfaut, Symfony2 stocke les sessions dans des fichiers. La plupart des sites de moyenne et grande
envergure vont cependant vouloir stocker les sessions dans la base de donnes plutt que dans des
fichiers, car l'usage des bases de donnes permet plus facilement la gestion de la monte en charge dans
un environnement multi-serveurs.
Symfony2 incorpore une solution de stockage de sessions dans la base de donnes appele
PdoSessionHandler
1
. Pour l'utiliser, il vous suffit de changer quelques paramtres dans config.yml (ou
le format de configuration de votre choix):
New in version 2.1: Dans Symfony2.1 la classe et l'espace de noms ont volu. Vous trouvez
dornavant la classe de stockage de session dans l'espace de nom Session\Storage :
Symfony\Component\HttpFoundation\Session\Storage. Notez galement que dans Symfony
2.1, vous devrez configurer handler_id et non pas storage_id comme en Symfony2.0.
Ci-dessous, vous verrez que %session.storage.options% n'est plus utilis.
# app/config/config.yml
framework:
session:
# ...
handler_id: session.handler.pdo
parameters:
pdo.db_options:
db_table: session
db_id_col: session_id
db_data_col: session_value
db_time_col: session_time
services:
pdo:
1. http://api.symfony.com/2.3/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.html
PDF brought to you by
generated on June 26, 2014
Chapter 14: Comment stocker les sessions dans la base de donnes grce PdoSessionStorage | 54
Listing 14-2
Listing 14-3
Listing 14-4
class: PDO
arguments:
dsn: "mysql:dbname=mydatabase"
user: myuser
password: mypassword
session.handler.pdo:
class: Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler
arguments: [@pdo, %pdo.db_options%]
db_table : Nom de la table des sessions dans votre base de donnes
db_id_col : Nom de la colonne identifiant dans la table des sessions (de type VARCHAR(255)
ou plus)
db_data_col : Nom de la colonne des valeurs dans la table des sessions (de type TEXT ou
CLOB)
db_time_col : Nom de la colonne temps dans la table des sessions (INTEGER)
Partager les informations de connection la base de donnes
Avec cette configuration, les paramtres de connexion la base de donnes ne concernent que le stockage
des sessions. Ceci peut fonctionner si vous ddiez une base de donnes aux sessions.
Mais si vous dsirez stocker les informations de session dans la mme base de donnes que le reste des
donnes du projet, vous pouvez rutiliser les paramtres de connexion dfinis dans dans parameter.ini
en rfrenant lesdits paramtres :
pdo:
class: PDO
arguments:
- "mysql:dbname=%database_name%"
- %database_user%
- %database_password%
Exemple d'instruction SQL
MySQL
L'instruction SQL pour la cration d'une table de sessions sera probablement proche de :
1
2
3
4
5
6
CREATE TABLE `session` (
`session_id` varchar(255) NOT NULL,
`session_value` text NOT NULL,
`session_time` int(11) NOT NULL,
PRIMARY KEY (`session_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
PostgreSQL
Pour PostgreSQL, ce sera plutt :
1
2
3
CREATE TABLE session (
session_id character varying(255) NOT NULL,
session_value text NOT NULL,
PDF brought to you by
generated on June 26, 2014
Chapter 14: Comment stocker les sessions dans la base de donnes grce PdoSessionStorage | 55
4
5
6
session_time integer NOT NULL,
CONSTRAINT session_pkey PRIMARY KEY (session_id)
);
PDF brought to you by
generated on June 26, 2014
Chapter 14: Comment stocker les sessions dans la base de donnes grce PdoSessionStorage | 56
Listing 15-1
Listing 15-2
Chapter 15
Comment utiliser le routeur Apache
Symfony2, bien que dj trs rapide, fournit galement plusieurs astuces pour augmenter encore la vitesse
avec quelques modifications. L'une de ces astuces est de laisser Apache grer les routes directement plutt
que d'utiliser Symfony2 pour le faire.
Changer les paramtres de la configuration du routeur
Pour dumper les routes Apache, vous devez d'abord modifier les paramtres de configuration pour dire
Symfony2 d'utiliser le ApacheUrlMatcher plutt que celui par dfaut :
1
2
3
4
# app/config/config_prod.yml
parameters:
router.options.matcher.cache_class: ~ # Dsactive le cache du routeur
router.options.matcher_class: Symfony\Component\Routing\Matcher\ApacheUrlMatcher
Notez que cet ApacheUrlMatcher
1
tend UrlMatcher
2
donc mme si vous ne regnrez pas les
rgles url_rewrite, tout fonctionnera (parce qu' la fin de ApacheUrlMatcher::match() un appel
parent::match() est ralis).
Gnrer les rgles mod_rewrite
Pour tester que cela fonctionne, crons une route trs basique pour le bundle de dmo :
1
2
3
4
# app/config/routing.yml
hello:
pattern: /hello/{name}
defaults: { _controller: AcmeDemoBundle:Demo:hello }
1. http://api.symfony.com/2.3/Symfony/Component/Routing/Matcher/ApacheUrlMatcher.html
2. http://api.symfony.com/2.3/Symfony/Component/Routing/Matcher/UrlMatcher.html
PDF brought to you by
generated on June 26, 2014
Chapter 15: Comment utiliser le routeur Apache | 57
Listing 15-3
Listing 15-4
Listing 15-5
Listing 15-6
Maintenant gnrez les rgles url_rewrite :
1 $ php app/console router:dump-apache -e=prod --no-debug
Ce qui devrait afficher quelque chose du genre :
1
2
3
4
5
6
7
# skip "real" requests
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule .* - [QSA,L]
# hello
RewriteCond %{REQUEST_URI} ^/hello/([^/]+?)$
RewriteRule .* app.php
[QSA,L,E=_ROUTING__route:hello,E=_ROUTING_name:%1,E=_ROUTING__controller:AcmeDemoBundle\:Demo\:hello]
Vous pouvez maintenant rcrire le web/.htaccess pour utiliser les nouvelles rgles. Avec cet exemple, cela
ressemblerait ceci :
1
2
3
4
5
6
7
8
9
10
11
<IfModule mod_rewrite.c>
RewriteEngine On
# skip "real" requests
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule .* - [QSA,L]
# hello
RewriteCond %{REQUEST_URI} ^/hello/([^/]+?)$
RewriteRule .* app.php
[QSA,L,E=_ROUTING__route:hello,E=_ROUTING_name:%1,E=_ROUTING__controller:AcmeDemoBundle\:Demo\:hello]
</IfModule>
La procdure ci-dessus devrait tre effectue chaque fois que vous ajoutez/modifiez une route si
vous voulez en tirer pleinement avantage
C'est tout ! Nous sommes maintenant prts utiliser les rgles de routage Apache.
Modification supplmentaires
Pour gagner un peu de temps d'xcution, changez les occurences de Request par ApacheRequest dans
le fichier web/app.php:
1
2
3
4
5
6
7
8
9
10
// web/app.php
require_once __DIR__.'/../app/bootstrap.php.cache';
require_once __DIR__.'/../app/AppKernel.php';
//require_once __DIR__.'/../app/AppCache.php';
use Symfony\Component\HttpFoundation\ApacheRequest;
$kernel = new AppKernel('prod', false);
$kernel->loadClassCache();
PDF brought to you by
generated on June 26, 2014
Chapter 15: Comment utiliser le routeur Apache | 58
11
12
//$kernel = new AppCache($kernel);
$kernel->handle(ApacheRequest::createFromGlobals())->send();
PDF brought to you by
generated on June 26, 2014
Chapter 15: Comment utiliser le routeur Apache | 59
Listing 16-1
Chapter 16
Comment crer une commande pour la
Console
La page Console de la partie Components (Le Composant Console) dcrit comment crer une commande.
Cet article du Cookbook aborde les diffrences lorsque vous crez des commandes pour la console avec
le framework Symfony2.
Enregistrement automatique des commandes
Pour que les commandes soient automatiquement disponibles dans Symfony2, crez un rpertoire
Command dans votre bundle et crez un fichier php se terminant par Command.php pour chaque commande
que vous voulez crer. Par exemple, si vous voulez tendre le bundle AcmeDemoBundle (disponible
dans la Standard Edition de Symfony2) pour vous saluer en ligne de commande, crez le fichier
GreetCommand.php et insrez y le contenu suivant:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/Acme/DemoBundle/Command/GreetCommand.php
namespace Acme\DemoBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class GreetCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('demo:greet')
->setDescription('Saluer une personne')
->addArgument('name', InputArgument::OPTIONAL, 'Qui voulez vous saluer??')
->addOption('yell', null, InputOption::VALUE_NONE, 'Si dfinie, la tche
PDF brought to you by
generated on June 26, 2014
Chapter 16: Comment crer une commande pour la Console | 60
Listing 16-2
Listing 16-3
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
criera en majuscules')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$name = $input->getArgument('name');
if ($name) {
$text = 'Bonjour '.$name;
} else {
$text = 'Bonjour';
}
if ($input->getOption('yell')) {
$text = strtoupper($text);
}
$output->writeln($text);
}
}
Cette commande sera maintenant automatiquement prte tre excute :
1 $ app/console demo:greet Fabien
Tester les commandes
Pour tester les commandes utilises dans le cadre du framework, la classe
Symfony\Bundle\FrameworkBundle\Console\Application
1
devrait tre utilise au lieu de la classe
Symfony\Component\Console\Application
2
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Acme\DemoBundle\Command\GreetCommand;
class ListCommandTest extends \PHPUnit_Framework_TestCase
{
public function testExecute()
{
// mockez le Kernel ou crez en un selon vos besoins
$application = new Application($kernel);
$application->add(new GreetCommand());
$command = $application->find('demo:greet');
$commandTester = new CommandTester($command);
$commandTester->execute(array('command' => $command->getName()));
$this->assertRegExp('/.../', $commandTester->getDisplay());
// ...
}
}
1. http://api.symfony.com/2.3/Symfony/Bundle/FrameworkBundle/Console/Application.html
2. http://api.symfony.com/2.3/Symfony/Component/Console/Application.html
PDF brought to you by
generated on June 26, 2014
Chapter 16: Comment crer une commande pour la Console | 61
Listing 16-4
Rcuprer des services du Conteneur de services
En utilisant ContainerAwareCommand
3
comme classe parente de la commande (au lieu de la classe
basique Command
4
), vous avez accs au conteneur de services. En d'autres termes, vous avez accs tous
les services configurs. Par exemple, vous pouvez facilement tendre la tche pour grer les traductions:
1
2
3
4
5
6
7
8
9
10
protected function execute(InputInterface $input, OutputInterface $output)
{
$name = $input->getArgument('name');
$translator = $this->getContainer()->get('translator');
if ($name) {
$output->writeln($translator->trans('Hello %name%!', array('%name%' => $name)));
} else {
$output->writeln($translator->trans('Hello!'));
}
}
3. http://api.symfony.com/2.3/Symfony/Bundle/FrameworkBundle/Command/ContainerAwareCommand.html
4. http://api.symfony.com/2.3/Symfony/Component/Console/Command/Command.html
PDF brought to you by
generated on June 26, 2014
Chapter 16: Comment crer une commande pour la Console | 62
Listing 17-1
Listing 17-2
Listing 17-3
Listing 17-4
Listing 17-5
Chapter 17
Comment utiliser la Console
La page Utiliser les commandes de console, les raccourcis et les commandes prconstruites de la
documentation des Composants traite des options de la console. Lorsque vous utilisez la console comme
partie intgrante du framework full-stack, des options globales supplmentaires sont galement
disponibles.
Par dfaut, les commandes de la console sont excutes dans l'environnement de dev, vous pourriez
vouloir changer cela pour certaines commandes. Par exemple, vous pourriez vouloir excuter certaines
commandes dans l'environnement de prod pour des raisons de performance. Le rsultat de certaines
commandes sera diffrent selon l'environnement, par exemple, la commande cache:clear ne videra le
cache que de l'environnement spcifi. Pour vider le cache de prod, vous devez excuter :
1 $ php app/console cache:clear --env=prod
ou son quivalent :
1 $ php app/console cache:clear -e prod
En plus de changer l'environnement, vous pouvez galement choisir de dsactiver le mode debug. Cela
peut tre utile lorsque vous voulez excuter des commandes dans l'environnement de dev mais viter de
dgrader les performances en collectant des donnes de debug :
1 $ php app/console list --no-debug
Il existe un shell interactive qui vous permet de taper des commandes sans devoir spcifier php app/
console chaque fois, ce qui est trs utile si vous devez saisir plusieurs commandes. Pour entrer dans le
shell, excutez :
1
2
$ php app/console --shell
$ php app/console -s
Vous pouvez maintenant vous contenter de saisir simplement le nom de la commande :
PDF brought to you by
generated on June 26, 2014
Chapter 17: Comment utiliser la Console | 63
Listing 17-6
1 Symfony > list
Lorsque vous utilisez le shell, vous pouvez choisir d'excuter chaque commande dans un processus
distinct :
1
2
$ php app/console --shell --process-isolation
$ php app/console -s --process-isolation
Lorsque vous faites cela, la sortie ne sera pas colorise et l'interactivit n'est pas supporte, vous devrez
donc passer chaque paramtre explicitement.
A moins que vous n'utilisiez des processus spars, vider le cache dans le shell n'aura aucun effet
sur les commandes que vous excutez. Ceci est d au fait que les fichiers de cache originaux sont
toujours utiliss.
PDF brought to you by
generated on June 26, 2014
Chapter 17: Comment utiliser la Console | 64
Listing 18-1
Chapter 18
Comment gnrer des URLs et envoyer des
emails depuis la Console
Malheureusement, le contexte de la ligne de commande n'a pas "conscience" de votre VirtualHost ou
votre nom de domaine. Cela signifie que si vous gnrez des URLs absolues travers une Commande
Console vous finirez sans doute par obtenir quelque chose comme http://localhost/foo/bar, ce qui
n'est pas trs utile.
Pour corriger cela, vous devez configurer le "request context", qui est une faon lgante de dire que
vous avez besoin de configurer votre environnement afin qu'il connaisse quelle URL il devrait utiliser la
gnration d'URLs.
Il y a deux faons de configurer le request context : au niveau de l'application ou via une commande.
Configurer le Request Contexte globalement
Pour configurer le Context Request - qui est utilis par le gnrateur d'URL - vous pouvez redfinir les
paramtres qu'il utilise comme valeur par dfaut pour changer l'host par dfaut (localhost) et le scheme
(http). Vous devez galement configurer le chemain de base si Symfony ne tourne pas la racine de votre
serveur.
Notez que ceci n'impacte pas les URLs gnres via les requtes web normales, puisqu'elles remplaces
celles par dfauts.
1
2
3
4
5
# app/config/parameters.yml
parameters:
router.request_context.host: example.org
router.request_context.scheme: https
router.request_context.base_url: my/path
PDF brought to you by
generated on June 26, 2014
Chapter 18: Comment gnrer des URLs et envoyer des emails depuis la Console | 65
Listing 18-2
Listing 18-3
Configurer le Request Context via la Command
Pour le changer uniquement en une commande vous pouvez simplement rcuprer le Request Context
depuis le service router et remplaants ses rglages:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Acme/DemoBundle/Command/DemoCommand.php
// ...
class DemoCommand extends ContainerAwareCommand
{
protected function execute(InputInterface $input, OutputInterface $output)
{
$context = $this->getContainer()->get('router')->getContext();
$context->setHost('example.com');
$context->setScheme('https');
$context->setBaseUrl('my/path');
// ... votre code ici
}
}
Utiliser le Memory Spooling
Envoyer des e-mails depuis la commande console se fait de la mme manire que dans le cookbook
Comment envoyer un Email hormis le fait que le memory spooling est utilis.
En utilisant le memory spooling (consultez le cookbook Comment utiliser le Spool d'Email pour plus
d'informations), vous devez savoir que c'est parce que Symfony gre la commande console de manire
particulire, que les e-mails ne sont pas envoys automatiquement. Vous devez prendre soin de nttoyer
file vous mme. Utiliser le code suivant pour envoyer des emails depuis la commande console:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$message = new \Swift_Message();
// ... prparez le message
$container = $this->getContainer();
$mailer = $container->get('mailer');
$mailer->send($message);
// maintenant nettoyez la file manuellement
$spool = $mailer->getTransport()->getSpool();
$transport = $container->get('swiftmailer.transport.real');
$spool->flushQueue($transport);
Une autre option est de crer un environnement qui ne serait utilis uniquement que par la commande
console et utiliserait une autre mthode de spooling.
S'occuper du spooling n'est uniquement ncessaie que si le memory spolling est utilis. Si vous
utilisez le file spooling (ou pas de spooling du tout), il n'est pas utile de nettoyer manuellement
dans une commande.
PDF brought to you by
generated on June 26, 2014
Chapter 18: Comment gnrer des URLs et envoyer des emails depuis la Console | 66
Listing 19-1
Chapter 19
Comment activer les logs dans la commande
console
Le composant Console ne fournit aucune solution out of the box. Normallement, vous lancez les
commandes console manuellement et observez la sortie, c'est pourquoi le systme de log n'est pas
fourni. Cependant, il y a quelques cas o vous devriez en avoir besoin. Par example, si vous lancez une
commande console sans surveillance, comme des cron ou des scripts de dploiements, il serait plus facile
d'utiliser les fonctionnalits de log de Symfony, au lieu de configurer d'autres outils pour rassembler la
sortie console et la traiter. Ceci peut tre particulirement pratique si vous avez dj quelques paramtres
existants pour aggrger et analyser les logs Symfony.
Il y a fondamentalement deux cas de log dont vous auriez besoin :
Logguer manuellement quelques informations depuis votre commande;
Logguer les exceptions non catches.
Logguer manuellement depuis la commande console
Cette solution est vraiment simple. Lorsque vous crez une commande console dans le framework
complet comme dcrit dans "Comment crer une commande pour la Console", votre commande tend
ContainerAwareCommand
1
. Cela signifie que vous pouvez simplement accder au service logger standard
travers le conteneur de services et utilisez le pour logguer:
1
2
3
4
5
6
7
8
9
// src/Acme/DemoBundle/Command/GreetCommand.php
namespace Acme\DemoBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Psr\Log\LoggerInterface;
1. http://api.symfony.com/2.3/Symfony/Bundle/FrameworkBundle/Command/ContainerAwareCommand.html
PDF brought to you by
generated on June 26, 2014
Chapter 19: Comment activer les logs dans la commande console | 67
Listing 19-2
Listing 19-3
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class GreetCommand extends ContainerAwareCommand
{
// ...
protected function execute(InputInterface $input, OutputInterface $output)
{
/** @var $logger LoggerInterface */
$logger = $this->getContainer()->get('logger');
$name = $input->getArgument('name');
if ($name) {
$text = 'Hello '.$name;
} else {
$text = 'Hello';
}
if ($input->getOption('yell')) {
$text = strtoupper($text);
$logger->warning('Yelled: '.$text);
} else {
$logger->info('Greeted: '.$text);
}
$output->writeln($text);
}
}
Selon l'environnement dans lequel vous lancez votre commande (et votre paramtrage de logging), vous
devriez voir les entres loggues dans le fichier app/logs/dev.log ou app/logs/prod.log`.
Activer le logging automatique des Exceptions
Pour faire que votre application console rcupre les logs des exceptions manques automatiquement
pour toutes vos commande, vous pouvez utiliser console events.
New in version 2.3: Les vnements console ont t ajouts en Symfony 2.3.
Dans un premier temps, configurez un couteur (listener) pour les vnements exception de la console
dans le conteneur de service :
1
2
3
4
5
6
7
8
# app/config/services.yml
services:
kernel.listener.command_dispatch:
class: Acme\DemoBundle\EventListener\ConsoleExceptionListener
arguments:
logger: "@logger"
tags:
- { name: kernel.event_listener, event: console.exception }
Puis implmentez l'couteur (listener):
PDF brought to you by
generated on June 26, 2014
Chapter 19: Comment activer les logs dans la commande console | 68
Listing 19-4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// src/Acme/DemoBundle/EventListener/ConsoleExceptionListener.php
namespace Acme\DemoBundle\EventListener;
use Symfony\Component\Console\Event\ConsoleExceptionEvent;
use Psr\Log\LoggerInterface;
class ConsoleExceptionListener
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function onConsoleException(ConsoleExceptionEvent $event)
{
$command = $event->getCommand();
$exception = $event->getException();
$message = sprintf(
'%s: %s (uncaught exception) at %s line %s while running console command `%s`',
get_class($exception),
$exception->getMessage(),
$exception->getFile(),
$exception->getLine(),
$command->getName()
);
$this->logger->error($message);
}
}
Dans le code ci-dessus, lorsque l'une des commandes lance une exception, le listener recevera un
vnement. Vous pouvez simplement logguer en passant le service logger via la configuration du service.
Votre mthode reoit un objet ConsoleExceptionEvent
2
, qui a une mthode pour rcuprer les
informations concernant l'vnement et l'exception.
Logguer les statuts "non-0 exit"
L'utilisation du logger de la console peut tre pouss plus loin en logguant les statuts "non-0 exit". De
cette faon, vous saurez si une commande comporte des erreurs, mme si une aucune exception n'a t
leve.
Dans un premier temps, configurez un couteur pour l'vnement console.termine dans le conteneur de
services :
1
2
3
4
5
6
# app/config/services.yml
services:
kernel.listener.command_dispatch:
class: Acme\DemoBundle\EventListener\ConsoleTerminateListener
arguments:
logger: "@logger"
2. http://api.symfony.com/2.3/Symfony/Component/Console/Event/ConsoleExceptionEvent.html
PDF brought to you by
generated on June 26, 2014
Chapter 19: Comment activer les logs dans la commande console | 69
Listing 19-5
7
8
tags:
- { name: kernel.event_listener, event: console.terminate }
Puis implmentez l'couteur (listener):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// src/Acme/DemoBundle/EventListener/ConsoleExceptionListener.php
namespace Acme\DemoBundle\EventListener;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Psr\Log\LoggerInterface;
class ConsoleTerminateListener
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function onConsoleTerminate(ConsoleTerminateEvent $event)
{
$statusCode = $event->getExitCode();
$command = $event->getCommand();
if ($statusCode === 0) {
return;
}
if ($statusCode > 255) {
$statusCode = 255;
$event->setExitCode($statusCode);
}
$this->logger->warning(sprintf(
'Command `%s` exited with status code %d',
$command->getName(),
$statusCode
));
}
}
PDF brought to you by
generated on June 26, 2014
Chapter 19: Comment activer les logs dans la commande console | 70
Listing 20-1
Chapter 20
Comment personnaliser les pages d'erreur
Lorsqu'une exception quelconque est lance dans Symfony2, cette dernire est capture par la classe
Kernel et ventuellement transmise un contrleur spcial, TwigBundle:Exception:show pour qu'il la
gre. Ce contrleur, qui fait partie du coeur de TwigBundle, dtermine quelle template d'erreur afficher
et le code de statut qui devrait tre dfini pour l'exception donne.
Les pages d'erreur peuvent tre personnalises de deux manires diffrentes, dpendant du niveau de
contrle que vous souhaitez :
1. Personnalisez les templates d'erreur des diffrentes pages d'erreur (expliqu ci-dessous) ;
2. Remplacez le contrleur d'exception par dfaut TwigBundle::Exception:show par votre
propre contrleur et grez le comme vous le dsirez (voir exception_controller dans la rfrence
de Twig) ;
La personnalisation de la gestion d'exception est en fait bien plus puissante que ce qui est crit
dans ces lignes. Un vnement interne, kernel.exception, est lanc et permet d'avoir le contrle
complet de la gestion des exceptions. Pour plus d'informations, voir L'vnement kernel.exception.
Tous les templates d'erreur se trouvent dans le TwigBundle. Pour surcharger ces templates, utilisez
simplement la mthode standard qui permet de surcharger un template qui se trouve dans un bundle.
Pour plus d'informations, voir La Surcharge de templates de Bundle.
Par exemple, pour surcharger le template d'erreur par dfaut qui est affich l'utilisateur final, crez
un nouveau template situ cet emplacement app/Resources/TwigBundle/views/Exception/
error.html.twig :
1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Une erreur est survenue : {{ status_text }}</title>
</head>
<body>
<h1>Oups! Une erreur est survenue</h1>
<h2>Le serveur a retourn une erreur "{{ status_code }} {{ status_text }}".</h2>
PDF brought to you by
generated on June 26, 2014
Chapter 20: Comment personnaliser les pages d'erreur | 71
10
11
</body>
</html>
Si vous n'tes pas familier avec Twig, ne vous inquitez pas. Twig est un moteur de templating
simple, puissant et optionnel qui est intgr dans Symfony2. Pour plus d'informations propos de
Twig, voir Crer et utiliser les templates.
En plus de la page d'erreur HTML standard, Symfony fournit une page d'erreur par dfaut pour
quasiment tous les formats de rponse les plus communs, incluant JSON (error.json.twig), XML
(error.xml.twig), et mme Javascript (error.js.twig), pour n'en nommer que quelques uns. Pour
surcharger n'importe lequel de ces templates, crez simplement un nouveau fichier avec le mme nom
dans le rpertoire app/Resources/TwigBundle/views/Exception. C'est la manire standard de
surcharger n'importe quel template qui se trouve dans un bundle.
Personnaliser la page 404 et les autres pages d'erreur
Vous pouvez aussi personnaliser des templates d'erreur spcifiques en vous basant sur le code de
statut HTTP. Par exemple, crez un template app/Resources/TwigBundle/views/Exception/
error404.html.twig pour afficher une page spciale pour les erreurs 404 (page non trouve).
Symfony utilise l'algorithme suivant pour dterminer quel template utiliser :
Premirement, il cherche un template pour le format et le code de statut donn (ex
error404.json.twig) ;
S'il n'existe pas, il cherche un template pour le format donn (ex error.json.twig) ;
S'il n'existe pas, il se rabat sur le template HTML (ex error.html.twig).
Pour voir la liste complte des templates d'erreur par dfaut, jetez un oeil au rpertoire Resources/
views/Exception du TwigBundle. Dans une installation standard de Symfony2, le TwigBundle
se trouve cet emplacement : vendor/symfony/symfony/src/Symfony/Bundle/TwigBundle.
Souvent, la faon la plus facile de personnaliser une page d'erreur est de la copier depuis le
TwigBundle vers le dossier app/Resources/TwigBundle/views/Exception, puis de la modifier.
Les pages d'exception aidant au dbuggage qui sont montres au dveloppeur peuvent aussi tre
personnalises de la mme manire en crant des templates comme exception.html.twig pour la
page d'exception HTML standard ou exception.json.twig pour la page d'exception JSON.
PDF brought to you by
generated on June 26, 2014
Chapter 20: Comment personnaliser les pages d'erreur | 72
Listing 21-1
Listing 21-2
Chapter 21
Comment dfinir des contrleurs en tant que
Services
Dans le Book, vous avez appris comment un contrleur peut facilement tre utilis lorsqu'il tend la classe
de base Controller
1
. Bien que ceci fonctionne trs bien, les contrleurs peuvent aussi tre dfinis en tant
que services.
Pour faire rfrence un contrleur qui est dfini en tant que service, utilisez la notation avec deux-points
(:). Par exemple, supposons que vous ayez dfini un service nomm my_controller et que vous voulez
le transmettre une mthode appele indexAction() l'intrieur du service:
1 $this->forward('my_controller:indexAction', array('foo' => $bar));
Vous devez utiliser la mme notation quand vous dfinissez la valeur de la route _controller :
1
2
3
my_controller:
pattern: /
defaults: { _controller: my_controller:indexAction }
Pour utiliser un contrleur de cette manire, il doit tre dfini dans la configuration du conteneur de
services. Pour plus d'informations, voir le chapitre Service Container.
Lorsque vous utilisez un contrleur dfini en tant que service, il ne va pas tendre la classe de base
Controller dans la plupart des cas. Au lieu de se reposer sur ses mthodes raccourcis , vous allez
intragir directement avec les services dont vous avez besoin. Heureusement, cela est gnralement trs
facile et la classe de base Controller elle-mme est une formidable source d'inspiration quant comment
effectuer de nombreuses tches usuelles.
1. http://api.symfony.com/2.3/Symfony/Bundle/FrameworkBundle/Controller/Controller.html
PDF brought to you by
generated on June 26, 2014
Chapter 21: Comment dfinir des contrleurs en tant que Services | 73
Listing 21-3
Spcifier un contrleur en tant que service demande un petit plus de travail. L'avantage premier est
que le contrleur en entier ou tout service pass au contrleur peut tre modifi via la configuration
du conteneur de services. Cela est spcialement utile lorsque vous dveloppez un bundle open-
source ou tout autre bundle qui va tre utilis dans beaucoup de projets diffrents. Donc, mme
si vous ne spcifiez pas vos contrleurs en tant que services, vous allez probablement voir ceci
effectu dans quelques bundles Symfony2 open-source.
Utiliser les annotations de routage
Lorsque vous utilisez les annotations pour dfinir le routage dans un contrleur dfini comme service,
vous devrez spcifier votre service comme suit:
1
2
3
4
5
6
7
/**
* @Route("/blog", service="my_bundle.annot_controller")
* @Cache(expires="tomorrow")
*/
class AnnotController extends Controller
{
}
Dans cet exemple, my_bundle.annot_controller devrait tre l'id de l'instance du AnnotController
dfini dans le conteneur de services. Cette partie est documente dans le chapitre @Route et @Method.
PDF brought to you by
generated on June 26, 2014
Chapter 21: Comment dfinir des contrleurs en tant que Services | 74
Listing 22-1
Chapter 22
Comment optimiser votre environnement pour
le debuggage
Quand vous travaillez sur un projet Symfony sur votre machine locale, vous devriez utiliser
l'environnement dev (correspondant au contrleur frontal app_dev.php). Cet environnement est
optimis pour :
Donner au dveloppeur des informations rapides et claires si quelque chose ne se droule
pas comme prvu ( l'aide de la web debug toolbar, d'exceptions documentes et prsentes
clairement, du profiler, ...) ;
tre aussi proche que possible l'environnement de production afin de prparer le
dploiement du projet.
Dsactiver le bootstrap et le cache des classes
Pour rendre l'environnement de production aussi rapide que possible, Symfony cre de longs fichiers PHP
dans le dossier cache, qui correspondent l'aggrgation des classes PHP dont votre projet a besoin
chaque requte. Cependant, ce comportement peut dsorienter votre IDE ou votre debugger. Nous allons
vous montrer ici comment modifier le mcanisme de cache afin qu'il permette un dbuggage des classes
intgres Symfony.
Le contrleur frontal app_dev.php se compose par dfaut du code suivant:
1
2
3
4
5
6
7
8
9
10
// ...
require_once __DIR__.'/../app/bootstrap.php.cache';
require_once __DIR__.'/../app/AppKernel.php';
use Symfony\Component\HttpFoundation\Request;
$kernel = new AppKernel('dev', true);
$kernel->loadClassCache();
$kernel->handle(Request::createFromGlobals())->send();
PDF brought to you by
generated on June 26, 2014
Chapter 22: Comment optimiser votre environnement pour le debuggage | 75
Listing 22-2
Listing 22-3
Pour faciliter le travail du debugger, dsactivez le cache des classes PHP en supprimant l'appel
loadClassCache() et en replaant les fichiers requis comme ceci:
1
2
3
4
5
6
7
8
9
10
11
// ...
// require_once __DIR__.'/../app/bootstrap.php.cache';
require_once __DIR__.'/../app/autoload.php';
require_once __DIR__.'/../app/AppKernel.php';
use Symfony\Component\HttpFoundation\Request;
$kernel = new AppKernel('dev', true);
// $kernel->loadClassCache();
$kernel->handle(Request::createFromGlobals())->send();
Si vous dsactivez le cache des classes, n'oubliez pas de revenir aux rglages initiaux aprs votre
session de dbuggage.
Certains IDEs n'apprcient pas que certaines classes soient enregistres diffrents emplacements. Pour
prvenir ces problmes, vous pouvez dsactiver la lecture du dossier cache dans votre IDE, ou changer
l'extension utilise par Symfony pour ces fichiers:
1 $kernel->loadClassCache('classes', '.php.cache');
PDF brought to you by
generated on June 26, 2014
Chapter 22: Comment optimiser votre environnement pour le debuggage | 76
Chapter 23
Dployer une application Symfony2
Le dploiement peut tre une tche complexe et variable en fonction de votre configuration et de
vos besoins. Cet article n'essaie pas de rpondre tout, mais plutt d'aborder les besoins les plus
rcurrents et d'apporter quelques ides lors du dploiement.
Bases du dploiement Symfony2
Les tapes typiques suivre lors du dploiement d'une application Symfony2 sont :
1. Uploader votre code jour sur le serveur de production;
2. Mettre jour vos dpendances Vendor (en gnral, c'est fait via Composer et cela peut tre fait
avant l'upload);
3. Lancer les migrations de base de donnes ou toute tche similaire qui apporterait des
changements de structure votre base;
4. Vider (et peut tre plus important encore, faire un "warm up") le cache.
Un dploiement peut aussi inclure d'autres tapes comme :
Tagger une version particulire de votre code dans votre systme de gestion de code;
Crer un espace temporaire pour mettre certaines choses jour hors ligne;
Lancer les tests pour garantir la stabilit du code et/ou du serveur;
Supprimer tout fichier inutile du rpertoire web pour conserver votre environnement de
production propre;
Vider les systmes de cache externes (comme Memcached
1
ou Redis
2
).
Comment dployer une application Symfony2
Il y a plusieurs manires de dployer une application Symfony2.
Commenons par les bases pour entrer dans les dtails ensuite.
1. http://memcached.org/
2. http://redis.io/
PDF brought to you by
generated on June 26, 2014
Chapter 23: Dployer une application Symfony2 | 77
Listing 23-1
Listing 23-2
Transfert de fichier de base
La manire la plus basique de dployer une application est de copier les fichiers manuellement via ftp/
scp (ou une mthode similaire). Cela a quelques inconvnients puisque vous ne contrlez pas tout,
notamment le processus de mise jour. Cette mthode implique galement de raliser d'autres tches
manuellement aprs le transfert de fichiers (voir Tches communes aprs le dploiement).
Utiliser un systme de gestion de version
Si vous utilisez un systme de gestion de version (ex git ou svn), vous pouvez vous simplifier la vie en
faisant en sorte que votre application en production soit une copie de votre dpt. Ainsi, lorsque vous
tes prt mettre jour votre code, il suffit juste de rcuprer les dernires modifications de votre dpt.
Cela rend les mises jour de vos fichiers plus facile, mais vous devrez tout de mme vous occuper
manuellement d'autres tapes (voir Tches communes aprs le dploiement).
Utiliser des scripts et d'autres outils
Il existe des outils de qualit pour faciliter le dploiement. Il y a mme certains outils qui ont spcialement
t taills pour les besoins de Symfony2, et d'autres qui s'assurent que tout se passe bien avant, pendant
et aprs le dploiement.
Lisez Les outils pour une liste des outils qui peuvent vous aider dployer.
Tches communes aprs le dploiement
Aprs avoir dploy votre code source, il y a un certain nombre de choses faire :
A) Configurer votre fichier app/config/parameters.ini
Ce fichier devrait tre personnalis sur chaque systme. La mthode que vous utilisez pour dployer
votre code source de doit pas dployer ce fichier. Vous devriez plutt le dfinir manuellement (ou via un
processus) sur votre serveur.
B) Mettre jour les vendors
Vos vendors peuvent tre mis jour avant de transfrer votre code source (mettez jour votre rpertoire
vendor/ puis transfrez le avec le reste de votre code source) ou aprs sur le serveur. Peu importe ce que
vous choisissez, mettez jour vos vendors comme vous le faites d'habitude :
1 $ php composer.phar install --optimize-autoloader
L'option --optimize-autoloader rend l'autoloader de Composer plus performant en construisant
une map .
C) Videz votre cache Symfony
Assurez vous de vider (et faire un warm up) de votre cache :
1 $ php app/console cache:clear --env=prod --no-debug
PDF brought to you by
generated on June 26, 2014
Chapter 23: Dployer une application Symfony2 | 78
Listing 23-3
D) Dumpez vos ressources Assetic
Si vous utilisez Assetic, vous devrez aussi dumpez vos ressources :
1 $ php app/console assetic:dump --env=prod --no-debug
E) Et bien d'autres !
Il y a encore bien d'autres choses que vous devrez peut tre faire selon votre configuration :
Lancer vos migrations de base de donnes
Vider votre cache APC
Lancer assets:install (dj dans composer.phar install)
Ajouter/diter des tches CRON
Mettre vos ressources sur un CDN
...
Cycle de vie de l'application : intgration continue, qualit, ...
Alors que cet article couvre les aspects techniques du dploiement, le cycle de vie complet du code depuis
le dveloppement jusqu'au serveur de production peut contenir bien d'autres tapes (dploiement en
prproduction, qualit, lancement des tests, ...).
L'utilisation de la prproduction, des tests, de l 'assurance qualit, de l'intgration continue, des
migrations de base de donnes et la capacit de retour arrire en cas d'chec sont fortement
recommands. Il existes des outils simples ou plus complexes qui vous permettent de simplifier le
dploiement.
N'oubliez pas que dployer votre application implique galement de mettre jour vos dpendances
(gnralement avec Composer), mettre jour votre base de donnes, vider votre cache et de raliser
potentiellement d'autres chose comme mettre vos ressources sur un CDN (voir Tches communes aprs
le dploiement).
Les outils
Capifony
3
:
Cet outil fournit un ensemble d'outils spcialiss bass sur Capistrano et taills spcifiquement
pour les projets symfony et Symfony2.
sf2debpkg
4
:
Cet outil aide construire un paquet natif Debian pour vos projets Symfony2.
Magallanes
5
:
Cet outil de dploiement semblable Capistrano est construit en PHP et est peut tre plus
facile tendre pour les dveloppeurs PHP qui ont des besoins spcifiques.
Bundles:
3. http://capifony.org/
4. https://github.com/liip/sf2debpkg
5. https://github.com/andres-montanez/Magallanes
PDF brought to you by
generated on June 26, 2014
Chapter 23: Dployer une application Symfony2 | 79
Il existe plusieurs bundles qui proposent des fonctionnalits lis au dploiement
6
directement
dans votre console Symfony2.
Scripts de base:
Vous pouvez bien sur utiliser le shell, Ant
7
, ou n'importe quel autre outil de script pour
dployer vos projets.
Platform as a Service Providers:
Paas est une manire relativement nouvelle de dployer votre application. Typiquement, une
Paas utilisera un unique fichier de configuration la racine de votre projet pour dterminer
comment construire un environnement la vole qui supporte votre logiciel. PagodaBox
8
possde un excellent support de Symfony2.
Vous voulez en savoir plus ? Discutez avec la communaut sur le canal IRC Symfony
9
#symfony
(sur freenode) pour plus d'informations.
6. http://knpbundles.com/search?q=deploy
7. http://blog.sznapka.pl/deploying-symfony2-applications-with-ant
8. https://github.com/jmather/pagoda-symfony-sonata-distribution/blob/master/Boxfile
9. http://webchat.freenode.net/?channels=symfony
PDF brought to you by
generated on June 26, 2014
Chapter 23: Dployer une application Symfony2 | 80
Listing 24-1
Chapter 24
Comment grer les uploads de fichier avec
Doctrine
Grer les uploads de fichier avec les entits Doctrine n'est en rien diffrent de grer n'importe quel autre
upload. En d'autres termes, vous tes libre de dplacer le fichier via votre contrleur aprs avoir gr la
soumission du formulaire. Pour voir des exemples, rfrez-vous la page de rfrence du type fichier.
Si vous le choisissez, vous pouvez aussi intgrer l'upload de fichier dans le cycle de vie de votre entit
(c-a-d cration, mise jour et suppression). Dans ce cas, lorsque votre entit est cre, mise jour
et supprime de Doctrine, les processus d'upload et de suppression du fichier se feront de manire
automatique (sans avoir besoin de faire quoi que ce soit dans votre contrleur).
Pour que cela fonctionne, vous allez avoir besoin de prendre en compte un certain nombre de dtails qui
vont tre couverts dans cet article du Cookbook.
Installation basique
Tout d'abord, crez une classe Entit Doctrine simple avec laquelle nous allons travailler:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Acme/DemoBundle/Entity/Document.php
namespace Acme\DemoBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity
*/
class Document
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
PDF brought to you by
generated on June 26, 2014
Chapter 24: Comment grer les uploads de fichier avec Doctrine | 81
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
*/
public $id;
/**
* @ORM\Column(type="string", length=255)
* @Assert\NotBlank
*/
public $name;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
public $path;
public function getAbsolutePath()
{
return null === $this->path ? null : $this->getUploadRootDir().'/'.$this->path;
}
public function getWebPath()
{
return null === $this->path ? null : $this->getUploadDir().'/'.$this->path;
}
protected function getUploadRootDir()
{
// le chemin absolu du rpertoire o les documents uploads doivent tre
sauvegards
return __DIR__.'/../../../../web/'.$this->getUploadDir();
}
protected function getUploadDir()
{
// on se dbarrasse de __DIR__ afin de ne pas avoir de problme lorsqu'on
affiche
// le document/image dans la vue.
return 'uploads/documents';
}
}
L'entit Document a un nom et est associe un fichier. La proprit path stocke le chemin relatif du
fichier et est persiste dans la base de donnes. La mthode getAbsolutePath() est un moyen pratique
de retourner le chemin absolu du fichier alors que la mthode getWebPath() permet de retourner le
chemin web, qui lui peut tre utilis dans un template pour ajouter un lien vers le fichier upload.
Si vous ne l'avez pas dj fait, vous devriez probablement lire la documentation du type fichier en
premier afin de comprendre comment le processus de base de l'upload fonctionne.
Si vous utilisez les annotations pour spcifier vos rgles de validation (comme montr dans cet
exemple), assurez-vous d'avoir activ la validation via les annotations (voir configuration de la
validation).
Pour grer l'upload de fichier dans le formulaire, utilisez un champ virtuel file. Par exemple, si vous
construisez votre formulaire directement dans un contrleur, cela ressemblerait quelque chose comme
a:
PDF brought to you by
generated on June 26, 2014
Chapter 24: Comment grer les uploads de fichier avec Doctrine | 82
Listing 24-2
Listing 24-3
Listing 24-4
1
2
3
4
5
6
7
8
9
10
11
public function uploadAction()
{
// ...
$form = $this->createFormBuilder($document)
->add('name')
->add('file')
->getForm();
// ...
}
Ensuite, crez cette proprit dans votre classe Document et ajoutez quelques rgles de validation:
1
2
3
4
5
6
7
8
9
10
11
12
// src/Acme/DemoBundle/Entity/Document.php
// ...
class Document
{
/**
* @Assert\File(maxSize="6000000")
*/
public $file;
// ...
}
Comme vous utilisez la contrainte File, Symfony2 va automatiquement deviner que le champ
du formulaire est un champ d'upload de fichier. C'est pourquoi vous n'avez pas eu le spcifier
explicitement lors de la cration du formulaire ci-dessus (->add('file')).
Le contrleur suivant vous montre comment grer le processus en entier:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use Acme\DemoBundle\Entity\Document;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
// ...
/**
* @Template()
*/
public function uploadAction()
{
$document = new Document();
$form = $this->createFormBuilder($document)
->add('name')
->add('file')
->getForm()
;
if ($this->getRequest()->isMethod('POST')) {
$form->bind($this->getRequest());
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($document);
PDF brought to you by
generated on June 26, 2014
Chapter 24: Comment grer les uploads de fichier avec Doctrine | 83
Listing 24-5
Listing 24-6
Listing 24-7
23
24
25
26
27
28
29
30
$em->flush();
$this->redirect($this->generateUrl(...));
}
}
return array('form' => $form->createView());
}
Lorsque vous crivez le template, n'oubliez pas de spcifier l'attribut enctype :
<h1>Upload File</h1>
<form action="#" method="post" {{ form_enctype(form) }}>
{{ form_widget(form) }}
<input type="submit" value="Upload Document" />
</form>
Le contrleur prcdent va automatiquement persister l'entit Document avec le nom soumis, mais elle ne
va rien faire propos du fichier et la proprit path sera vide.
Une manire facile de grer l'upload de fichier est de le dplacer juste avant que l'entit soit persiste
et ainsi spcifier la proprit path en consquence. Commencez par appeler une nouvelle mthode
upload() sur la classe Document que vous allez crer dans un moment pour grer l'upload de fichier:
1
2
3
4
5
6
7
8
9
10
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$document->upload();
$em->persist($document);
$em->flush();
$this->redirect(...);
}
La mthode upload() va tirer parti de l'objet UploadedFile
1
, qui correspond ce qui est retourn aprs
qu'un champ file ait t soumis:
1
2
3
4
5
6
7
8
9
10
11
12
13
public function upload()
{
// la proprit file peut tre vide si le champ n'est pas requis
if (null === $this->file) {
return;
}
// utilisez le nom de fichier original ici mais
// vous devriez l'assainir pour au moins viter
// quelconques problmes de scurit
// la mthode move prend comme arguments le rpertoire cible et
// le nom de fichier cible o le fichier doit tre dplac
1. http://api.symfony.com/2.3/Symfony/Component/HttpFoundation/File/UploadedFile.html
PDF brought to you by
generated on June 26, 2014
Chapter 24: Comment grer les uploads de fichier avec Doctrine | 84
Listing 24-8
Listing 24-9
14
15
16
17
18
19
20
21
22
$this->file->move($this->getUploadRootDir(), $this->file->getClientOriginalName());
// dfinit la proprit path comme tant le nom de fichier o vous
// avez stock le fichier
$this->path = $this->file->getClientOriginalName();
// nettoie la proprit file comme vous n'en aurez plus besoin
$this->file = null;
}
Utiliser les callbacks du cycle de vie (lifecycle)
Mme si cette implmentation fonctionne, elle souffre d'un dfaut majeur : que se passe-t-il s'il y a un
problme lorsque l'entit est persiste ? Le fichier serait dj dplac vers son emplacement final mme si
la proprit path de l'entit n'a pas t persiste correctement.
Pour viter ces problmes, vous devriez changer l'implmentation afin que les oprations sur la base de
donnes et le dplacement du fichier deviennent atomiques : s'il y a un problme en persistant l'entit ou
si le fichier ne peut pas tre dplac, alors rien ne devrait se passer.
Pour faire cela, vous devez dplacer le fichier aussitt que Doctrine persiste l'entit dans la base de
donns. Ceci peut tre accompli en s'interfrant dans le cycle de vie de l'entit via un callback:
1
2
3
4
5
6
7
/**
* @ORM\Entity
* @ORM\HasLifecycleCallbacks
*/
class Document
{
}
Ensuite, rfactorisez la classe Document pour tirer parti de ces callbacks:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* @ORM\Entity
* @ORM\HasLifecycleCallbacks
*/
class Document
{
/**
* @ORM\PrePersist()
* @ORM\PreUpdate()
*/
public function preUpload()
{
if (null !== $this->file) {
// faites ce que vous voulez pour gnrer un nom unique
$this->path = sha1(uniqid(mt_rand(), true)).'.'.$this->file->guessExtension();
}
}
/**
* @ORM\PostPersist()
PDF brought to you by
generated on June 26, 2014
Chapter 24: Comment grer les uploads de fichier avec Doctrine | 85
Listing 24-10
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
* @ORM\PostUpdate()
*/
public function upload()
{
if (null === $this->file) {
return;
}
// s'il y a une erreur lors du dplacement du fichier, une exception
// va automatiquement tre lance par la mthode move(). Cela va empcher
// proprement l'entit d'tre persiste dans la base de donnes si
// erreur il y a
$this->file->move($this->getUploadRootDir(), $this->path);
unset($this->file);
}
/**
* @ORM\PostRemove()
*/
public function removeUpload()
{
if ($file = $this->getAbsolutePath()) {
unlink($file);
}
}
}
La classe effectue maintenant tout ce dont vous avez besoin : elle gnre un nom de fichier unique avant
de le persister, dplace le fichier aprs avoir persist l'entit, et efface le fichier si l'entit est supprime.
Maintenant que le dplacement du fichier est automatiquement pris en charge par l'entit, l'appel de la
mthode $document->upload() devrait tre supprim du contrleur:
1
2
3
4
5
6
7
8
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($document);
$em->flush();
$this->redirect(...);
}
Les vnements de callback @ORM\PrePersist() et @ORM\PostPersist() sont dclenchs avant et
aprs que l'entit soit persiste dans la base de donnes. D'autre part, les vnements de callback
@ORM\PreUpdate() et @ORM\PostUpdate() sont appels lorsque l'entit est mise jour.
Les callbacks PreUpdate et PostUpdate sont dclenchs seulement s'il y a un changement dans l'un
des champs de l'entit tant persiste. Cela signifie que, par dfaut, si vous modifiez uniquement la
proprit $file, ces vnements ne seront pas dclenchs, comme la proprit elle-mme n'est pas
directement persiste via Doctrine. Une solution pourrait tre d'utiliser un champ updated qui soit
persist dans Doctrine, et de le modifier manuellement lorsque le fichier est chang.
PDF brought to you by
generated on June 26, 2014
Chapter 24: Comment grer les uploads de fichier avec Doctrine | 86
Listing 24-11
Utiliser l'id en tant que nom de fichier
Si vous voulez utiliser l'id comme nom de fichier, l'implmentation est lgrement diffrente car vous
devez sauvegarder l'extension dans la proprit path, la place du nom de fichier actuel:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* @ORM\Entity
* @ORM\HasLifecycleCallbacks
*/
class Document
{
// proprit utilis temporairement pour la suppression
private $filenameForRemove;
/**
* @ORM\PrePersist()
* @ORM\PreUpdate()
*/
public function preUpload()
{
if (null !== $this->file) {
$this->path = $this->file->guessExtension();
}
}
/**
* @ORM\PostPersist()
* @ORM\PostUpdate()
*/
public function upload()
{
if (null === $this->file) {
return;
}
// vous devez lancer une exception ici si le fichier ne peut pas
// tre dplac afin que l'entit ne soit pas persiste dans la
// base de donnes comme le fait la mthode move() de UploadedFile
$this->file->move($this->getUploadRootDir(),
$this->id.'.'.$this->file->guessExtension());
unset($this->file);
}
/**
* @ORM\PreRemove()
*/
public function storeFilenameForRemove()
{
$this->filenameForRemove = $this->getAbsolutePath();
}
/**
* @ORM\PostRemove()
*/
public function removeUpload()
{
PDF brought to you by
generated on June 26, 2014
Chapter 24: Comment grer les uploads de fichier avec Doctrine | 87
55
56
57
58
59
60
61
62
63
if ($this->filenameForRemove) {
unlink($this->filenameForRemove);
}
}
public function getAbsolutePath()
{
return null === $this->path ? null :
$this->getUploadRootDir().'/'.$this->id.'.'.$this->path;
}
}
Vous noterez que dans ce cas, vous devez effectuer un peu plus de travail pour supprimer le fichier. Avant
qu'il soit supprim, vous devez stocker le chemin du fichier (puisqu'il dpend de l'id). Ensuite, une fois
que l'objet est bien compltement supprim de la base de donnes, vous pouvez supprimer le fichier en
toute scurit (dans PostRemove).
PDF brought to you by
generated on June 26, 2014
Chapter 24: Comment grer les uploads de fichier avec Doctrine | 88
Chapter 25
Comment utiliser les extensions Doctrine:
Timestampable, Sluggable, Translatable, etc.
Doctrine2 est trs flexible, et la communaut a dj cr une srie d'extensions Doctrine trs pratiques
afin de vous aider avec les tches usuelles lies aux entits.
Une bibliothque en particulier - la bibliothque DoctrineExtensions
1
- fournit l'intgration de
fonctionnalits pour les comportements (Behaviors) Sluggable
2
, Translatable
3
, Timestampable
4
,
Loggable
5
, Tree
6
et Sortable
7
L'utilisation de chacune de ces extensions est explique dans son dpt.
Toutefois, pour installer/activer chaque extension, vous devez enregistrer et activer un Ecouteur
d'vnement (Event Listener). Pour faire cela, vous avez deux possibilits :
1. Utiliser le bundle StofDoctrineExtensionsBundle
8
, qui intgre la bibliothque ci-dessus.
2. Implmenter ces services directement en suivant la documentation pour l'intgration dans
Symfony2 : Installer les extensions Gedmo Doctrine2 dans Symfony2
9
1. https://github.com/l3pp4rd/DoctrineExtensions
2. https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/sluggable.md
3. https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/translatable.md
4. https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/timestampable.md
5. https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/loggable.md
6. https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/tree.md
7. https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/sortable.md
8. https://github.com/stof/StofDoctrineExtensionsBundle
9. https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/symfony2.md
PDF brought to you by
generated on June 26, 2014
Chapter 25: Comment utiliser les extensions Doctrine: Timestampable, Sluggable, Translatable, etc. |
89
Listing 26-1
Chapter 26
Comment enregistrer des listeners (
couteurs en franais) et des souscripteurs
d'vnement
Doctrine est fourni avec un riche systme d'vnement qui lance des vnements chaque fois - ou
presque - que quelque chose se passe dans le systme. Pour vous, cela signifie que vous pouvez crer
arbitrairement des services et dire Doctrine de notifier ces objets chaque fois qu'une certaine action
(par exemple : prePersist) a lieu dans Doctrine. Cela pourrait tre utile par exemple de crer un index
de recherche indpendant chaque fois qu'un objet est sauvegard dans votre base de donnes.
Doctrine dfinit deux types d'objets qui peuvent couter des vnements Doctrine : les listeners et les
souscripteurs d'vnement. Les deux sont trs similaires, mais les listeners sont un peu plus simples. Pour
plus d'informations, voir Le systme d'vnements
1
sur le site de Doctrine.
Configurer le listener/souscripteur d'vnement
Pour spcifier un service d'agir comme un listener d'vnements ou comme un souscripteur, vous devez
simplement le tagger avec un nom appropri. Selon votre cas, vous pouvez placer un listener dans chaque
connexion DBAL et gestionnaire d'entit ORM ou juste dans une connexion DBAL spcifique et tous les
gestionnaires d'entit qui utilisent cette connexion.
1
2
3
4
5
6
7
8
doctrine:
dbal:
default_connection: default
connections:
default:
driver: pdo_sqlite
memory: true
1. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html
PDF brought to you by
generated on June 26, 2014
Chapter 26: Comment enregistrer des listeners ( couteurs en franais) et des souscripteurs
d'vnement | 90
Listing 26-2
9
10
11
12
13
14
15
16
17
18
19
20
21
services:
my.listener:
class: Acme\SearchBundle\Listener\SearchIndexer
tags:
- { name: doctrine.event_listener, event: postPersist }
my.listener2:
class: Acme\SearchBundle\Listener\SearchIndexer2
tags:
- { name: doctrine.event_listener, event: postPersist, connection: default }
my.subscriber:
class: Acme\SearchBundle\Listener\SearchIndexerSubscriber
tags:
- { name: doctrine.event_subscriber, connection: default }
Crer la Classe du Listener
Dans l'exemple prcdent, un service my.listener a t configur en tant que listener Doctrine sur
l'vnement postPersist. Cette classe derrire ce service doit avoir une mthode postPersist, qui va
tre appele lorsque l'vnement est lanc:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/Acme/SearchBundle/Listener/SearchIndexer.php
namespace Acme\SearchBundle\Listener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Acme\StoreBundle\Entity\Product;
class SearchIndexer
{
public function postPersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
// peut-tre voulez-vous seulement agir sur une entit Product
if ($entity instanceof Product) {
// faites quelque chose avec l'entit Product
}
}
}
Dans chaque vnement, vous avez accs un objet LifecycleEventArgs, qui vous donne accs l'objet
entit de l'vnement ainsi qu'au gestionnaire d'entits lui-mme.
Une chose importante noter est qu'un listener va couter toutes les entits de votre application. Donc, si
vous ne voulez grer qu'un type spcifique d'entit (par exemple : une entit Product mais pas une entit
BlogPost), vous devez vrifier le nom de la classe de votre entit dans votre mthode (comme montr
ci-dessus).
PDF brought to you by
generated on June 26, 2014
Chapter 26: Comment enregistrer des listeners ( couteurs en franais) et des souscripteurs
d'vnement | 91
Listing 27-1
Chapter 27
Comment utiliser la couche DBAL de Doctrine
Cet article traite de la couche DBAL de Doctrine. Typiquement, vous allez travailler avec le haut
niveau de la couche ORM de Doctrine, qui utilise le DBAL en arrire-plan pour effectivement
communiquer avec la base de donnes. Pour en lire plus propos de l'ORM Doctrine, voir
Doctrine et les bases de donnes .
La couche d'abstraction de la base de donnes (DBAL pour DataBase Abstraction Layer) de Doctrine
1
se situe au plus haut niveau de PDO
2
et offre une API intuitive et flexible pour communiquer avec les
bases de donnes relationnelles les plus populaires. En d'autres termes, la bibliothque DBAL rend facile
l'excution de requtes et autres actions sur la base de donnes.
Lisez la Documentation DBAL
3
officielle de Doctrine pour apprendre tous les dtails et capacits
de la bibliothque DBAL de Doctrine.
Pour dmarrer, configurez les paramtres de connexion de la base de donnes :
1
2
3
4
5
6
7
8
# app/config/config.yml
doctrine:
dbal:
driver: pdo_mysql
dbname: Symfony2
user: root
password: null
charset: UTF8
Pour une liste complte des options de configuration de DBAL, voir Configuration du DBAL Doctrine.
Vous pouvez alors accder la connexion DBAL de Doctrine en utilisant le service
database_connection :
1. http://www.doctrine-project.org
2. http://www.php.net/pdo
3. http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/index.html
PDF brought to you by
generated on June 26, 2014
Chapter 27: Comment utiliser la couche DBAL de Doctrine | 92
Listing 27-2
Listing 27-3
Listing 27-4
1
2
3
4
5
6
7
8
9
10
class UserController extends Controller
{
public function indexAction()
{
$conn = $this->get('database_connection');
$users = $conn->fetchAll('SELECT * FROM users');
// ...
}
}
Dclarer des Types de Correspondance Personnaliss
Vous pouvez dclarer des types de correspondance personnaliss via la configuration de Symfony.
Ils seront ajouts toutes les connexions configures. Pour plus d'informations sur les types de
correspondances personnaliss, lisez la section types de correspondances personnaliss
4
de la
documentation de Doctrine.
1
2
3
4
5
6
# app/config/config.yml
doctrine:
dbal:
types:
custom_first: Acme\HelloBundle\Type\CustomFirst
custom_second: Acme\HelloBundle\Type\CustomSecond
Dclarer des Types de Correspondance Personnaliss via le SchemaTool
Le SchemaTool est utilis pour inspecter la base de donnes afin d'en comparer le schma. Pour effectuer
cette tche, il a besoin de connatre quel type de correspondance utiliser pour chaque type de base de
donnes. En dclarer de nouveaux peut tre effectu grce la configuration.
Faisons correspondre le type ENUM (non-support par DBAL par dfaut) un type string :
1
2
3
4
5
6
7
8
# app/config/config.yml
doctrine:
dbal:
connections:
default:
// Autres paramtres de connexion
mapping_types:
enum: string
4. http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/types.html#custom-mapping-types
PDF brought to you by
generated on June 26, 2014
Chapter 27: Comment utiliser la couche DBAL de Doctrine | 93
Listing 28-1
Chapter 28
Comment gnrer des Entits partir d'une
base de donnes existante
Lorsque vous commencez travailler sur un tout nouveau projet qui utilise une base de donnes, deux
situations diffrentes peuvent arriver. Dans la plupart des cas, le modle de base de donnes est conu et
construit de zro. Dans d'autres cas, cependant, vous commencerez avec un modle de base de donnes
existant et probablement inchangeable. Heureusement, Doctrine est fourni avec une srie d'outils vous
aidant gnrer les classes de modle partir de votre base de donnes existante.
Comme la documentation des outils Doctrine
1
le dit, faire du reverse engineering est un
processus qu'on n'effectue qu'une seule fois lorsqu'on dmarre un projet. Doctrine est capable
de convertir environ 70-80% des informations de correspondance ncessaires en se basant sur
les champs, les index et les contraintes de cls trangres. En revanche, Doctrine ne peut pas
identifier les associations inverses, les types d'inhritance, les entits avec cls trangres en tant
que cls primaires ou les oprations smantiques sur des associations telles que la cascade ou
les vnements de cycle de vie. Ainsi, du travail additionnel sur les entits gnres sera ncessaire
par la suite pour finir la conception de chacune afin de satisfaire les spcificits du modle de votre
domaine.
Ce tutoriel suppose que vous utilisez une application de blog simple avec les deux tables suivantes :
blog_post et blog_comment. Une entre comment est lie une entre post grce une contrainte
de cl trangre.
1
2
3
4
5
6
7
8
CREATE TABLE `blog_post` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`title` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`content` longtext COLLATE utf8_unicode_ci NOT NULL,
`created_at` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
1. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/tools.html#reverse-engineering
PDF brought to you by
generated on June 26, 2014
Chapter 28: Comment gnrer des Entits partir d'une base de donnes existante | 94
Listing 28-2
Listing 28-3
9
10
11
12
13
14
15
16
17
18
CREATE TABLE `blog_comment` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`post_id` bigint(20) NOT NULL,
`author` varchar(20) COLLATE utf8_unicode_ci NOT NULL,
`content` longtext COLLATE utf8_unicode_ci NOT NULL,
`created_at` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `blog_comment_post_id_idx` (`post_id`),
CONSTRAINT `blog_post_id` FOREIGN KEY (`post_id`) REFERENCES `blog_post` (`id`) ON
DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Avant d'aller plus loin, assurez-vous que vos paramtres de connexion la base de donnes sont
correctement dfinis dans le fichier app/config/parameters.yml (ou quelconque endroit que ce soit
o votre configuration de base de donnes est conserve) et que vous avez initialis un bundle qui va
contenir votre future classe entit. Dans ce tutorial, nous supposerons qu'un AcmeBlogBundle existe et
qu'il se situe dans le dossier src/Acme/BlogBundle.
La premire tape permettant de construire les classes entit depuis une base de donnes existante
est de demander Doctrine d'introspecter cette dernire et de gnrer les fichiers de mta-donnes
correspondants. Les fichiers de mta-donnes dcrivent la classe entit gnrer en se basant sur les
champs des tables.
1 $ php app/console doctrine:mapping:convert xml ./src/Acme/BlogBundle/Resources/config/
doctrine/metadata/orm --from-database --force
Cette outil de ligne de commande demande Doctrine d'introspecter la base de donnes et de gnrer les
fichiers XML de mta-donnes dans le dossier src/Acme/BlogBundle/Resources/config/doctrine/
metadata/orm de votre bundle.
Il est aussi possible de gnrer les mta-donnes au format YAML en changeant le premier
argument pour yml.
Le fichier de mta-donnes BlogPost.dcm.xml gnr ressemble ce qui suit :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping>
<entity name="BlogPost" table="blog_post">
<change-tracking-policy>DEFERRED_IMPLICIT</change-tracking-policy>
<id name="id" type="bigint" column="id">
<generator strategy="IDENTITY"/>
</id>
<field name="title" type="string" column="title" length="100"/>
<field name="content" type="text" column="content"/>
<field name="isPublished" type="boolean" column="is_published"/>
<field name="createdAt" type="datetime" column="created_at"/>
<field name="updatedAt" type="datetime" column="updated_at"/>
<field name="slug" type="string" column="slug" length="255"/>
<lifecycle-callbacks/>
</entity>
</doctrine-mapping>
PDF brought to you by
generated on June 26, 2014
Chapter 28: Comment gnrer des Entits partir d'une base de donnes existante | 95
Listing 28-4
Listing 28-5
Si vous avez des relations oneToMany entre vos entits, vous devrez diter les fichiers xml ou yml
gnrs pour ajouter une section sur les entits spcifiques afin de dfinir les attributs inversedBy
et mappedBy de la relation oneToMany.
Une fois que les fichiers de mta-donnes sont gnrs, vous pouvez demander Doctrine d'importer le
schma et de construire les classes entit qui lui sont lies en excutant les deux commandes suivantes.
1
2
$ php app/console doctrine:mapping:import AcmeBlogBundle annotation
$ php app/console doctrine:generate:entities AcmeBlogBundle
La premire commande gnre les classes entit avec des annotations de correspondance, mais vous
pouvez bien sr changer l'argument annotation pour tre xml ou yml. La classe entit nouvellement
cre ressemble ce qui suit :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
// src/Acme/BlogBundle/Entity/BlogComment.php
namespace Acme\BlogBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Acme\BlogBundle\Entity\BlogComment
*
* @ORM\Table(name="blog_comment")
* @ORM\Entity
*/
class BlogComment
{
/**
* @var bigint $id
*
* @ORM\Column(name="id", type="bigint", nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* @var string $author
*
* @ORM\Column(name="author", type="string", length=100, nullable=false)
*/
private $author;
/**
* @var text $content
*
* @ORM\Column(name="content", type="text", nullable=false)
*/
private $content;
/**
* @var datetime $createdAt
*
* @ORM\Column(name="created_at", type="datetime", nullable=false)
*/
PDF brought to you by
generated on June 26, 2014
Chapter 28: Comment gnrer des Entits partir d'une base de donnes existante | 96
44
45
46
47
48
49
50
51
52
53
private $createdAt;
/**
* @var BlogPost
*
* @ORM\ManyToOne(targetEntity="BlogPost")
* @ORM\JoinColumn(name="post_id", referencedColumnName="id")
*/
private $post;
}
Comme vous pouvez le voir, Doctrine convertit tous les champs de la table en proprits prives et
annotes de la classe. La chose la plus impressionnante est qu'il identifie aussi la relation avec la classe
entit BlogPost bas sur la contrainte de cl trangre. De ce fait, vous pouvez trouver une proprit
prive $post correspondant une entit BlogPost dans la classe entit BlogComment.
La dernire commande a gnr tous les getters et setters pour les proprits de vos deux classes
entit BlogPost et BlogComment. Les entits gnres sont maintenant prtes tre utilises. Amusez-
vous!
PDF brought to you by
generated on June 26, 2014
Chapter 28: Comment gnrer des Entits partir d'une base de donnes existante | 97
Listing 29-1
Chapter 29
Comment travailler avec plusieurs
gestionnaires d'entits et connexions
Vous pouvez utiliser plusieurs gestionnaires d'entits ou plusieurs connexions dans une application
Symfony2. Cela est ncessaire si vous utilisez diffrentes bases de donnes ou mme des vendors
avec des ensembles d'entits entirement diffrents. En d'autres termes, un gestionnaire d'entits qui se
connecte une base de donnes va grer quelques entits alors qu'un autre gestionnaire d'entits qui se
connecte une autre base de donnes pourrait grer le reste.
Utiliser plusieurs gestionnaires d'entits est assez facile, mais plus avanc et gnralement non
requis. Soyez sr que vous ncessitiez plusieurs gestionnaires d'entits avant d'ajouter cette couche
de complexit.
Le code de configuration suivant montre comment vous pouvez configurer deux gestionnaires d'entits :
doctrine:
dbal:
default_connection: default
connections:
default:
driver: %database_driver%
host: %database_host%
port: %database_port%
dbname: %database_name%
user: %database_user%
password: %database_password%
charset: UTF8
customer:
driver: %database_driver2%
host: %database_host2%
port: %database_port2%
dbname: %database_name2%
user: %database_user2%
password: %database_password2%
charset: UTF8
PDF brought to you by
generated on June 26, 2014
Chapter 29: Comment travailler avec plusieurs gestionnaires d'entits et connexions | 98
Listing 29-2
Listing 29-3
Listing 29-4
orm:
default_entity_manager: default
entity_managers:
default:
connection: default
mappings:
AcmeDemoBundle: ~
AcmeStoreBundle: ~
customer:
connection: customer
mappings:
AcmeCustomerBundle: ~
Dans ce cas, vous avez dfini deux gestionnaires d'entits et les avez appel default et customer.
Le gestionnaire d'entits default gre les entits des bundles AcmeDemoBundle et AcmeStoreBundle,
alors que le gestionnaire d'entits customer gre les entits du bundle AcmeCustomerBundle.Vous avez
galement dfini deux connexions, une pour chaque gestionnaire d'entit.
Lorsque vous travaillez avec plusieurs connexions ou plusieurs gestionnaires d'entits, vous devriez
tre explicite quant la configuration que vous voulez. Si vous omettez le nom de la connexion ou
du gestionnaire d'entit quand vous mettez jour votre schema, le gestionnaire par dfaut (c-a-d
default) sera utilis.
Crer votre base de donnes quand vous utilisez plusieurs connexion :
1
2
3
4
5
# N'utilise que la connexion default
$ php app/console doctrine:database:create
# N'utilise que la connexion customer
$ php app/console doctrine:database:create --connection=customer
Mettre jour votre schma quand vous utilisez plusieurs gestionnaires d'entit :
1
2
3
4
5
# Utilise le gestionnaire default
$ php app/console doctrine:schema:update --force
# Utilise le gestionnaire customer
$ php app/console doctrine:schema:update --force --em=customer
Si vous omettez le nom du gestionnaire d'entit quand vous le demandez, le gestionnaire d'entits par
dfaut (c'est--dire default) est retourn:
1
2
3
4
5
6
7
8
9
10
11
class UserController extends Controller
{
public function indexAction()
{
// les deux retournent le gestionnaire d'entits default
$em = $this->get('doctrine')->getManager();
$em = $this->get('doctrine')->getManager('default');
$customerEm = $this->get('doctrine')->getManager('customer');
}
}
PDF brought to you by
generated on June 26, 2014
Chapter 29: Comment travailler avec plusieurs gestionnaires d'entits et connexions | 99
Listing 29-5
Vous pouvez maintenant utiliser Doctrine comme vous le faisiez avant - en utilisant le gestionnaire
d'entits default pour persister et aller chercher les entits qu'il gre et le gestionnaire d'entits customer
pour persister et aller chercher ses entits.
La mme chose s'applique aux appels de repository:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class UserController extends Controller
{
public function indexAction()
{
// Retourne un repository gr par le gestionnaire default
$products = $this->get('doctrine')
->getRepository('AcmeStoreBundle:Product')
->findAll();
// Manire explicite de traiter avec le gestionnaire default
$products = $this->get('doctrine')
->getRepository('AcmeStoreBundle:Product', 'default')
->findAll();
// Retourne un repository gr par le gestionnaire customer
$customers = $this->get('doctrine')
->getRepository('AcmeCustomerBundle:Customer', 'customer')
->findAll();
}
}
PDF brought to you by
generated on June 26, 2014
Chapter 29: Comment travailler avec plusieurs gestionnaires d'entits et connexions | 100
Listing 30-1
Chapter 30
Comment dfinir des fonctions DQL
personnalises
Doctrine vous permet de spcifier des fonctions DQL personnalises. Pour plus d'informations ce sujet,
lisez l'article du cookbook de Doctrine Fonctions DQL dfinies par l'utilisateur
1
.
Dans Symfony, vous pouvez dfinir vos fonctions DQL personnalises comme suit :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# app/config/config.yml
doctrine:
orm:
# ...
entity_managers:
default:
# ...
dql:
string_functions:
test_string: Acme\HelloBundle\DQL\StringFunction
second_string: Acme\HelloBundle\DQL\SecondStringFunction
numeric_functions:
test_numeric: Acme\HelloBundle\DQL\NumericFunction
datetime_functions:
test_datetime: Acme\HelloBundle\DQL\DatetimeFunction
1. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/cookbook/dql-user-defined-functions.html
PDF brought to you by
generated on June 26, 2014
Chapter 30: Comment dfinir des fonctions DQL personnalises | 101
Chapter 31
Comment dfinir des Relations avec des
Classes Abstraites et des Interfaces
New in version 2.1: Le ResolveTargetEntityListener est une nouveaut de Doctrine 2.2, qui a t
packag pour la premire fois avec Symfony 2.1.
L'un des buts des bundles est de crer des ensembles distincts de fonctionnalits qui n'ont pas beaucoup
(ou pas du tout) de dpendances, vous permettant d'utiliser cette fonctionnalit dans d'autres
applications sans inclure d'lments superflus.
Doctrine 2.2 inclut un nouvel utilitaire appel le ResolveTargetEntityListener, qui fonctionne en
interceptant certains appels dans Doctrine et en r-crivant des paramtres targetEntity dans vos mta-
donnes de correspondance durant l'excution. Cela signifie que depuis votre bundle, vous tes capable
d'utiliser une interface ou une classe abstraite dans vos correspondances et que vous pouvez vous attendre
une correspondance correcte avec une entit concrte au moment de l'excution.
Cette fonctionnalit vous permet de dfinir des relations entre diffrentes entits sans en faire des
dpendances crites en dur .
Contexte/dcor
Supposons que vous ayez un InvoiceBundle qui fournit une fonctionnalit de facturation ( invoicing en
anglais) et un CustomerBundle qui contient les outils de gestion de client. Vous souhaitez garder ces deux
entits spares, car elles peuvent tre utilises dans d'autres systmes l'une sans l'autre ; mais pour votre
application, vous voulez les utiliser ensemble.
Dans ce cas, vous avez une entit Invoice ayant une relation avec un objet qui n'existe pas, une
InvoiceSubjectInterface. Le but est de rcuprer le ResolveTargetEntityListener pour remplacer
toute mention de l'interface par un objet rel qui implmente cette interface.
PDF brought to you by
generated on June 26, 2014
Chapter 31: Comment dfinir des Relations avec des Classes Abstraites et des Interfaces | 102
Listing 31-1
Listing 31-2
Listing 31-3
Mise en place
Utilisons les entits basiques suivantes (qui sont incompltes pour plus de brivet) pour expliquer
comment mettre en place et utiliser le RTEL.
Une entit Customer :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/Acme/AppBundle/Entity/Customer.php
namespace Acme\AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Acme\CustomerBundle\Entity\Customer as BaseCustomer;
use Acme\InvoiceBundle\Model\InvoiceSubjectInterface;
/**
* @ORM\Entity
* @ORM\Table(name="customer")
*/
class Customer extends BaseCustomer implements InvoiceSubjectInterface
{
// Dans notre exemple, toutes les mthodes dfinies dans
// l' InvoiceSubjectInterface sont dj implmentes dans le BaseCustomer
}
Une entit Invoice :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/Acme/InvoiceBundle/Entity/Invoice.php
namespace Acme\InvoiceBundle\Entity;
use Doctrine\ORM\Mapping AS ORM;
use Acme\InvoiceBundle\Model\InvoiceSubjectInterface;
/**
* Reprsente une Invoice .
*
* @ORM\Entity
* @ORM\Table(name="invoice")
*/
class Invoice
{
/**
* @ORM\ManyToOne(targetEntity="Acme\InvoiceBundle\Model\InvoiceSubjectInterface")
* @var InvoiceSubjectInterface
*/
protected $subject;
}
Une InvoiceSubjectInterface :
1
2
3
4
5
6
7
// src/Acme/InvoiceBundle/Model/InvoiceSubjectInterface.php
namespace Acme\InvoiceBundle\Model;
/**
* Une interface que le sujet de la facture devrait implmenter.
* Dans la plupart des circonstances, il ne devrait y avoir qu'un unique objet
PDF brought to you by
generated on June 26, 2014
Chapter 31: Comment dfinir des Relations avec des Classes Abstraites et des Interfaces | 103
Listing 31-4
8
9
10
11
12
13
14
15
16
17
18
19
20
21
* qui implmente cette interface puisque le ResolveTargetEntityListener peut
* changer seulement la cible d'un objet unique.
*/
interface InvoiceSubjectInterface
{
// Liste toutes les mthodes additionnelles dont votre
// InvoiceBundle aura besoin pour accder au sujet afin
// que vous soyez sr que vous avez accs ces mthodes.
/**
* @return string
*/
public function getName();
}
Ensuite, vous devez configurer le listener , qui informe le DoctrineBundle de votre remplacement :
1
2
3
4
5
6
7
# app/config/config.yml
doctrine:
# ....
orm:
# ....
resolve_target_entities:
Acme\InvoiceBundle\Model\InvoiceSubjectInterface: Acme\AppBundle\Entity\Customer
Rflexions finales
Avec le ResolveTargetEntityListener, vous tes capable de dcoupler vos bundles, en les gardant
utilisables par eux-mmes, mais en tant toujours capable de dfinir des relations entre diffrents objets.
En utilisant cette mthode, vos bundles vont finir par tre plus faciles maintenir indpendamment.
PDF brought to you by
generated on June 26, 2014
Chapter 31: Comment dfinir des Relations avec des Classes Abstraites et des Interfaces | 104
Listing 32-1
Chapter 32
Comment implmenter un simple formulaire
de cration de compte
Certains formulaires ont des champs en plus, dont les valeurs n'ont pas besoin d'tre stockes en base de
donnes. Par exemple, vous pourriez vouloir crer un formulaire de cration de compte avec des champs
en plus (comme par exemple une checkbox Accepter les conditions ) et imbriquer le formulaire qui
contient les informations relatives au compte.
Un modle simple : User
Vous avez une entit User simple associe la base de donnes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/Acme/AccountBundle/Entity/User.php
namespace Acme\AccountBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* @ORM\Entity
* @UniqueEntity(fields="email", message="Email already taken")
*/
class User
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="string", length=255)
PDF brought to you by
generated on June 26, 2014
Chapter 32: Comment implmenter un simple formulaire de cration de compte | 105
Listing 32-2
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
* @Assert\NotBlank()
* @Assert\Email()
*/
protected $email;
/**
* @ORM\Column(type="string", length=255)
* @Assert\NotBlank()
*/
protected $plainPassword;
public function getId()
{
return $this->id;
}
public function getEmail()
{
return $this->email;
}
public function setEmail($email)
{
$this->email = $email;
}
public function getPlainPassword()
{
return $this->plainPassword;
}
public function setPlainPassword($password)
{
$this->plainPassword = $password;
}
}
Cette entit User contient trois champs et deux d'entre eux (email et plainPassword) doivent tre
affichs dans le formulaire. La proprit email doit tre unique dans la base de donnes, ce qui est ralis
par l'ajout d'une validation au dbut de la classe.
Si vous voulez intgrer cet utilisateur dans le systme de scurit, vous devez implmenter la
UserInterface du composant Security.
Crer un formulaire pour le modle
Ensuite, crez le formulaire pour le modle User:
1
2
3
4
5
// src/Acme/AccountBundle/Form/Type/UserType.php
namespace Acme\AccountBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
PDF brought to you by
generated on June 26, 2014
Chapter 32: Comment implmenter un simple formulaire de cration de compte | 106
Listing 32-3
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('email', 'email');
$builder->add('plainPassword', 'repeated', array(
'first_name' => 'password',
'second_name' => 'confirm',
'type' => 'password',
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\AccountBundle\Entity\User'
));
}
public function getName()
{
return 'user';
}
}
Il n'y a que deux champs : email et plainPassword (dupliqu pour confirmer le mot de passe saisi).
L'option data_class spcifie au formulaire le nom de la classe associe (c-a-d l'entit User).
Pour en savoir plus sur le composant Formulaire, lisez Formulaires.
Imbriquer le formulaire User dans le formulaire de cration de compte
Le formulaire que vous utiliserez pour la page de cration de compte n'est pas le mme que le formulaire
qui est utilis pour modifier simplement l'objet User (c-a-d UserType). Le formulaire de cration de
compte contiendra quelques champs supplmentaires, comme Accepter les conditions , dont les
valeurs ne seront pas stockes en base de donnes.
Commencez par crer une simple classe qui reprsente la cration de compte ( registration en
anglais):
1
2
3
4
5
6
7
8
9
10
11
// src/Acme/AccountBundle/Form/Model/Registration.php
namespace Acme\AccountBundle\Form\Model;
use Symfony\Component\Validator\Constraints as Assert;
use Acme\AccountBundle\Entity\User;
class Registration
{
/**
* @Assert\Type(type="Acme\AccountBundle\Entity\User")
PDF brought to you by
generated on June 26, 2014
Chapter 32: Comment implmenter un simple formulaire de cration de compte | 107
Listing 32-4
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
*/
protected $user;
/**
* @Assert\NotBlank()
* @Assert\True()
*/
protected $termsAccepted;
public function setUser(User $user)
{
$this->user = $user;
}
public function getUser()
{
return $this->user;
}
public function getTermsAccepted()
{
return $this->termsAccepted;
}
public function setTermsAccepted($termsAccepted)
{
$this->termsAccepted = (Boolean) $termsAccepted;
}
}
Ensuite, crez le formulaire pour ce modle Registration:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/Acme/AccountBundle/Form/Type/RegistrationType.php
namespace Acme\AccountBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class RegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('user', new UserType());
$builder->add('terms', 'checkbox', array('property_path' => 'termsAccepted'));
}
public function getName()
{
return 'registration';
}
}
Vous n'avez pas besoin d'utiliser de mthode spciale pour imbriquer le formulaire UserType. Un
formulaire est aussi un champ, donc vous pouvez l'ajouter comme n'importe quel champ, avec l'objectif
que la proprit Registration.user contienne une instance de la classe User.
PDF brought to you by
generated on June 26, 2014
Chapter 32: Comment implmenter un simple formulaire de cration de compte | 108
Listing 32-5
Listing 32-6
Listing 32-7
Grer la soumission du formulaire
Ensuite, vous aurez besoin d'un contrleur pour prendre en charge le formulaire. Commencez par crer
un simple contrleur pour afficher le formulaire de cration de compte:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/Acme/AccountBundle/Controller/AccountController.php
namespace Acme\AccountBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Acme\AccountBundle\Form\Type\RegistrationType;
use Acme\AccountBundle\Form\Model\Registration;
class AccountController extends Controller
{
public function registerAction()
{
$form = $this->createForm(new RegistrationType(), new Registration());
return $this->render('AcmeAccountBundle:Account:register.html.twig', array('form'
=> $form->createView()));
}
}
et son template :
1
2
3
4
5
6
{# src/Acme/AccountBundle/Resources/views/Account/register.html.twig #}
<form action="{{ path('create')}}" method="post" {{ form_enctype(form) }}>
{{ form_widget(form) }}
<input type="submit" />
</form>
Enfin, crez le contrleur qui prendra en charge la soumission du formulaire. Il se chargera de la
validation, et enregistrera les donnes dans la base de donnes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public function createAction()
{
$em = $this->getDoctrine()->getEntityManager();
$form = $this->createForm(new RegistrationType(), new Registration());
$form->handleRequest($this->getRequest());
if ($form->isValid()) {
$registration = $form->getData();
$em->persist($registration->getUser());
$em->flush();
return $this->redirect(...);
}
return $this->render('AcmeAccountBundle:Account:register.html.twig', array('form' =>
$form->createView()));
}
PDF brought to you by
generated on June 26, 2014
Chapter 32: Comment implmenter un simple formulaire de cration de compte | 109
C'est tout ! Maintenant, votre formulaire valide et vous permet d'enregistrer un objet User dans la base de
donnes. La checkbox supplmentaire terms du modle Registration est utilise durant la validation,
mais n'est plus utilise ensuite lors de l'enregistrement de l'utilisateur en base de donnes.
PDF brought to you by
generated on June 26, 2014
Chapter 32: Comment implmenter un simple formulaire de cration de compte | 110
Listing 33-1
Listing 33-2
Chapter 33
Comment envoyer un Email
Envoyer des emails est une tche classique pour n'importe quelle application web et une qui possde des
complications et piges potentiels. Plutt que de rinventer la roue, une solution pour envoyer des emails
est d'utiliser le SwiftmailerBundle, qui tire partie de la puissance de la bibliothque Swiftmailer
1
.
N'oubliez pas d'activer le bundle dans votre Kernel avant de l'utiliser:
1
2
3
4
5
6
7
8
9
public function registerBundles()
{
$bundles = array(
...,
new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
);
// ...
}
Configuration
Avant d'utiliser Swiftmailer, soyez sr d'inclure sa configuration. Le seul paramtre obligatoire de la
configuration est transport :
1
2
3
4
5
6
7
8
# app/config/config.yml
swiftmailer:
transport: smtp
encryption: ssl
auth_mode: login
host: smtp.gmail.com
username: your_username
password: your_password
1. http://swiftmailer.org/
PDF brought to you by
generated on June 26, 2014
Chapter 33: Comment envoyer un Email | 111
Listing 33-3
La plus grande partie de la configuration de Swiftmailer traite de comment les messages eux-mmes
devraient tre envoys.
Les attributs de configuration suivants sont disponibles :
transport (smtp, mail, sendmail, ou gmail)
username
password
host
port
encryption (tls, or ssl)
auth_mode (plain, login, or cram-md5)
spool
type (comment mettre les messages dans une queue, seulement file est support
pour le moment)
path (o stocker les messages)
delivery_address (une adresse email o envoyer TOUS les emails)
disable_delivery (dfinir true pour dsactiver entirement l'envoi des emails)
Envoyer des Emails
La bibliothque Swiftmailer fonctionne grce la cration, la configuration et enfin l'envoi d'objets
Swift_Message. Le mailer est responsable de l'envoi rl du message et est accessible via le service
mailer. Globalement, envoyer un email est assez simple:
1
2
3
4
5
6
7
8
9
10
11
12
public function indexAction($name)
{
$message = \Swift_Message::newInstance()
->setSubject('Hello Email')
->setFrom('[email protected]')
->setTo('[email protected]')
->setBody($this->renderView('HelloBundle:Hello:email.txt.twig', array('name' =>
$name)))
;
$this->get('mailer')->send($message);
return $this->render(...);
}
Pour garder les choses dcouples, le corps de l'email a t stock dans un template et est rendu grce
la mthode renderView().
L'objet $message supporte beaucoup plus d'options, comme inclure des pices jointes, ajouter du
contenu HTML, et bien plus encore. Heureusement, Swiftmailer couvre le thme de la Cration de
Messages
2
de manire trs dtaille dans sa documentation.
Plusieurs articles du cookbook lis l'envoi d'emails dans Symfony2 sont disponibles :
Comment utiliser Gmail pour envoyer des Emails
Comment travailler avec les Emails pendant le Dveloppement
Comment utiliser le Spool d'Email
2. http://swiftmailer.org/docs/messages.html
PDF brought to you by
generated on June 26, 2014
Chapter 33: Comment envoyer un Email | 112
Listing 34-1
Chapter 34
Comment utiliser Gmail pour envoyer des
Emails
Durant le dveloppement, au lieu d'utiliser un serveur SMTP ordinaire pour envoyer des emails, vous
pourriez trouver qu'utiliser Gmail soit plus facile ou plus pratique. Le bundle Swiftmailer rend cela
vraiment facile.
Au lieu d'utiliser votre compte Gmail courant, il est bien sr recommand que vous criez un
compte spcial.
Dans le fichier de configuration de dveloppement, changez le paramtre transport pour qu'il contienne
la valeur gmail et dfinissez les paramtres username et password avec les informations d'accs de Google
:
1
2
3
4
5
# app/config/config_dev.yml
swiftmailer:
transport: gmail
username: your_gmail_username
password: your_gmail_password
Vous avez termin !
Le transport gmail est simplement un raccourci qui utilise le transport smtp et dfinit encryption,
auth_mode et host afin que cela fonctionne avec Gmail.
PDF brought to you by
generated on June 26, 2014
Chapter 34: Comment utiliser Gmail pour envoyer des Emails | 113
Listing 35-1
Listing 35-2
Chapter 35
Comment travailler avec les Emails pendant le
Dveloppement
Lorsque vous dveloppez une application qui envoie des emails, vous voudrez souvent que cette dernire
n'en envoie pas au destinataire spcifi pendant le dveloppement. Si vous utilisez le SwiftmailerBundle
avec Symfony2, vous pouvez facilement raliser cela travers les paramtres de configuration sans avoir
faire de quelconques changements dans votre code. Il y a deux choix principaux possibles lorsqu'on
parle de gestion d'emails durant le dveloppement : (a) dsactiver compltement l'envoi d'emails ou (b)
envoyer tous les emails une adresse spcifique.
Dsactiver l'Envoi
Vous pouvez dsactiver l'envoi d'emails en dfinissant l'option disable_delivery true. C'est ce qui
est fait par dfaut dans l'environnement test dans la distribution Standard. Si vous faites cela dans la
configuration spcifique test, alors les emails ne seront pas envoys lorsque vous excuterez les tests,
mais continueront d'tre envoys dans les environnements prod et dev :
1
2
3
# app/config/config_test.yml
swiftmailer:
disable_delivery: true
Si vous voulez aussi dsactiver l'envoi dans l'environnement dev, ajoutez tout simplement la mme
configuration dans le fichier config_dev.yml.
Envoyer une Adresse Spcifique
Vous pouvez aussi choisir d'avoir tous les emails envoys une adresse spcifique la place de l'adresse
rellement spcifie lors de l'envoi du message. Ceci peut tre accompli via l'option delivery_address :
PDF brought to you by
generated on June 26, 2014
Chapter 35: Comment travailler avec les Emails pendant le Dveloppement | 114
Listing 35-3
Listing 35-4
1
2
3
# app/config/config_dev.yml
swiftmailer:
delivery_address: [email protected]
Maintenant, supposons que vous envoyiez un email [email protected].
1
2
3
4
5
6
7
8
9
10
11
12
public function indexAction($name)
{
$message = \Swift_Message::newInstance()
->setSubject('Hello Email')
->setFrom('[email protected]')
->setTo('[email protected]')
->setBody($this->renderView('HelloBundle:Hello:email.txt.twig', array('name' =>
$name)))
;
$this->get('mailer')->send($message);
return $this->render(...);
}
Dans l'environnement dev, l'email sera envoy [email protected]. Swiftmailer ajoutera un en-tte
supplmentaire l'email, X-Swift-To, contenant l'adresse remplace, afin que vous puissiez toujours voir
qui il aurait t envoy.
En plus des adresses to, cela va aussi stopper les emails envoys n'importe quelle adresse des
champs CC et BCC. Swiftmailer ajoutera des en-ttes additionnels l'email contenant les adresses
surcharges. Ces en-ttes sont respectivement X-Swift-Cc et X-Swift-Bcc pour les adresses de CC
et BCC.
Voir les informations depuis la Barre d'Outils de Dbuggage Web
Vous pouvez voir tout email envoy durant une unique rponse lorsque vous tes dans l'environnement
dev via la Barre d'Outils de Dbuggage. L'icne d'email dans la barre d'outils montrera combien d'emails
ont t envoys. Si vous cliquez dessus, un rapport s'ouvrira montrant les dtails des emails envoys.
Si vous envoyez un email et puis redirigez immdiatement vers une autre page, la barre d'outils de
dbuggage n'affichera pas d'icne d'email ni de rapport sur la page d'aprs.
A la place, vous pouvez dfinir l'option intercept_redirects comme tant true dans le fichier
config_dev.yml, ce qui va forcer les redirections s'arrter et vous permettre d'ouvrir le rapport avec
les dtails des emails envoys.
Sinon, vous pouvez ouvrir le profiler aprs la redirection et rechercher par l'URL soumise et utilise
lors de la requte prcdente (par exemple : /contact/handle). La fonctionnalit de recherche du
profiler vous permet de charger les informations du profiler pour toutes les requtes passes.
1
2
3
# app/config/config_dev.yml
web_profiler:
intercept_redirects: true
PDF brought to you by
generated on June 26, 2014
Chapter 35: Comment travailler avec les Emails pendant le Dveloppement | 115
Listing 36-1
Listing 36-2
Listing 36-3
Chapter 36
Comment utiliser le Spool d'Email
Quand vous utilisez le SwiftmailerBundle pour envoyer un email depuis une application Symfony2,
il va par dfaut l'envoyer immdiatement. Vous pourriez, cependant, vouloir viter d'avoir un hit de
performance entre le Swiftmailer et le transport de l'email, qui pourrait avoir pour consquence que
l'utilisateur ait attendre que la page suivante se charge pendant que l'email est envoy. Cela peut tre
vit en choisissant d'envoyer les emails en mode spool au lieu de les envoyer directement. Cela
signifie que Swiftmailer n'essaie pas d'envoyer l'email mais la place sauvegarde le message quelque
part comme par exemple dans un fichier. Un autre processus peut alors lire depuis le spool et prendre
en charge l'envoi des emails contenus dans ce dernier. Pour le moment, seul le spooling via un fichier
est support par Swiftmailer.
Afin d'utiliser le spool , utilisez la configuration suivante :
1
2
3
4
5
6
# app/config/config.yml
swiftmailer:
# ...
spool:
type: file
path: /path/to/spool
Si vous voulez stocker le spool quelque part dans votre rpertoire de projet, rappelez-vous que
vous pouvez utiliser le paramtre %kernel.root_dir% pour rfrencer la racine du projet :
1 path: "%kernel.root_dir%/spool"
Maintenant, quand votre application envoie un email, il ne sera en fait pas envoy mais ajout au spool
la place. L'envoi des messages depuis le spool est effectu sparment. Il y a une commande de la
console pour envoyer les messages qui sont dans le spool :
1 $ php app/console swiftmailer:spool:send --env=prod
Cette commande possde une option pour limiter le nombre de messages devant tre envoys :
PDF brought to you by
generated on June 26, 2014
Chapter 36: Comment utiliser le Spool d'Email | 116
Listing 36-4
Listing 36-5
1 $ php app/console swiftmailer:spool:send --message-limit=10 --env=prod
Vous pouvez aussi dfinir la limite de temps en secondes :
1 $ php app/console swiftmailer:spool:send --time-limit=10 --env=prod
Bien sr, vous n'avez pas besoin de lancer cette tche manuellement. A la place, la commande de la
console devrait tre lance par une tche cron ou une tche planifie et excute intervalle rgulier.
PDF brought to you by
generated on June 26, 2014
Chapter 36: Comment utiliser le Spool d'Email | 117
Listing 37-1
Chapter 37
Comment mettre en place des filtres avant et
aprs un processus donn
Il est trs commun, dans le dveloppement d'application web, d'avoir besoin qu'un bout de logique soit
excut juste avant ou juste aprs vos actions de contrleur agissant comme des filtres ou des hooks .
Dans Symfony1, cela tait effectu avec les mthodes preExecute et postExecute ; la plupart des
principaux frameworks ont des mthodes similaires mais il n'y a rien de semblable dans Symfony2. La
bonne nouvelle est qu'il y a une bien meilleure manire d'interfrer le processus Requte -> Rponse en
utilisant le composant EventDispatcher.
Exemple de validation de jeton
Imaginez que vous devez dvelopper une API dans laquelle certains contrleurs sont publics mais d'autres
ont un accs restreint qui est rserv un ou plusieurs clients. Pour ces fonctionnalits prives, vous
pourriez fournir un jeton vos clients afin qu'ils s'identifient eux-mmes.
Donc, avant d'excuter votre action de contrleur, vous devez vrifier si l'action est restreinte ou pas. Si
elle est restreinte, vous devez valider le jeton fourni.
Veuillez noter que, pour garder cet article suffisament simple, les jetons vont tre dfinis dans la
configuration et aucune mise en place de base de donnes ni de fournisseur d'authentification via
le composant de Scurit ne vont tre utiliss.
Crer un filtre avant un vnement kernel.controller
Premirement, stockez un jeton basique en configuration en utilisant le fichier config.yml et la cl
parameters :
PDF brought to you by
generated on June 26, 2014
Chapter 37: Comment mettre en place des filtres avant et aprs un processus donn | 118
Listing 37-2
Listing 37-3
Listing 37-4
1
2
3
4
5
# app/config/config.yml
parameters:
tokens:
client1: pass1
client2: pass2
Les contrleurs de Tag devant tre vrifis
Un listener de kernel.controller est notifi chaque requte juste avant que le contrleur ne soit
excut. D'abord, vous avez besoin de savoir si le contrleur qui correspond la requte a besoin d'une
validation de token.
Une faon propre et facile est de crer une interface vide et de faire en sorte que les contrleurs
l'implmentent:
1
2
3
4
5
6
namespace Acme\DemoBundle\Controller;
interface TokenAuthenticatedController
{
// ...
}
Un contrleur qui implmente cette interface ressemble simplement cela:
1
2
3
4
5
6
7
8
9
10
11
12
13
namespace Acme\DemoBundle\Controller;
use Acme\DemoBundle\Controller\TokenAuthenticatedController;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class FooController extends Controller implements TokenAuthenticatedController
{
// une action qui a besoin d'une authentification
public function barAction()
{
// ...
}
}
Crer un Listener d'vnement
Ensuite, vous allez avoir besoin de crer un listener d'vnement, qui va contenir la logique que vous
souhaitez excuter avant vos contrleurs. Si vous n'tes pas familier avec les listeners d'vnement,
vous pouvez en apprendre plus sur eux en lisant Comment crer un listener ( couteur en franais)
d'vnement:
1
2
3
4
5
6
7
8
9
// src/Acme/DemoBundle/EventListener/TokenListener.php
namespace Acme\DemoBundle\EventListener;
use Acme\DemoBundle\Controller\TokenAuthenticatedController;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class TokenListener
{
PDF brought to you by
generated on June 26, 2014
Chapter 37: Comment mettre en place des filtres avant et aprs un processus donn | 119
Listing 37-5
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private $tokens;
public function __construct($tokens)
{
$this->tokens = $tokens;
}
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
/*
* $controller peut tre une classe ou une closure. Ce n'est pas
* courant dans Symfony2 mais a peut arriver.
* Si c'est une classe, elle est au format array
*/
if (!is_array($controller)) {
return;
}
if ($controller[0] instanceof TokenAuthenticatedController) {
$token = $event->getRequest()->query->get('token');
if (!in_array($token, $this->tokens)) {
throw new AccessDeniedHttpException('Cette action ncessite un jeton
valide!');
}
}
}
}
Dclarez le Listener
Finalement, dclarez votre listener comme un service et taggez-le en tant que listener
d'vnement. En coutant le kernel.controller, vous dites Symfony que vous voulez que votre
listener soit appel juste avant qu'un contrleur quelconque soit excut :
# app/config/config.yml (or inside your services.yml)
services:
demo.tokens.action_listener:
class: Acme\DemoBundle\EventListener\TokenListener
arguments: [ %tokens% ]
tags:
- { name: kernel.event_listener, event: kernel.controller, method:
onKernelController }
Avec cette configuration, votre mthode onKernelController de TokenListener sera excute chaque
requte. Si le contrleur qui doit tre excut implmente TokenAuthenticatedController,
l'authentification par jeton est applique. Cela vous permet d'avoir le filtre avant que vous vouliez sur
tous les contrleurs.
Crer un filtre aprs un vnement kernel.response
En plus d'avoir un hook qui est excut avant notre contrleur, vous pouvez galement ajouter un
hook qui sera excut aprs votre contrleur. Pour cet exemple, imaginez que vous voulez ajouter un hash
PDF brought to you by
generated on June 26, 2014
Chapter 37: Comment mettre en place des filtres avant et aprs un processus donn | 120
Listing 37-6
Listing 37-7
Listing 37-8
sha1 (avec un salage - ou salt - qui utilise le jeton) chaque rponse qui a pass notre authentification par
jeton.
Un autre vnement du noyau de Symfony, appel kernel.response, est notifi chaque requte, mais
aprs que le contrleur a retourn un objet Response. Crer un couteur aprs est aussi simple que de
crer une classe couteur et de l'enregistrer en tant que service sur cet vnement.
Par exemple, prenez le TokenListener de l'exemple prcdent et enregistrez d'abord le jeton
d'authentification dans les attributs de la requte. Cela indiquera que cette requte a subi une demande
d'authentification par jeton:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function onKernelController(FilterControllerEvent $event)
{
// ...
if ($controller[0] instanceof TokenAuthenticatedController) {
$token = $event->getRequest()->query->get('token');
if (!in_array($token, $this->tokens)) {
throw new AccessDeniedHttpException('Cette action ncessite un jeton valide!');
}
// marque la requte aprs authentification
$event->getRequest()->attributes->set('auth_token', $token);
}
}
Maintenant, ajoutons une autre mthode cette classe, onKernelResponse, qui vrifiera que l'objet
requte est marqu et, si c'est le cas, qui dfinira un en-tte personnalis pour la rponse:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ajoutez la nouvelle instruction use en haut de votre fichier
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
public function onKernelResponse(FilterResponseEvent $event)
{
// Vrifie que onKernelController est une requte authentifie
if (!$token = $event->getRequest()->attributes->get('auth_token')) {
return;
}
$response = $event->getResponse();
// cre un hash et le dfinit comme en-tte de la rponse
$hash = sha1($response->getContent().$token);
$response->headers->set('X-CONTENT-HASH', $hash);
}
Enfin, un second tag est ncessaire dans la dfinition de service pour notifier Symfony que l'vnement
onKernelResponse doit tre notifi pour l'vnement kernel.response :
# app/config/config.yml (or inside your services.yml)
services:
demo.tokens.action_listener:
class: Acme\DemoBundle\EventListener\TokenListener
arguments: [ %tokens% ]
tags:
- { name: kernel.event_listener, event: kernel.controller, method:
onKernelController }
- { name: kernel.event_listener, event: kernel.response, method: onKernelResponse }
PDF brought to you by
generated on June 26, 2014
Chapter 37: Comment mettre en place des filtres avant et aprs un processus donn | 121
C'est tout ! Le TokenListener est maintenant notifi avant chaque contrleur qui est excut
(onKernelController) et aprs chaque rponse retourne par un contrleur (onKernelResponse). En
faisant des contrleurs spcifiques qui implmentent l'interface TokenAuthenticatedController, nos
couteurs savent quels contrleurs traiter. Et en stockant une valeur dans les attributs de la requte, la
mthode onKernelResponse sait quand ajouter notre nouvel en-tte. Amusez-vous !
PDF brought to you by
generated on June 26, 2014
Chapter 37: Comment mettre en place des filtres avant et aprs un processus donn | 122
Listing 38-1
Listing 38-2
Chapter 38
Comment tendre une Classe sans utiliser
l'Hritage
Pour permettre plusieurs classes d'ajouter des mthodes une autre, vous pouvez dfinir la mthode
magique __call() dans la classe que vous voulez tendre comme cela :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Foo
{
// ...
public function __call($method, $arguments)
{
// cre un vnement nomm 'foo.method_is_not_found'
$event = new HandleUndefinedMethodEvent($this, $method, $arguments);
$this->dispatcher->dispatch('foo.method_is_not_found', $event);
// aucun listener n'a t capable d'analyser l'vnement ? La mthode
// n'existe pas
if (!$event->isProcessed()) {
throw new \Exception(sprintf('Call to undefined method %s::%s.',
get_class($this), $method));
}
// retourne la valeur retourne par le listener
return $event->getReturnValue();
}
}
Cela utilise un vnement HandleUndefinedMethodEvent spcial qui devrait aussi tre cr. C'est en
fait une classe gnrique qui pourrait tre rutilise chaque fois que vous avez besoin de ce pattern
d'extension de classe :
1
2
3
use Symfony\Component\EventDispatcher\Event;
class HandleUndefinedMethodEvent extends Event
PDF brought to you by
generated on June 26, 2014
Chapter 38: Comment tendre une Classe sans utiliser l'Hritage | 123
Listing 38-3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
{
protected $subject;
protected $method;
protected $arguments;
protected $returnValue;
protected $isProcessed = false;
public function __construct($subject, $method, $arguments)
{
$this->subject = $subject;
$this->method = $method;
$this->arguments = $arguments;
}
public function getSubject()
{
return $this->subject;
}
public function getMethod()
{
return $this->method;
}
public function getArguments()
{
return $this->arguments;
}
/**
* Dfinit la valeur retourner et stoppe les autres listeners allant
* tre notifis
*/
public function setReturnValue($val)
{
$this->returnValue = $val;
$this->isProcessed = true;
$this->stopPropagation();
}
public function getReturnValue($val)
{
return $this->returnValue;
}
public function isProcessed()
{
return $this->isProcessed;
}
}
Ensuite, crez une classe qui va couter l'vnement foo.method_is_not_found et ajoutez la mthode
bar() :
1
2
3
4
class Bar
{
public function onFooMethodIsNotFound(HandleUndefinedMethodEvent $event)
{
PDF brought to you by
generated on June 26, 2014
Chapter 38: Comment tendre une Classe sans utiliser l'Hritage | 124
Listing 38-4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// nous voulons rpondre seulement aux appels de la mthode 'bar'
if ('bar' != $event->getMethod()) {
// autorise un autre listener prendre en charge cette mthode
// inconnue
return;
}
// le sujet de l'objet (l'instance foo)
$foo = $event->getSubject();
// les arguments de la mthode bar
$arguments = $event->getArguments();
// faites quelque chose
// ...
// dfinit la valeur retourne
$event->setReturnValue($someValue);
}
}
Finalement, ajoutez la nouvelle mthode bar la classe Foo en dclarant une instance de Bar avec
l'vnement foo.method_is_not_found :
1
2
$bar = new Bar();
$dispatcher->addListener('foo.method_is_not_found', array($bar, 'onFooMethodIsNotFound'));
PDF brought to you by
generated on June 26, 2014
Chapter 38: Comment tendre une Classe sans utiliser l'Hritage | 125
Listing 39-1
Chapter 39
Comment personnaliser le Comportement
d'une Mthode sans utiliser l'Hritage
Faire quelque chose avant ou aprs l'Appel d'une Mthode
Si vous souhaitez faire quelque chose juste avant, ou juste aprs qu'une mthode a t appele, vous
pouvez dispatcher un vnement respectivement au dbut ou la fin d'une mthode:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Foo
{
// ...
public function send($foo, $bar)
{
// faites quelque chose avant le dbut de la mthode
$event = new FilterBeforeSendEvent($foo, $bar);
$this->dispatcher->dispatch('foo.pre_send', $event);
// rcuprez $foo et $bar depuis l'vnement, ils pourraient
// avoir t modifis
$foo = $event->getFoo();
$bar = $event->getBar();
// l'implmentation relle est ici
$ret = ...;
// faites quelque chose aprs la fin de la mthode
$event = new FilterSendReturnValue($ret);
$this->dispatcher->dispatch('foo.post_send', $event);
return $event->getReturnValue();
}
}
PDF brought to you by
generated on June 26, 2014
Chapter 39: Comment personnaliser le Comportement d'une Mthode sans utiliser l'Hritage | 126
Listing 39-2
Dans cet exemple, deux vnements sont lancs : foo.pre_send, avant que la mthode soit excute, et
foo.post_send aprs que la mthode est excute. Chacun utilise une classe Event personnalise pour
communiquer des informations aux listeners des deux vnements. Ces classes d'vnements devraient
tre cres par vous-mme et devraient permettre, dans cet exemple, aux variables $foo, $bar et $ret
d'tre rcupres et dfinies par les listeners.
Par exemple, supposons que FilterSendReturnValue possde une mthode setReturnValue, un
listener pourrait alors ressembler ceci :
1
2
3
4
5
6
7
public function onFooPostSend(FilterSendReturnValue $event)
{
$ret = $event->getReturnValue();
// modifie la valeur originale de ``$ret``
$event->setReturnValue($ret);
}
PDF brought to you by
generated on June 26, 2014
Chapter 39: Comment personnaliser le Comportement d'une Mthode sans utiliser l'Hritage | 127
Listing 40-1
Listing 40-2
Listing 40-3
Chapter 40
Comment personnaliser le rendu de formulaire
Symfony vous propose un large choix de mthodes pour personnaliser la manire dont un formulaire
est affich. Dans ce guide, vous apprendrez comment personnaliser autant que possible chaque partie de
votre formulaire sans effort, et ceci que vous utilisiez Twig ou PHP comme moteur de template.
Bases de l'affichage de formulaire
Rappelons que le libell, les ventuelles erreurs et le widget HTML d'un champ de formulaire peuvent
tre facilement affichs en utilisant la fonction Twig form_row ou la mthode d'aide ( helper en anglais)
PHP row :
1 {{ form_row(form.age) }}
Vous pouvez galement afficher chacune de ces trois parties d'un champ individuellement :
1
2
3
4
5
<div>
{{ form_label(form.age) }}
{{ form_errors(form.age) }}
{{ form_widget(form.age) }}
</div>
Dans les deux cas, le libell du formulaire, les erreurs et le widget HTML sont affichs en utilisant un
ensemble de balises livr d'office avec Symfony. Par exemple, les deux templates ci-dessus afficheront :
1
2
3
4
5
6
7
<div>
<label for="form_age">Age</label>
<ul>
<li>This field is required</li>
</ul>
<input type="number" id="form_age" name="form[age]" />
</div>
Pour tester rapidement un formulaire, vous pouvez afficher l'ensemble du formulaire en une seule ligne :
PDF brought to you by
generated on June 26, 2014
Chapter 40: Comment personnaliser le rendu de formulaire | 128
Listing 40-4
Listing 40-5
Listing 40-6
Listing 40-7
Listing 40-8
1 {{ form_widget(form) }}
La suite de ce document explique comment le rendu de chaque partie du formulaire peut tre modifi
diffrents niveaux. Pour plus d'informations sur l'affichage de formulaires en gnral, lisez Afficher un
Formulaire dans un Template.
Que sont les thmes de formulaire ?
Symfony utilise des fragments de formulaires - des parties de template qui affichent juste une partie du
formulaire - pour afficher chaque partie du formulaire : libells de champs, erreurs, champs texte input,
balises select, etc.
Ces fragments sont dfinis comme blocs dans Twig et comme fichiers de templates avec PHP.
Un thme n'est rien de plus qu'un ensemble de fragments que vous pouvez utiliser pour afficher un
formulaire. En d'autres termes, si vous voulez personnaliser l'affichage d'une partie d'un formulaire, vous
pouvez importer un thme qui contient une personnalisation des fragments de formulaire concerns.
Symfony est fourni avec un thme par dfaut (form_div_layout.html.twig
1
pour Twig et
FrameworkBundle:Form pour PHP) qui dfinit chaque fragment ncessaire l'affichage des diffrentes
parties d'un formulaire.
Dans la section suivante, vous apprendrez comment personnaliser un thme en surchargeant un ou
l'ensemble de ses fragments.
Par exemple, lorsque le widget d'un type de champ integer est affich, un champ input number est
gnr.
1 {{ form_widget(form.age) }}
affiche :
1 <input type="number" id="form_age" name="form[age]" required="required" value="33" />
En interne, Symfony utilise le fragment integer_widget pour afficher le champ. C'est parce que le type
de champ est integer et que vous voulez afficher son widget (par opposition son libell ou ses
erreurs).
Par dfaut, avec Twig, le bloc integer_widget du template form_div_layout.html.twig
2
serait choisi.
En PHP, cela serait plutt le fichier integer_widget.html.php situ dans le dossier FrameworkBundle/
Resources/views/Form.
L'implmentation par dfaut du fragment integer_widget ressemble ceci :
1
2
3
4
5
{# form_div_layout.html.twig #}
{% block integer_widget %}
{% set type = type|default('number') %}
{{ block('form_widget_simple') }}
{% endblock integer_widget %}
Comme vous pouvez le voir, ce fragment affiche un autre fragment (form_widget_simple) :
1. https://github.com/symfony/symfony/blob/2.1/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
2. https://github.com/symfony/symfony/blob/2.1/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
PDF brought to you by
generated on June 26, 2014
Chapter 40: Comment personnaliser le rendu de formulaire | 129
1
2
3
4
5
{# form_div_layout.html.twig #}
{% block form_widget_simple %}
{% set type = type|default('text') %}
<input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty
%}value="{{ value }}" {% endif %}/>
{% endblock form_widget_simple %}
L'ide est qu'un fragment dtermine le code HTML gnr pour chaque partie du formulaire. Pour
personnaliser l'affichage d'un formulaire, vous devez juste identifier et surcharger le bon fragment. Un
ensemble de personnalisations de fragments d'un formulaire est appel thme de formulaire. Lorsque
vous affichez un formulaire, pouvez choisir quel(s) thme(s) de formulaire appliquer.
Dans Twig, un thme est un unique fichier de template et les fragments sont des blocs dfinis dans ce
fichier.
En PHP, un thme est un rpertoire et les fragments sont des fichiers de template individuels dans ce
rpertoire.
Savoir quel bloc personnaliser
Dans cet exemple, le nom du fragment personnalis est integer_widget parce que vous voulez
surcharger le widget HTML pour tous les types de champ integer. Si vous voulez personnaliser
les champs textarea , vous devrez personnaliser textarea_widget.
Comme vous le voyez, un nom de fragment est une combinaison du type de champ et de la
partie du formulaire qui doit tre affiche (ex widget, label, errors, row). En consquence, pour
personnaliser la manire dont les erreurs sont affiches pour les champs input text uniquement,
vous devrez personnaliser le fragment text_errors.
Pourtant, bien souvent, vous voudrez personnaliser l'affichage des erreurs pour tous les champs.
Vous pouvez faire cela en personnalisant le fragment form_errors. Cette mthode tire avantage
de l'hritage de type de champs. Plus prcisment, puisque le type text tend le type field,
le composant formulaire cherchera d'abord le fragment spcifique au type (par exemple :
text_errors) avant de se rabattre sur le nom du fragment parent si le spcifique n'existe pas (par
exemple : form_errors).
Pour plus d'informations sur ce sujet, lisez Nommage de Fragment de Formulaire.
Thmes de formulaire
Pour apprcier la puissance des thmes de formulaire, supposons que vous vouliez encadrer chaque
champ number par un div. La cl pour faire cela est de personnaliser le fragment integer_widget.
Thmes de formulaire avec Twig
Lorsque vous personnalisez un bloc de champ de formulaire Twig, vous avez deux choix possibles quant
la localisation du bloc personnalis :
Mthode Avantages Inconvnients
Dans le mme template que le
formulaire
Rapide et facile Ne peut pas tre rutilis
PDF brought to you by
generated on June 26, 2014
Chapter 40: Comment personnaliser le rendu de formulaire | 130
Listing 40-9
Listing 40-10
Mthode Avantages Inconvnients
Dans un template spar Peut tre rutilis par d'autres
templates
Ncessite la cration d'un
template
Ces deux mthodes ont les mmes effets mais ne sont pas aussi avantageuses l'une que l'autre suivant les
situations.
Mthode 1: Dans le mme template que le formulaire
La manire la plus facile de personnaliser le bloc integer_widget est de le modifier directement dans le
template qui affiche le formulaire.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{% extends '::base.html.twig' %}
{% form_theme form _self %}
{% block integer_widget %}
<div class="integer_widget">
{% set type = type|default('number') %}
{{ block('form_widget_simple') }}
</div>
{% endblock %}
{% block content %}
{# affiche le formulaire #}
{{ form_row(form.age) }}
{% endblock %}
En utilisant la balise spciale {% form_theme form _self %}, Twig cherchera tout bloc surcharg dans le
mme template. En supposant que le champ form.age soit du type de champ integer, lorsque le widget
sera affich, le bloc personnalis integer_widget sera utilis.
L'inconvnient de cette mthode est que les blocs de formulaire personnaliss ne peuvent pas tre
rutiliss pour afficher d'autres formulaires dans d'autres templates. En d'autres termes, cette mthode
est spcialement utile pour faire des changements applicables un formulaire spcifique de votre
application. Si vous voulez rutiliser vos personnalisations pour certains (ou tous les) autres formulaires,
lisez la section suivante.
Mthode 2: Dans un template spar
Vous pouvez galement choisir de mettre le bloc de formulaire personnalis integer_widget dans un
template spar. Le code et le rsultat final seront les mmes, mais vous pourrez alors rutiliser cette
personnalisation de formulaire dans d'autres templates :
1
2
3
4
5
6
7
{# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #}
{% block integer_widget %}
<div class="integer_widget">
{% set type = type|default('number') %}
{{ block('form_widget_simple') }}
</div>
{% endblock %}
Maintenant que vous avez cr le bloc de formulaire personnalis, vous devez dire Symfony de l'utiliser.
Dans le template o vous affichez votre formulaire, indiquez Symfony d'utiliser votre template via la
balise form_theme :
PDF brought to you by
generated on June 26, 2014
Chapter 40: Comment personnaliser le rendu de formulaire | 131
Listing 40-11
Listing 40-12
Listing 40-13
Listing 40-14
1
2
3
{% form_theme form 'AcmeDemoBundle:Form:fields.html.twig' %}
{{ form_widget(form.age) }}
Pour afficher le widget form.age, Symfony utilisera le bloc de formulaire integer_widget du nouveau
template et la balise input sera encadre par l'lment div comme vous l'avez spcifi dans le bloc
personnalis.
Thme de formulaire en PHP
Si vous utilisez PHP comme moteur de template, la seule mthode pour personnaliser un fragment est de
crer un nouveau fichier de template, ce qui est quivalent la seconde mthode utilise par Twig.
Le fichier de template doit tre nomm en fonction du fragment. Vous devez crer un fichier
integer_widget.html.php pour personnaliser le fragment integer_widget.
1
2
3
4
<!-- src/Acme/DemoBundle/Resources/views/Form/integer_widget.html.php -->
<div class="integer_widget">
<?php echo $view['form']->block($form, 'form_widget_simple', array('type' =>
isset($type) ? $type : "number")) ?>
</div>
Maintenant que vous avez cr le template de formulaire personnalis, vous devez dire Symfony de
l'utiliser. Dans le template o vous affichez votre formulaire, indiquez Symfony d'utiliser le thme via la
mthode setTheme :
1
2
3
<?php $view['form']->setTheme($form, array('AcmeDemoBundle:Form')) ;?>
<?php $view['form']->widget($form['age']) ?>
Pour afficher le widget form.age, Symfony utilisera le template personnalis integer_widget.html.php
et la balise input sera encadre par l'lment div.
Faire rfrence aux blocs par dfaut d'un formulaire (spcifique Twig)
Jusqu' prsent, pour surcharger un bloc de formulaire particulier, la meilleure mthode consistait
copier le bloc par dfaut depuis form_div_layout.html.twig
3
, le copier dans un nouveau template, et le
personnaliser. Dans la plupart des cas, vous pouvez viter cela en rfrenant le bloc de base lorsque vous
le personnalisez.
C'est facile faire, mais cela peut varier sensiblement si vos personnalisations de bloc sont dans le mme
template que le formulaire ou pas.
Faire rfrence aux blocs depuis le mme template que le formulaire
Importer les blocs en ajoutant la balise use dans le template o vous affichez le formulaire :
1 {% use 'form_div_layout.html.twig' with integer_widget as base_integer_widget %}
3. https://github.com/symfony/symfony/blob/2.1/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
PDF brought to you by
generated on June 26, 2014
Chapter 40: Comment personnaliser le rendu de formulaire | 132
Listing 40-15
Listing 40-16
Listing 40-17
A partir de maintenant, lorsque les blocs sont imports depuis form_div_layout.html.twig
4
, le bloc
integer_widget sera renomm en base_integer_widget. Cela signifie que lorsque vous redfinissez
le bloc integer_widget, vous pouvez faire rfrence l'implmentation par dfaut via
base_integer_widget :
1
2
3
4
5
{% block integer_widget %}
<div class="integer_widget">
{{ block('base_integer_widget') }}
</div>
{% endblock %}
Faire rfrence aux blocs par dfaut depuis un template externe
Si vos personnalisations de formulaire se trouvent dans un template externe, vous pouvez faire rfrence
au bloc par dfaut en utilisant la fonction Twig parent() :
1
2
3
4
5
6
7
8
{# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #}
{% extends 'form_div_layout.html.twig' %}
{% block integer_widget %}
<div class="integer_widget">
{{ parent() }}
</div>
{% endblock %}
Il n'est pas possible de faire rfrence au bloc par dfaut si vous utilisez PHP comme moteur de
template. Vous devrez alors copier manuellement le code du bloc par dfaut dans votre nouveau
fichier de template.
Faire des personnalisations au niveau de l'application
Si vous aimeriez qu'une personnalisation de formulaire soit globale votre application, vous pouvez faire
cela en ralisant cette personnalisation dans un template externe, et en l'important dans la configuration
de votre application :
Twig
En utilisant la configuration suivante, toute personnalisation de blocs de formulaire qui se trouve dans le
template AcmeDemoBundle:Form:fields.html.twig sera utilise quand un formulaire sera affich.
1
2
3
4
5
6
7
# app/config/config.yml
twig:
form:
resources:
- 'AcmeDemoBundle:Form:fields.html.twig'
# ...
4. https://github.com/symfony/symfony/blob/2.1/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
PDF brought to you by
generated on June 26, 2014
Chapter 40: Comment personnaliser le rendu de formulaire | 133
Listing 40-18
Listing 40-19
Listing 40-20
Listing 40-21
Listing 40-22
Par dfaut, Twig utilise un layout base de div pour afficher les formulaires. Cependant, certaines
personnes prfreront utiliser un layout base de tableau. Utilisez la ressource
form_table_layout.html.twig pour utiliser un tel layout :
1
2
3
4
5
# app/config/config.yml
twig:
form:
resources: ['form_table_layout.html.twig']
# ...
Si vous ne voulez appliquer ce changement que dans un seul template, ajoutez la ligne suivante dans votre
fichier de template plutt que d'ajouter le template comme ressource :
1 {% form_theme form 'form_table_layout.html.twig' %}
Notez que la variable form dans le code ci-dessus est la vue du formulaire que vous avez passe votre
template.
PHP
En utilisant la configuration suivante, tout fragment de formulaire personnalis se situant dans le
rpertoire src/Acme/DemoBundle/Resources/views/Form sera utilis lorsque le formulaire sera affich.
1
2
3
4
5
6
7
# app/config/config.yml
framework:
templating:
form:
resources:
- 'AcmeDemoBundle:Form'
# ...
Par dfaut, le moteur de template PHP utilise un layout base de div pour afficher les formulaires.
Nanmoins, certains prfreront afficher leurs formulaires dans un layout base de tableau. Utilisez la
ressource FrameworkBundle:FormTable pour utiliser un tel layout :
1
2
3
4
5
6
# app/config/config.yml
framework:
templating:
form:
resources:
- 'FrameworkBundle:FormTable'
Si vous ne voulez appliquer vos changements que dans un seul template, ajoutez la ligne suivante dans
votre fichier de template plutt que d'ajouter le template comme ressource :
1 <?php $view['form']->setTheme($form, array('FrameworkBundle:FormTable')); ?>
Notez que la variable form dans le code ci-dessus est la vue du formulaire que vous avez passe votre
template.
Comment personnaliser un champ individuel
Jusqu'ici, vous avez vu les diffrentes manires de personnaliser le widget gnr pour tous les types de
champ texte. Vous pouvez galement personnaliser un champ individuel. Par exemple, supposons que
PDF brought to you by
generated on June 26, 2014
Chapter 40: Comment personnaliser le rendu de formulaire | 134
Listing 40-23
Listing 40-24
vous ayez deux champs texte (nom et prnom) mais que vous vouliez seulement personnaliser l'un de
ces deux champs. Cela peut tre fait en personnalisant un fragment dont le nom est une combinaison de
l'attribut id du champ et de la partie du champ concern (libell, widget ou erreur). Par exemple :
1
2
3
4
5
6
7
8
9
{% form_theme form _self %}
{% block _product_name_widget %}
<div class="text_widget">
{{ block('form_widget_simple') }}
</div>
{% endblock %}
{{ form_widget(form.name) }}
Ici, le fragment _product_name_widget dfinit le template utiliser pour le widget du champ dont l'id
est product_name (et dont le nom est product[name]).
La partie product du champ est le nom du formulaire qui peut tre dfini manuellement ou
automatiquement gnr en se basant sur le nom du type de formulaire (par exemple :
ProductType donnera product). Si vous n'tes pas sr du nom de votre formulaire, regardez le
code source gnr de votre formulaire.
Vous pouvez aussi surcharger le code d'un champ entier en utilisant la mme mthode :
1
2
3
4
5
6
7
8
9
10
{# _product_name_row.html.twig #}
{% form_theme form _self %}
{% block _product_name_row %}
<div class="name_row">
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endblock %}
Autres personnalisations courantes
Jusqu' prsent, ce document vous a expliqu diffrentes manires de personnaliser une unique partie de
l'affichage d'un formulaire. L'ide est de personnaliser un fragment spcifique qui correspond une partie
du formulaire que vous voulez contrler (lisez nommer les blocs de formulaire).
Dans les sections suivantes, vous verrez comment effectuer plusieurs personnalisations de formulaires
communes. Pour appliquer ces personnalisations, utilisez l'une des mthodes dcrites dans la section
Thmes de formulaire.
Personnaliser l'affichage des erreurs
Le composant formulaire ne prend en charge que la manire dont les erreurs de validation sont
affiches, et non les messages d'erreurs eux-mmes. Les messages d'erreurs sont dtermins par
les contraintes de validation que vous appliquez vos objets. Pour plus d'informations, lisez le
chapitre sur la validation.
PDF brought to you by
generated on June 26, 2014
Chapter 40: Comment personnaliser le rendu de formulaire | 135
Listing 40-25
Listing 40-26
Listing 40-27
Listing 40-28
Il y a plusieurs manires diffrentes de personnaliser l'affichage des erreurs lorsqu'un formulaire soumis
n'est pas valide. Les messages d'erreur d'un champ sont affichs lorsque vous utilisez le helper
form_errors :
1 {{ form_errors(form.age) }}
Par dfaut, les erreurs sont affiches dans une liste non-ordonne :
1
2
3
<ul>
<li>Ce champ est obligatoire</li>
</ul>
Pour surcharger l'affichage des erreurs pour tous les champs, il vous suffit de copier, coller et
personnaliser le fragment form_errors.
{# fields_errors.html.twig #}
{% block form_errors %}
{% spaceless %}
{% if errors|length > 0 %}
<ul class="error_list">
{% for error in errors %}
<li>{{
error.messagePluralization is null
? error.messageTemplate|trans(error.messageParameters, 'validators')
: error.messageTemplate|transchoice(error.messagePluralization,
error.messageParameters, 'validators')
}}</li>
{% endfor %}
</ul>
{% endif %}
{% endspaceless %}
{% endblock form_errors %}
Lisez Thmes de formulaire pour savoir comment appliquer ces personnalisations.
Vous pouvez aussi personnaliser l'affichage des erreurs pour un seul type de champ spcifique. Par
exemple, certaines erreurs qui sont plus globales (c'est--dire qui ne sont pas spcifiques un seul champ)
sont affiches sparment, souvent en haut de votre formulaire :
1 {{ form_errors(form) }}
Pour personnaliser uniquement le rendu de ces erreurs, suivez les mmes directives que ci-dessus, sauf que
vous devez maintenant appeler le bloc form_errors (Twig) ou le fichier form_errors.html.php (PHP).
Maintenant, lorsque les erreurs du type form seront affiches, votre fragment personnalis sera utilis au
lieu du fragment par dfaut form_errors.
Personnaliser le Form Row
Quand vous pouvez le faire, la manire la plus simple d'afficher un champ de formulaire est la fonction
form_row, qui affiche le libell, les erreurs et le widget HTML d'un champ. Pour personnaliser le code
gnr utilis pour afficher tous les champs de formulaire, surchargez le fragment form_row. Par exemple,
supposons que vous vouliez ajouter une classe l'lment div qui entoure chaque bloc :
PDF brought to you by
generated on June 26, 2014
Chapter 40: Comment personnaliser le rendu de formulaire | 136
Listing 40-29
Listing 40-30
Listing 40-31
Listing 40-32
1
2
3
4
5
6
7
8
{# form_row.html.twig #}
{% block form_row %}
<div class="form_row">
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endblock form_row %}
Lisez Thmes de formulaire pour savoir comment appliquer cette personnalisation.
Ajouter une astrisque obligatoire sur les libells de champs
Si vous voulez marquer tous les champs obligatoires par une astrisque (*), vous pouvez le faire en
personnalisant le fragment form_label.
Avec Twig, si vous faites cette personnalisation dans le mme template que votre formulaire, modifiez le
tag use et ajoutez ce qui suit :
1
2
3
4
5
6
7
8
9
{% use 'form_div_layout.html.twig' with form_label as base_form_label %}
{% block form_label %}
{{ block('base_form_label') }}
{% if required %}
<span class="required" title="Ce champ est obligatoire">*</span>
{% endif %}
{% endblock %}
Avec Twig, si vous faites les changements dans un template spar, ajoutez ce qui suit :
1
2
3
4
5
6
7
8
9
{% extends 'form_div_layout.html.twig' %}
{% block form_label %}
{{ parent() }}
{% if required %}
<span class="required" title="Ce champ est obligatoire">*</span>
{% endif %}
{% endblock %}
Si vous utilisez PHP comme moteur de template, vous devrez copier le contenu depuis le template
original :
1
2
3
4
5
6
7
8
<!-- form_label.html.php -->
<!-- contenu original -->
<?php if ($required) { $label_attr['class'] = trim((isset($label_attr['class']) ?
$label_attr['class'] : '').' required'); } ?>
<?php if (!$compound) { $label_attr['for'] = $id; } ?>
<?php if (!$label) { $label = $view['form']->humanize($name); } ?>
<label <?php foreach ($label_attr as $k => $v) { printf('%s="%s" ', $view->escape($k),
PDF brought to you by
generated on June 26, 2014
Chapter 40: Comment personnaliser le rendu de formulaire | 137
Listing 40-33
Listing 40-34
Listing 40-35
9
10
11
12
13
$view->escape($v)); } ?>><?php echo $view->escape($view['translator']->trans($label,
array(), $translation_domain)) ?></label>
<!-- personnalisation -->
<?php if ($required) : ?>
<span class="required" title="Ce champ est obligatoire">*</span>
<?php endif ?>
Lisez Thmes de formulaire pour savoir comment appliquer cette personnalisation.
Ajouter des messages d' aide
Vous pouvez aussi personnaliser vos widgets de formulaire avec un message d' aide ( help en anglais)
facultatif.
Avec Twig, si vous faites les changements dans le mme template que votre formulaire, modifiez le tag
use et ajoutez ce qui suit :
1
2
3
4
5
6
7
8
9
{% use 'form_div_layout.html.twig' with form_widget_simple as base_form_widget_simple %}
{% block form_widget_simple %}
{{ block('base_form_widget_simple') }}
{% if help is defined %}
<span class="help">{{ help }}</span>
{% endif %}
{% endblock %}
Avec Twig, si vous faites les changements dans un template spar, procdez comme suit :
1
2
3
4
5
6
7
8
9
{% extends 'form_div_layout.html.twig' %}
{% block form_widget_simple %}
{{ parent() }}
{% if help is defined %}
<span class="help">{{ help }}</span>
{% endif %}
{% endblock %}
Si vous utilisez PHP comme moteur de template, vous devrez copier le contenu depuis le template
original :
1
2
3
4
5
6
7
8
<!-- form_widget_simple.html.php -->
<!-- Contenu original -->
<input
type="<?php echo isset($type) ? $view->escape($type) : 'text' ?>"
<?php if (!empty($value)): ?>value="<?php echo $view->escape($value) ?>"<?php endif ?>
<?php echo $view['form']->block($form, 'widget_attributes') ?>
/>
PDF brought to you by
generated on June 26, 2014
Chapter 40: Comment personnaliser le rendu de formulaire | 138
Listing 40-36
Listing 40-37
9
10
11
12
13
<!-- Personnalisation -->
<?php if (isset($help)) : ?>
<span class="help"><?php echo $view->escape($help) ?></span>
<?php endif ?>
Pour afficher un message d'aide en dessous du champ, passez le dans une variable help :
1 {{ form_widget(form.title, {'help': 'foobar'}) }}
Lisez Thmes de formulaire pour savoir comment appliquer ces personnalisations.
Utiliser les Variables
La plupart des fonctions disponibles pour afficher les diffrents parties d'un formulaire (ex widget, label,
erreurs, etc) vous permettent galement de raliser certaines personnalisations directement. Jetez un oeil
l'exemple suivant :
1
2
{# affiche un widget, mais y ajoute la classe "foo" #}
{{ form_widget(form.name, { 'attr': {'class': 'foo'} }) }}
Le tableau pass comme second argument contient des variables de formulaire. Pour plus de dtails
sur ce concept de Twig, lisez Un peu plus sur les Variables de formulaire.
PDF brought to you by
generated on June 26, 2014
Chapter 40: Comment personnaliser le rendu de formulaire | 139
Listing 41-1
Chapter 41
Comment utiliser les Convertisseurs de
Donnes
Vous allez souvent prouver le besoin de transformer les donnes entres par l'utilisateur dans un
formulaire en quelque chose d'autre qui sera utilis dans votre programme. Vous pourriez effectuer
ceci manuellement dans votre contrleur, mais que se passe-t-il si vous voulez rutiliser ce formulaire
spcifique diffrents endroits ?
Supposons que vous ayez une relation one-to-one de Task ( Tche en franais) vers Issue (
Problme en franais), par exemple : une tche possde de manire optionnelle un problme qui lui est
li. Ajouter un lment listbox contenant tous les problmes possibles peut ventuellement engendrer
une trs longue liste dans laquelle il est impossible de trouver quoi que ce soit. Vous pourriez vouloir
utiliser plutt un champ texte dans lequel l'utilisateur pourra simplement taper le numro du problme
recherch.
Vous pouvez essayer de faire cela dans votre contrleur mais ce n'est pas la meilleure solution. Ce
serait beaucoup mieux si votre problme pouvait automatiquement tre converti en un objet Issue. C'est
maintenant que le Convertisseur de Donnes entre en jeu.
Crer le convertisseur (Transformer)
Crez d'abord la classe IssueToNumberTransformer, elle sera charge de convertir un numro de
problme en un objet Issue et inversement:
1
2
3
4
5
6
7
8
9
10
// src/Acme/TaskBundle/Form/DataTransformer/IssueToNumberTransformer.php
namespace Acme\TaskBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Doctrine\Common\Persistence\ObjectManager;
use Acme\TaskBundle\Entity\Issue;
class IssueToNumberTransformer implements DataTransformerInterface
{
PDF brought to you by
generated on June 26, 2014
Chapter 41: Comment utiliser les Convertisseurs de Donnes | 140
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/**
* @var ObjectManager
*/
private $om;
/**
* @param ObjectManager $om
*/
public function __construct(ObjectManager $om)
{
$this->om = $om;
}
/**
* Transforms an object (issue) to a string (number).
*
* @param Issue|null $issue
* @return string
*/
public function transform($issue)
{
if (null === $issue) {
return "";
}
return $issue->getNumber();
}
/**
* Transforms a string (number) to an object (issue).
*
* @param string $number
* @return Issue|null
* @throws TransformationFailedException if object (issue) is not found.
*/
public function reverseTransform($number)
{
if (!$number) {
return null;
}
$issue = $this->om
->getRepository('AcmeTaskBundle:Issue')
->findOneBy(array('number' => $number))
;
if (null === $issue) {
throw new TransformationFailedException(sprintf(
'Le problme avec le numro "%s" ne peut pas tre trouv!',
$number
));
}
return $issue;
}
}
PDF brought to you by
generated on June 26, 2014
Chapter 41: Comment utiliser les Convertisseurs de Donnes | 141
Listing 41-2
Si vous voulez qu'un nouveau problme soit cr quand un numro inconnu est saisi, vous pouvez
l'instancier plutt que de lancer l'exception TransformationFailedException.
Utiliser le Convertisseur
Maintenant que le convertisseur est construit, il vous suffit juste de l'ajouter votre champ Issue dans un
formulaire.
Vous pouvez galement utiliser les convertisseurs sans crer de nouveau type de champ de
formulaire personnalis en appelant addModelTransformer (ou addViewTransformer, lisez
Convertisseurs de modle et de vue) sur n'importe quel constructeur de champ:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
use Symfony\Component\Form\FormBuilderInterface;
use Acme\TaskBundle\Form\DataTransformer\IssueToNumberTransformer;
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
// cela suppose que le gestionnaire d'entit a t pass en option
$entityManager = $options['em'];
$transformer = new IssueToNumberTransformer($entityManager);
// ajoute un champ texte normal, mais y ajoute aussi votre convertisseur
$builder->add(
$builder->create('issue', 'text')
->addModelTransformer($transformer)
);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\TaskBundle\Entity\Task',
));
$resolver->setRequired(array(
'em',
));
$resolver->setAllowedTypes(array(
'em' => 'Doctrine\Common\Persistence\ObjectManager',
));
// ...
}
// ...
}
PDF brought to you by
generated on June 26, 2014
Chapter 41: Comment utiliser les Convertisseurs de Donnes | 142
Listing 41-3
Listing 41-4
Cet exemple requiert que vous ayez pass le gestionnaire d'entit en option lors de la cration du
formulaire. Plus tard, vous apprendrez comment vous pourriez crer un type de champ issue
personnalis pour viter de devoir faire cela dans votre contrleur:
1
2
3
$taskForm = $this->createForm(new TaskType(), $task, array(
'em' => $this->getDoctrine()->getManager(),
));
Cool, vous avez fini ! Votre utilisateur sera maintenant capable de saisir un numro de problme dans
le champ texte, et il sera converti en un object Issue qui reprsente ce problme. Cela signifie que, aprs
avoir russi associer (bind) les donnes, le framework Formulaire passera un vritable objet Issue la
mthode Task::setIssue() plutt que son numro.
Si le problme ne peut pas tre retrouv partir de son numro, une erreur sera cre pour ce champ et
le message de cette erreur peut tre contrl grce l'option de champ invalid_message.
Veuillez noter qu'ajouter le convertisseur requiert une syntaxe un peu plus complexe lorsque
vous ajoutez un champ. L'exemple suivant est incorrect car le convertisseur serait appliqu au
formulaire entier au lieu d'tre juste appliqu au champ:
1
2
3
4
// C'EST INCORRECT, LE CONVERTISSEUR SERA APPLIQUE A TOUT LE FORMULAIRE
// regardez l'exemple ci-dessus pour connatre la bonne syntaxe
$builder->add('issue', 'text')
->addModelTransformer($transformer);
Convertisseurs de modle et de vue
New in version 2.1: Les noms et mthodes des convertisseurs ont t changs dans Symfony 2.1.
prependNormTransformer devient addModelTransformer et appendClientTransformer devient
addViewTransformer.
Dans l'exemple ci-dessus, le convertisseur a t utilis comme convertisseur de modle . En fait, il y a
deux types diffrents de convertisseurs, et trois types diffrents de donnes.
Dans tout formulaire, les trois types de donnes sont :
1) Donnes modle - C'est une donne dans le format utilis dans votre application (ex, un objet Issue).
Si vous appelez Form::getData ou Form::setData, vous traitez avec la donne modle .
2) Donnes normalise - C'est la version normalise de votre donne, et c'est bien souvent la mme que
votre donne modle (mais pas dans cet exemple). Elle n'est en gnral pas utilise directement.
3) Donne vue - C'est le format qui est utilis pour remplir les champs eux-mmes. C'est aussi le format
dans lequel l'utilisateur soumettra ses donnes. Quand vous appelez les mthodes Form::bind($data),
la variable $data est dans le format de donnes vue .
Les deux diffrents types de convertisseurs vous aident faire des conversions entre ces types de donnes
:
Convertisseurs modle:
transform: donne modle => donne normalise
reverseTransform: donne normalise => donne modle
Convertisseurs vue:
transform: donne normalise => donne vue
reverseTransform: donne vue => donne normalise
PDF brought to you by
generated on June 26, 2014
Chapter 41: Comment utiliser les Convertisseurs de Donnes | 143
Listing 41-5
Le convertisseur que vous utiliserez dpendra de votre situation.
Pour utiliser le convertisseur vue, appelez addViewTransformer.
Alors pourquoi utiliser le convertisseur modle ?
Dans cet exemple, le champ est un champ texte, et un champ texte devrait toujours tre dans un format
simple, scalaire dans l'un des formats normalis ou vue . Pour cette raison, le convertisseur le plus
appropri tait le convertisseur modle (qui convertit un format normalis, le numro du problme,
en un format modle, l'objet Issue, et inversement).
La diffrence entre les convertisseurs est subtile et vous devriez toujours penser ce que la donne
normalise d'un champ devrait tre. Par exemple, la donne normalise d'un champ texte est une
chane de caractres, mais c'est un objet DateTime pour un champ date.
Utiliser les convertisseurs dans un type de champ personnalis
Dans l'exemple ci-dessus, vous aviez appliqu le convertisseur sur un champ texte normal. C'tait facile
mais cela a deux inconvnients :
1) Vous devez toujours vous souvenir d'appliquer le convertisseur lorsque vous ajoutez des champs pour
saisir des numros de problme
2) Vous devez vous soucier de passer l'option em quand vous crez le formulaire qui utilise le
convertisseur.
Pour ces raisons, vous pourriez choisir de crer un type de champ personnalis. Premirement, crez la
classe du type de champ personnalis:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// src/Acme/TaskBundle/Form/Type/IssueSelectorType.php
namespace Acme\TaskBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Acme\TaskBundle\Form\DataTransformer\IssueToNumberTransformer;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class IssueSelectorType extends AbstractType
{
/**
* @var ObjectManager
*/
private $om;
/**
* @param ObjectManager $om
*/
public function __construct(ObjectManager $om)
{
$this->om = $om;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new IssueToNumberTransformer($this->om);
$builder->addModelTransformer($transformer);
PDF brought to you by
generated on June 26, 2014
Chapter 41: Comment utiliser les Convertisseurs de Donnes | 144
Listing 41-6
Listing 41-7
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'invalid_message' => 'The selected issue does not exist',
));
}
public function getParent()
{
return 'text';
}
public function getName()
{
return 'issue_selector';
}
}
Ensuite, enregistrez votre type comme service et taggez le avec form.type pour qu'il soit reconnu comme
type de champ personnalis :
1
2
3
4
5
6
services:
acme_demo.type.issue_selector:
class: Acme\TaskBundle\Form\Type\IssueSelectorType
arguments: ["@doctrine.orm.entity_manager"]
tags:
- { name: form.type, alias: issue_selector }
Maintenant, lorsque vous aurez besoin d'utiliser votre type de champ spcial issue_selector, ce sera
trs facile:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/Acme/TaskBundle/Form/Type/TaskType.php
namespace Acme\TaskBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('task')
->add('dueDate', null, array('widget' => 'single_text'));
->add('issue', 'issue_selector');
}
public function getName()
{
return 'task';
}
}
PDF brought to you by
generated on June 26, 2014
Chapter 41: Comment utiliser les Convertisseurs de Donnes | 145
Listing 42-1
Chapter 42
Comment modifier dynamiquement les
formulaires en utilisant les vnements
Souvent, un formulaire ne pourra pas tre cr de faon statique. Dans cet article, vous apprendrez
personnaliser vos formulaires selon trois cas courants:
1. Personnaliser un formulaire en se basant sur les donnes
Exemple: vous avez un formulaire "Product" et vous devez modifier/ajouter/supprimer un champ selon
les donnes de l 'objet avec lequel vous travaillez.
2. Comment grer des formulaires en se basant sur les donnes utilisateur
Exemple: vous crez un formulaire "Message" et vous devez construire une liste droulante qui ne
contient que les utilisateurs qui sont amis avec l'utilisateur actuellement authentifi.
3. Gnration dynamique pour formulaire soumis
Exemple: dans un formulaire d'inscription, vous avez un champ "pays" et un champ "tat" que vous
voulez alimenter selon la valeur du champ "pays".
Personnaliser un formulaire en se basant sur les donnes
Avant de se lancer directement dans la gnration dynamique de formulaire, commenons par rappeler
ce quoi ressemble une classe de formulaire basique:
1
2
3
4
5
6
7
8
9
10
11
12
// src/Acme/DemoBundle/Form/Type/ProductType.php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name');
PDF brought to you by
generated on June 26, 2014
Chapter 42: Comment modifier dynamiquement les formulaires en utilisant les vnements | 146
Listing 42-2
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$builder->add('price');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\DemoBundle\Entity\Product'
));
}
public function getName()
{
return 'product';
}
}
Si la section de code ci-dessus ne vous est pas familire, vous avez probablement besoin de lire
d'abord le chapitre sur les Formulaires avant d'aller plus loin.
Supposons un instant que ce formulaire utilise une classe imaginaire Product qui possde uniquement
deux proprits ( name et price ). Le formulaire gnr partir de cette classe sera identique que
ce soit pour la cration d'un Produit ou pour l'dition d'un Produit existant (par exemple : un Produit
rcupr depuis la base de donnes).
Supposons maintenant que vous ne souhaitiez pas que l'utilisateur puisse changer la valeur de name une
fois que l'objet a t cr. Pour faire cela, vous pouvez utiliser le Rpartiteur d'vnements de Symfony
pour analyser les donnes de l'objet et modifier le formulaire en se basant sur les donnes de l'objet
Product. Dans cet article, vous allez apprendre comment ajouter ce niveau de flexibilit vos formulaires.
Ajouter un couteur d'vnement une classe de formulaire
Donc, au lieu d'ajouter directement ce widget name`, la responsabilit de crer ce champ en particulier
est laisse un couteur d'vnement:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/Acme/DemoBundle/Form/Type/ProductType.php
namespace Acme\DemoBundle\Form\Type;
// ...
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('price');
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) {
// ... adding the name field if needed
});
}
// ...
}
PDF brought to you by
generated on June 26, 2014
Chapter 42: Comment modifier dynamiquement les formulaires en utilisant les vnements | 147
Listing 42-3
Listing 42-4
Le but est de crer un champ name uniquement si l'objet Product sous-jacent est nouveau (par exemple
: n'a pas t persist dans la base de donnes). Partant de ce principe, l'couteur d'vnement pourrait
ressembler quelque chose comme a:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ...
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event){
$product = $event->getData();
$form = $event->getForm();
// vrifie si l'objet Product est "nouveau"
// Si aucune donne n'est passe au formulaire, la donne est "null".
// Ce doit tre considr comme un nouveau "Product"
if (!$product || null === $product->getId()) {
$form->add('name', 'text');
}
});
}
New in version 2.2: La possibilit de passer une chaine de caractres FormInterface::add
1
est
une nouveaut de Symfony 2.2.
Vous pouvez bien sr utiliser n'importe quel type de callback au lieu d'une closure, par exemple
une mthode de l'objet ProductType` pour une meilleur lisibilit:
1
2
3
4
5
6
7
8
9
10
11
12
13
// ...
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
$builder->addEventListener(FormEvents::PRE_SET_DATA, array($this,
'onPreSetData'));
}
public function onPreSetData(FormEvent $event){
// ...
}
}
La ligne FormEvents::PRE_SET_DATA est convertie en form.pre_set_data. La classe FormEvents
2
a un but organisationnel. C'est un endroit centralis o vous trouverez la liste des diffrents
vnements de formulaire disponibles. Vous pouvez voir la liste complte des vnement de
formulaire dans la classe FormEvents
3
.
1. http://api.symfony.com/2.3/Symfony/Component/Form/FormInterface.html#add()
2. http://api.symfony.com/2.3/Symfony/Component/Form/FormEvents.html
3. http://api.symfony.com/2.3/Symfony/Component/Form/FormEvents.html
PDF brought to you by
generated on June 26, 2014
Chapter 42: Comment modifier dynamiquement les formulaires en utilisant les vnements | 148
Listing 42-5
Listing 42-6
Ajouter un souscripteur d'vnement sur un formulaire
Pour une meilleur rutilisabilit ou s'il y a trop de logique dans votre couteur d'vnement, vous pouvez
galement dplacer la logique qui cre le champ name dans un souscripteur d'vnements:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/Acme/DemoBundle/Form/Type/ProductType.php
namespace Acme\DemoBundle\Form\Type;
// ...
use Acme\DemoBundle\Form\EventListener\AddNameFieldSubscriber;
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('price');
$builder->addEventSubscriber(new AddNameFieldSubscriber());
}
// ...
}
Maintenant, la logique qui cre le champ name est situe dans sa propre classe de souscripteur:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// src/Acme/DemoBundle/Form/EventListener/AddNameFieldSubscriber.php
namespace Acme\DemoBundle\Form\EventListener;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class AddNameFieldSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
// Dit au dispatcher que vous voulez couter l'vnement
// form.pre_set_data et que la mthode preSetData doit tre appele
return array(FormEvents::PRE_SET_DATA => 'preSetData');
}
public function preSetData(FormEvent $event)
{
$product = $event->getData();
$form = $event->getForm();
if (!$product || null === $product->getId()) {
$form->add('name', 'text');
}
}
}
Comment grer des formulaires en se basant sur les donnes utilisateur
Parfois, vous voulez qu'un formulaire soit gnr dynamiquement en se basant sur les donnes du
formulaire mais aussi sur quelque chose d'autre - par exemple des donnes de l'utilisateur connect.
Supposons que vous travaillez sur un site social o un utilisateur ne peut envoyer des messages qu' des
PDF brought to you by
generated on June 26, 2014
Chapter 42: Comment modifier dynamiquement les formulaires en utilisant les vnements | 149
Listing 42-7
Listing 42-8
personnes marques comme amies sur ce site. Dans ce cas, la liste des personnes qui l'utilisateur peut
envoyer des messages ne doit contenir que des amis.
Crer le type de formulaire
En utilisant un couteur d'vnements, votre formulaire pourrait ressembler ceci:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// src/Acme/DemoBundle/Form/Type/FriendMessageFormType.php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class FriendMessageFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('subject', 'text')
->add('body', 'textarea')
;
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event){
// ... Ajouter une liste de choix d'amis de l'utilisateur connect
});
}
public function getName()
{
return 'acme_friend_message';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
}
}
Le problme est maintenant de rcuprer l'utilisateur actuel et de crer un champ select qui ne contient
que des amis de l'utilisateur.
Heureusement, il est assez facile d'injecter un service dans le formulaire. Cela peut tre fait dans le
constructeur:
1
2
3
4
5
6
private $securityContext;
public function __construct(SecurityContext $securityContext)
{
$this->securityContext = $securityContext;
}
PDF brought to you by
generated on June 26, 2014
Chapter 42: Comment modifier dynamiquement les formulaires en utilisant les vnements | 150
Listing 42-9
Vous devez vous demander, maintenant que vous avez accs l'utilisateur (au travers du security
context), pourquoi ne pas utiliser directement buildForm et viter de passer par un couteur
d'vnement? C'est parce que le faire dans la mthode buildForm signifierait que tout le type
de formulaire sera modifi et non pas juste l'instance de formulaire que vous voulez. Ce n'est
gnralement pas trs grave, mais techniquement, un seul type de formulaire pourrait tre utilis
dans une seule requte pour crer plusieurs formulaires ou champs
Personnaliser le type de formulaire
Maintenant que vous avez toutes les bases, vous pouvez tirer avantage du SecurityContext et remplir
votre couteur:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// src/Acme/DemoBundle/FormType/FriendMessageFormType.php
use Symfony\Component\Security\Core\SecurityContext;
use Doctrine\ORM\EntityRepository;
// ...
class FriendMessageFormType extends AbstractType
{
private $securityContext;
public function __construct(SecurityContext $securityContext)
{
$this->securityContext = $securityContext;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('subject', 'text')
->add('body', 'textarea')
;
// rcupre le user et vrifie rapidement qu'il existe bien
$user = $this->securityContext->getToken()->getUser();
if (!$user) {
throw new \LogicException(
'Le FriendMessageFormType ne peut pas tre utilis sans utilisateur
connect!'
);
}
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function(FormEvent $event) use ($user) {
$form = $event->getForm();
$formOptions = array(
'class' => 'Acme\DemoBundle\Entity\User',
'property' => 'fullName',
'query_builder' => function(EntityRepository $er) use ($user) {
// construit une requte personnalise
// retourne $er->createQueryBuilder('u')->addOrderBy('fullName',
'DESC');
// ou appelle une mthode d'un repository qui retourne un query
PDF brought to you by
generated on June 26, 2014
Chapter 42: Comment modifier dynamiquement les formulaires en utilisant les vnements | 151
Listing 42-10
Listing 42-11
46
47
48
49
50
51
52
53
54
55
56
57
builder
// l'instance $er est une instance de UserRepository
// retourne $er->createOrderByFullNameQueryBuilder();
},
);
// cre le champ, cela quivaut $builder->add()
// nom du champ, type de champ, donne, options
$form->add('friend', 'entity', $formOptions);
}
);
}
// ...
}
Les options multiple et expanded valent false par dfaut car le type de champ est entity.
Utiliser le formulaire
Votre formulaire est maintenant prt tre utilis et il y a deux manires possibles de l'utiliser dans un
contrleur :
1. le crer manuellement et y passer le security context;
ou
2. le dfinir comme service.
a) Crer le formulaire manuellement
C'est trs simple, et probablement la meilleur approche moins que vous n'utilisiez votre nouveau type
de champ plusieurs endroits ou que vous l'imbriquez dans d'autres formulaires:
1
2
3
4
5
6
7
8
9
10
11
12
class FriendMessageController extends Controller
{
public function newAction(Request $request)
{
$securityContext = $this->container->get('security.context');
$form = $this->createForm(
new FriendMessageFormType($securityContext)
);
// ...
}
}
b) Definir le formulaire comme service
Pour dfinir votre formulaire comme service, crez simplement un service classique que vous taggerez
avec form.type.
1
2
# app/config/config.yml
services:
PDF brought to you by
generated on June 26, 2014
Chapter 42: Comment modifier dynamiquement les formulaires en utilisant les vnements | 152
Listing 42-12
Listing 42-13
Listing 42-14
Listing 42-15
3
4
5
6
7
acme.form.friend_message:
class: Acme\DemoBundle\Form\Type\FriendMessageFormType
arguments: ["@security.context"]
tags:
- { name: form.type, alias: acme_friend_message }
Si vous dsirez le crer dans un contrleur ou dans n'importe quel autre service qui a accs la formfactory,
alors faites:
1
2
3
4
5
6
7
8
9
10
11
use Symfony\Component\DependencyInjection\ContainerAware;
class FriendMessageController extends ContainerAware
{
public function newAction(Request $request)
{
$form = $this->get('form.factory')->create('acme_friend_message');
// ...
}
}
Si vous tendez la classe Symfony\Bundle\FrameworkBundle\Controller\Controller, appelez
simplement:
1 $form = $this->createForm('acme_friend_message');
Vous pouvez galement l'imbriquer facilement dans un autre formulaire:
1
2
3
4
5
// dans une classe "form type"
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('message', 'acme_friend_message');
}
Gnration dynamique pour formulaire soumis
Un autre cas qui peut survenir est que vous voulez personnaliser votre formulaire selon des donnes
soumises par l'utilisateur. Par exemple, imaginez que vous ayez un formulaire d'inscription pour des
rassemblements sportifs. Certains vnements vous permettront de spcifier votre position prfre sur
le terrain. Ce serait par exemple une liste de choix. Cependant, les choix possibles dpendront de chaque
sport. Le football aura des attaquants, des dfenseurs, un gardien, ... mais le baseball aura un lanceur et
pas de gardien. Vous devrez corriger les options pour que la validation soit bonne.
Le rassemblement est au formulaire comme une entit. Nous pouvons donc accder chaque sport
comme ceci:
1
2
3
4
5
6
7
// src/Acme/DemoBundle/Form/Type/SportMeetupType.php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
// ...
PDF brought to you by
generated on June 26, 2014
Chapter 42: Comment modifier dynamiquement les formulaires en utilisant les vnements | 153
Listing 42-16
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class SportMeetupType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('sport', 'entity', array(...))
;
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function(FormEvent $event) {
$form = $event->getForm();
// ce sera votre entit, c-a-d SportMeetup
$data = $event->getData();
$positions = $data->getSport()->getAvailablePositions();
$form->add('position', 'entity', array('choices' => $positions));
}
);
}
}
Lorsque vous construisez ce formulaire pour l'afficher l'utilisateur la premire fois, alors cet exemple
fonctionne parfaitement.
Cependant, les choses se compliqueront lorsque vous grerez la soumission. Ceci est d au fait que
l'vnement PRE_SET_DATA nous donne la donne avec laquelle vous commencez (un objet SportMeetup
vide), et non pas la donnes soumise.
Dans un formulaire, vous pouvez couter les vnements suivants:
PRE_SET_DATA
POST_SET_DATA
PRE_SUBMIT
SUBMIT
POST_SUBMIT
New in version 2.3: Les vnements PRE_SUBMIT, SUBMIT et POST_SUBMIT ont t ajouts dans
Symfony 2.3. Avant, ils taient nomms PRE_BIND, BIND et POST_BIND.
New in version 2.2.6: Le comportement de l'vnement POST_SUBMIT a chang dans la version
2.2.6. Ci-dessous, un exemple d'utilisation.
La solution est d'ajouter un couteur POST_SUBMIT sur le champ dont votre nouveau champ dpend. Si
vous ajoutez un couteur POST_SUBMIT sur un champ enfant (ex sport), et ajoutez un nouvel enfant au
formulaire parent, le composant Form dtectera automatiquement le nouveau champ et lui associera les
donnes soumises par le client.
Le type de formulaire ressemble maintenant ceci:
PDF brought to you by
generated on June 26, 2014
Chapter 42: Comment modifier dynamiquement les formulaires en utilisant les vnements | 154
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// src/Acme/DemoBundle/Form/Type/SportMeetupType.php
namespace Acme\DemoBundle\Form\Type;
// ...
use Acme\DemoBundle\Entity\Sport;
use Symfony\Component\Form\FormInterface;
class SportMeetupType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('sport', 'entity', array(...))
;
$formModifier = function(FormInterface $form, Sport $sport) {
$positions = $sport->getAvailablePositions();
$form->add('position', 'entity', array('choices' => $positions));
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function(FormEvent $event) use ($formModifier) {
// ce sera votre entit, c-a-d SportMeetup
$data = $event->getData();
$formModifier($event->getForm(), $data->getSport());
}
);
$builder->get('sport')->addEventListener(
FormEvents::POST_SUBMIT,
function(FormEvent $event) use ($formModifier) {
// Il est important de rcuprer ici $event->getForm()->getData(),
// car $event->getData() vous renverra la donnes initiale (vide)
$sport = $event->getForm()->getData();
// puisque nous avons ajout l'couteur l'enfant, il faudra passer
// le parent aux fonctions de callback!
$formModifier($event->getForm()->getParent(), $sport);
}
);
}
}
Vous pouvez constater que vous devez couter ces deux vnements et avoir diffrentes fonctions de
callback juste parce que dans deux scnarios diffrents, les donnes que vous pouvez utiliser sont
disponibles dans diffrents vnements. A part cela, les couteurs ralisent exactement la mme chose
dans un formulaire donn.
Mais il manque encore la mise jour du formulaire ct client aprs que la slection du sport a t faite.
Cela devrait tre fait grce un appel AJAX dans votre application. Dans ce controller, vous pourrez
soumettre votre formulaire, mais au lieu de le traiter, simplement utiliser le formulaire soumis pour
afficher les champs mis jour. La rponse de l'appel AJAX pourra alors tre utilise pour mettre jour la
vue.
PDF brought to you by
generated on June 26, 2014
Chapter 42: Comment modifier dynamiquement les formulaires en utilisant les vnements | 155
Listing 42-17
Supprimer la validation de formulaire
Pour supprimer la validation, vous pouvez utiliser l'vnement POST_SUBMIT et empcher le
ValidationListener
4
d'tre appel.
Vous pouvez tre amen faire cela si vous dfinissez group_validation false car, mme dans ce cas,
certaines vrifications sont tout de mme faites. Par exemple, un fichier upload sera quand mme vrifi
pour voir s'il est trop volumineux, et un formulaire vrifiera galement si des champs supplmentaires
ont t soumis. Pour dsactiver tout cela, utilisez un couteur:
1
2
3
4
5
6
7
8
9
10
11
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvents;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::POST_SUBMIT, function($event) {
$event->stopPropagation();
}, 900); // Dfinissez toujours une priorit plus grande que le ValidationListener
// ...
}
En faisant cela, vous risquez de dsactiver plus que la validation du formulaire, puisque
POST_SUBMIT peut avoir d'autres couteurs.
4. http://api.symfony.com/2.3/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.html
PDF brought to you by
generated on June 26, 2014
Chapter 42: Comment modifier dynamiquement les formulaires en utilisant les vnements | 156
Listing 43-1
Chapter 43
Comment imbriquer une Collection de
Formulaires
Dans cet article, vous allez apprendre crer un formulaire qui imbrique une collection de plusieurs
autres formulaires. Cela pourrait tre utile, par exemple, si vous avez une classe Task et que vous voulez
diter/crer/supprimer diffrents objets Tag lis cette Task , tout cela dans le mme formulaire.
Dans cet article, nous supposons que vous utilisez Doctrine en tant qu'outil de stockage pour
votre base de donnes. Mais si vous n'utilisez pas Doctrine (par exemple : Propel ou une simple
connexion la base de donnes), tout reste trs similaire. Seules quelques sections de ce tutoriel
s'occupent de la persistence .
Si vous utilisez Doctrine, vous aurez besoin d'ajouter les mta-donnes de Doctrine, incluant la
relation ManyToMany sur la proprit tags de Task.
Pour commencer, supposons que chaque Task appartienne plusieurs objets Tags. Crons tout d'abord
une simple classe Task:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/Acme/TaskBundle/Entity/Task.php
namespace Acme\TaskBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
class Task
{
protected $description;
protected $tags;
public function __construct()
{
$this->tags = new ArrayCollection();
}
PDF brought to you by
generated on June 26, 2014
Chapter 43: Comment imbriquer une Collection de Formulaires | 157
Listing 43-2
Listing 43-3
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public function getDescription()
{
return $this->description;
}
public function setDescription($description)
{
$this->description = $description;
}
public function getTags()
{
return $this->tags;
}
public function setTags(ArrayCollection $tags)
{
$this->tags = $tags;
}
}
ArrayCollection est spcifique Doctrine et revient utiliser un array (mais cela doit tre une
ArrayCollection si vous utilisez Doctrine).
Maintenant, crez une classe Tag. Comme vous l'avez vu ci-dessus, une Task peut avoir plusieurs objets
Tag:
1
2
3
4
5
6
7
// src/Acme/TaskBundle/Entity/Tag.php
namespace Acme\TaskBundle\Entity;
class Tag
{
public $name;
}
La proprit name est public ici, mais elle pourrait tout aussi bien tre protected ou private
(ce qui impliquerait d'avoir des mthodes getName et setName).
Venons-en maintenant aux formulaires. Crez une classe formulaire afin qu'un objet Tag puisse tre
modifi par l'utilisateur:
1
2
3
4
5
6
7
8
9
10
// src/Acme/TaskBundle/Form/Type/TagType.php
namespace Acme\TaskBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class TagType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
PDF brought to you by
generated on June 26, 2014
Chapter 43: Comment imbriquer une Collection de Formulaires | 158
Listing 43-4
Listing 43-5
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
$builder->add('name');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\TaskBundle\Entity\Tag',
));
}
public function getName()
{
return 'tag';
}
}
Avec cela, vous avez tout ce qu'il faut pour afficher un formulaire pour le tag lui-mme. Mais comme le
but final est de permettre la modification des tags d'une Task directement depuis le formulaire de la
task lui-mme, crez un formulaire pour la classe Task.
Notez que vous imbriquez une collection de formulaires TagType en utilisant le type de champ collection:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// src/Acme/TaskBundle/Form/Type/TaskType.php
namespace Acme\TaskBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('description');
$builder->add('tags', 'collection', array('type' => new TagType()));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\TaskBundle\Entity\Task',
));
}
public function getName()
{
return 'task';
}
}
Dans votre contrleur, vous allez maintenant initialiser une nouvelle instance de TaskType:
1
2
3
4
// src/Acme/TaskBundle/Controller/TaskController.php
namespace Acme\TaskBundle\Controller;
use Acme\TaskBundle\Entity\Task;
PDF brought to you by
generated on June 26, 2014
Chapter 43: Comment imbriquer une Collection de Formulaires | 159
Listing 43-6
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
use Acme\TaskBundle\Entity\Tag;
use Acme\TaskBundle\Form\Type\TaskType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class TaskController extends Controller
{
public function newAction(Request $request)
{
$task = new Task();
// code de test - le code ci-dessous est simplement l pour que la
// Task ait quelques tags, sinon, l'exemple ne serait pas intressant
$tag1 = new Tag();
$tag1->name = 'tag1';
$task->getTags()->add($tag1);
$tag2 = new Tag();
$tag2->name = 'tag2';
$task->getTags()->add($tag2);
// fin du code de test
$form = $this->createForm(new TaskType(), $task);
// analyse le formulaire quand on reoit une requte POST
if ($request->isMethod('POST')) {
$form->bind($request);
if ($form->isValid()) {
// ici vous pouvez par exemple sauvegarder la Task et ses objets Tag
}
}
return $this->render('AcmeTaskBundle:Task:new.html.twig', array(
'form' => $form->createView(),
));
}
}
Le template correspondant est maintenant capable d'afficher le champ description pour le formulaire
de la tche ainsi que les formulaires TagType pour n'importe quels tags qui sont lis cet objet Task. Dans
le contrleur ci-dessus, j'ai ajout du code de test afin que vous puissiez voir cela en action (puisqu'un
objet Task possde zro tag lorsqu'il est cr pour la premire fois).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{# src/Acme/TaskBundle/Resources/views/Task/new.html.twig #}
{# ... #}
<form action="..." method="POST" {{ form_enctype(form) }}>
{# affiche l'unique champ de la tche : description #}
{{ form_row(form.description) }}
<h3>Tags</h3>
<ul class="tags">
{# itre sur chaque tag existant et affiche son unique champ : name #}
{% for tag in form.tags %}
<li>{{ form_row(tag.name) }}</li>
{% endfor %}
</ul>
PDF brought to you by
generated on June 26, 2014
Chapter 43: Comment imbriquer une Collection de Formulaires | 160
Listing 43-7
17
18
19
{{ form_rest(form) }}
{# ... #}
</form>
Lorsque l'utilisateur soumet le formulaire, les donnes soumises pour les champs Tags sont utilises
pour construire une collection ArrayCollection d'objets Tag, qui est ensuite affecte au champ tag de
l'instance Task.
La collection Tags est naturellement accessible via $task->getTags() et peut tre persiste dans la base
de donnes ou utilise de la manire que vous voulez.
Jusqu'ici, tout cela fonctionne bien, mais cela ne vous permet pas d'ajouter de nouveaux tags ou de
supprimer des tags existants de manire dynamique. Donc, bien qu'diter des tags existants fonctionnera
parfaitement, votre utilisateur ne pourra pour le moment pas en ajouter de nouveaux.
Dans cet exemple, vous n'imbriquez qu'une seule collection, mais vous n'tes pas limit cela.
Vous pouvez aussi intgrer des collections imbriques avec autant de sous-niveaux que vous
souhaitez. Mais si vous utilisez Xdebug dans votre environnement de dveloppement, vous
pourriez recevoir une erreur telle Maximum function nesting level of '100' reached,
aborting!. Cela est d au paramtre PHP xdebug.max_nesting_level, qui est dfini avec une
valeur de 100 par dfaut.
Cette directive limite la rcursion 100 appels, ce qui ne pourrait pas tre assez pour afficher le
formulaire dans le template si vous affichez le formulaire en entier en une seule fois (par exemple
: form_widget(form)). Pour parer cela, vous pouvez dfinir cette directive avec une valeur plus
haute (soit dans le fichier ini PHP ou l'aide de ini_set
1
, par exemple dans app/autoload.php)
ou bien afficher chaque champ du formulaire manuellement en utilisant form_row.
Autoriser de nouveaux tags avec le prototypage
Autoriser l'utilisateur ajouter de nouveaux tags signifie que vous allez avoir besoin d'utiliser un peu de
Javascript. Plus tt, vous avez ajout deux tags votre formulaire dans le contrleur. Maintenant, vous
devez permettre l'utilisateur d'ajouter autant de tags qu'il souhaite directement depuis le navigateur.
Quelques lignes de Javascript sont ncessaires pour effectuer cela.
La premire chose que vous devez faire est de spcifier la collection du formulaire qu'elle va recevoir
un nombre inconnu de tags. Jusqu'ici, vous avez ajout deux tags et le formulaire s'attend en recevoir
exactement deux, sinon une erreur sera leve : This form should not contain extra fields ce qui
signifie que le formulaire ne peut contenir de champs supplmentaires. Pour rendre cela flexible, vous
ajoutez l'option allow_add votre champ collection:
1
2
3
4
5
6
7
8
9
10
11
12
// src/Acme/TaskBundle/Form/Type/TaskType.php
// ...
use Symfony\Component\Form\FormBuilderInterface;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('description');
$builder->add('tags', 'collection', array(
'type' => new TagType(),
1. http://php.net/manual/en/function.ini-set.php
PDF brought to you by
generated on June 26, 2014
Chapter 43: Comment imbriquer une Collection de Formulaires | 161
Listing 43-8
Listing 43-9
Listing 43-10
Listing 43-11
13
14
15
16
'allow_add' => true,
'by_reference' => false,
));
}
Notez que 'by_reference' => false a galement t ajout. Normalement, le framework de formulaire
modifierait les tags d'un objet Task sans jamais appeler setTags. En dfinissant by_reference false,
setTags sera appele. Vous comprendrez plus tard pourquoi cela est important.
En plus de dire au champ d'accepter n'importe quel nombre d'objets soumis, l'option allow_add met une
variable prototype votre disposition. Ce prototype est un petit template qui contient tout le
code HTML ncessaire pour afficher un nouveau formulaire tag . Pour l'utiliser, faites le changement
suivant dans votre formulaire :
1
2
3
<ul class="tags" data-prototype="{{ form_widget(form.tags.vars.prototype)|e }}">
...
</ul>
Si vous affichez votre sous-formulaire tags en entier et en une seule fois (par exemple :
form_row(form.tags)), alors le prototype est automatiquement disponible dans le div extrieur
avec l'attribut data-prototype, similaire ce que vous voyez ci-dessus.
form.tags.vars.prototype est un lment de formulaire qui ressemble l'lment individuel
form_widget(tag) l'intrieur de votre boucle for. Cela signifie que vous pouvez appeler
form_widget, form_row, ou form_label sur ce prototype. Vous pourriez mme choisir de
n'afficher qu'un seul de ses champs (par exemple : le champ name) :
1 {{ form_widget(form.tags.vars.prototype.name)|e }}
Sur la page affiche, le rsultat ressemblera quelque chose comme ceci :
1 <ul class="tags" data-prototype="&lt;div&gt;&lt;label class=&quot;
required&quot;&gt;__name__&lt;/label&gt;&lt;div
id=&quot;task_tags___name__&quot;&gt;&lt;div&gt;&lt;label
for=&quot;task_tags___name___name&quot; class=&quot; required&quot;&gt;Name&lt;/
label&gt;&lt;input type=&quot;text&quot; id=&quot;task_tags___name___name&quot;
name=&quot;task[tags][__name__][name]&quot; required=&quot;required&quot;
maxlength=&quot;255&quot; /&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;">
Le but de cette section sera d'utiliser Javascript pour lire cet attribut et ajouter dynamiquement un
nouveau tag lorsque l'utilisateur clique sur un lien Ajouter un tag . Pour garder les choses simples, cet
exemple utilise jQuery et suppose que vous l'avez dj inclus quelque part dans votre page.
Ajoutez une balise script quelque part dans votre page afin que vous puissiez commencer crire un
peu de Javascript.
Tout d'abord, ajoutez un lien en bas de votre liste de tags via Javascript. Ensuite, liez l'vnement
click de ce lien afin que vous puissiez ajouter un formulaire tag (addTagForm sera expliqu plus tard) :
1
2
3
// Rcupre le div qui contient la collection de tags
var collectionHolder = $('ul.tags');
PDF brought to you by
generated on June 26, 2014
Chapter 43: Comment imbriquer une Collection de Formulaires | 162
Listing 43-12
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ajoute un lien add a tag
var $addTagLink = $('<a href="#" class="add_tag_link">Ajouter un tag</a>');
var $newLinkLi = $('<li></li>').append($addTagLink);
jQuery(document).ready(function() {
// ajoute l'ancre ajouter un tag et li la balise ul
collectionHolder.append($newLinkLi);
$addTagLink.on('click', function(e) {
// empche le lien de crer un # dans l'URL
e.preventDefault();
// ajoute un nouveau formulaire tag (voir le prochain bloc de code)
addTagForm(collectionHolder, $newLinkLi);
});
});
Le travail de la fonction addTagForm sera d'utiliser l'attribut data-prototype pour ajouter
dynamiquement un nouveau formulaire lorsqu'on clique sur ce lien. Le code HTML de data-prototype
contient la balise texte avec un nom du genre task[tags][__name__][name] et un id du genre
task_tags___name___name. La chane de caractres __name__ est une variable de substitution (
placeholder en anglais) que vous remplacerez avec un nombre unique et incrmental (par exemple :
task[tags][3][name]).
New in version 2.1: La variable de substitution a t change dans Symfony 2.1. Au lieu de
$$name$$, elle se nomme dornavant __name__.
Le code ncessaire pour faire fonctionner tout cela peut varier, mais en voici un exemple :
1
2
3
4
5
6
7
8
9
10
11
12
function addTagForm(collectionHolder, $newLinkLi) {
// Rcupre l'lment ayant l'attribut data-prototype comme expliqu plus tt
var prototype = collectionHolder.attr('data-prototype');
// Remplace '__name__' dans le HTML du prototype par un nombre bas sur
// la longueur de la collection courante
var newForm = prototype.replace(/__name__/g, collectionHolder.children().length);
// Affiche le formulaire dans la page dans un li, avant le lien "ajouter un tag"
var $newFormLi = $('<li></li>').append(newForm);
$newLinkLi.before($newFormLi);
}
Maintenant, chaque fois qu'un utilisateur cliquera sur le lien Ajouter un tag, un nouveau sous-
formulaire apparatra sur la page. Lors de la soumission, tout nouveau formulaire de tag sera converti en
un nouvel objet Tag et ajout la proprit tags de l'objet Task.
PDF brought to you by
generated on June 26, 2014
Chapter 43: Comment imbriquer une Collection de Formulaires | 163
Listing 43-13
Listing 43-14
Listing 43-15
Doctrine: Relations de Cascade et sauvegarde du ct Inverse
Afin que les nouveaux tags soient sauvegards dans Doctrine, vous devez prendre en compte
certains lments. Tout d'abord, moins que vous n'itriez sur tous les nouveaux objets Tag et
appelez $em->persist($tag) sur chacun d'entre eux, vous allez recevoir une erreur de la part de
Doctrine :
A new entity was found through the relationship 'AcmeTaskBundleEntityTask#tags'
that was not configured to cascade persist operations for entity...
Pour rparer cela, vous pourriez choisir d'effectuer automatiquement l'opration de persistence en
mode cascade de l'objet Task pour tout les tags lis. Pour faire ceci, ajoutez l'option cascade
votre mta-donne ManyToMany :
1
2
3
4
5
6
7
8
// src/Acme/TaskBundle/Entity/Task.php
// ...
/**
* @ORM\OneToMany(targetEntity="Tag", cascade={"persist"})
*/
protected $tags;
Un second problme potentiel peut toucher les relations de Doctrine en ce qui concerne Le ct
Propritaire et le ct Inverse
2
. Dans cet exemple, si le ct propritaire dans la relation est
Task , alors la persistence va fonctionner sans problme comme les tags sont ajouts correctement
la Task . Cependant, si le ct propritaire est Tag , alors vous aurez besoin de coder un
peu plus afin de vous assurer que le bon ct de la relation est correctement modifi.
L'astuce est de s'assurer qu'une unique Task est dfinie pour chaque Tag . Une manire
facile de faire cela est d'ajouter un bout de logique supplmentaire la mthode setTags(), qui est
appele par le framework formulaire puisque la valeur de by_reference est dfinie comme false:
1
2
3
4
5
6
7
8
9
10
11
12
// src/Acme/TaskBundle/Entity/Task.php
// ...
public function setTags(ArrayCollection $tags)
{
foreach ($tags as $tag) {
$tag->addTask($this);
}
$this->tags = $tags;
}
Dans le Tag, assurez-vous simplement d'avoir une mthode addTask:
1
2
3
4
5
6
7
8
9
10
// src/Acme/TaskBundle/Entity/Tag.php
// ...
public function addTask(Task $task)
{
if (!$this->tasks->contains($task)) {
$this->tasks->add($task);
}
}
PDF brought to you by
generated on June 26, 2014
Chapter 43: Comment imbriquer une Collection de Formulaires | 164
Listing 43-16
Listing 43-17
Si vous avez une relation OneToMany, alors l'astuce reste similaire, except que vous pouvez
simplement appeler la mthode setTask depuis la mthode setTags.
Autoriser des tags tre supprims
La prochaine tape est d'autoriser la suppression d'un lment particulier de la collection. La solution est
similaire celle qui permet d'autoriser l'ajout de tags.
Commencez par ajouter l'option allow_delete dans le Type de formulaire:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/Acme/TaskBundle/Form/Type/TaskType.php
// ...
use Symfony\Component\Form\FormBuilderInterface;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('description');
$builder->add('tags', 'collection', array(
'type' => new TagType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
));
}
Modifications des Templates
L'option allow_delete a une consquence : si un lment d'une collection n'est pas envoy lors de la
soumission, les donnes lies cet lment sont supprimes de la collection sur le serveur. La solution est
donc de supprimer l'lment formulaire du DOM.
Premirement, ajoutez un lien Supprimer ce tag dans chaque formulaire de tag :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
jQuery(document).ready(function() {
// ajoute un lien de suppression tous les lments li de
// formulaires de tag existants
collectionHolder.find('li').each(function() {
addTagFormDeleteLink($(this));
});
// ... le reste du bloc vu plus haut
});
function addTagForm() {
// ...
// ajoute un lien de suppression au nouveau formulaire
addTagFormDeleteLink($newFormLi);
}
2. http://docs.doctrine-project.org/en/latest/reference/unitofwork-associations.html
PDF brought to you by
generated on June 26, 2014
Chapter 43: Comment imbriquer une Collection de Formulaires | 165
Listing 43-18
La fonction addTagFormDeleteLink va ressembler quelque chose comme ceci :
1
2
3
4
5
6
7
8
9
10
11
12
function addTagFormDeleteLink($tagFormLi) {
var $removeFormA = $('<a href="#">Supprimer ce tag</a>');
$tagFormLi.append($removeFormA);
$removeFormA.on('click', function(e) {
// empche le lien de crer un # dans l'URL
e.preventDefault();
// supprime l'lment li pour le formulaire de tag
$tagFormLi.remove();
});
}
Lorsqu'un formulaire de tag est supprim du DOM et soumis, l'objet Tag supprim ne sera pas inclus
dans la collection passe setTags. Selon votre couche de persistence, cela pourrait ou ne pourrait pas
tre suffisant pour effectivement supprimer la relation entre le Tag supprim et l'objet Task.
PDF brought to you by
generated on June 26, 2014
Chapter 43: Comment imbriquer une Collection de Formulaires | 166
Listing 43-19
Doctrine: Assurer la persistence dans la base de donnes
Quand vous supprimez des objets de cette manire, vous pourriez avoir travailler un peu plus afin
de vous assurer que la relation entre la Task et le Tag supprim soit correctement enleve.
Dans Doctrine, vous avez deux cts dans une relation : le ct propritaire et le ct inverse.
Normalement, dans ce cas, vous aurez une relation ManyToMany et les tags supprims disparatront
et seront persists correctement (ajouter des tags fonctionne aussi sans efforts supplmentaires).
Mais si vous avez une relation OneToMany ou une ManyToMany avec un mappedBy sur l'entit Task
(signifiant qu'une Task est le ct inverse ), vous devrez effectuer plus de choses afin que
les tags supprims soient persists correctement.
Dans ce cas, vous pouvez modifier le contrleur afin qu'il efface la relation pour les tags supprims.
Ceci suppose que vous ayez une editAction qui gre la mise jour de votre Task :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// src/Acme/TaskBundle/Controller/TaskController.php
use Doctrine\Common\Collections\ArrayCollection;
// ...
public function editAction($id, Request $request)
{
$em = $this->getDoctrine()->getManager();
$task = $em->getRepository('AcmeTaskBundle:Task')->find($id);
if (!$task) {
throw $this->createNotFoundException('Aucune tche trouve pour cet id :
'.$id);
}
$originalTags = new ArrayCollection();
// Cre un tableau contenant les objets Tag courants de la
// base de donnes
foreach ($task->getTags() as $tag) {
$originalTags->add($tag);
}
$editForm = $this->createForm(new TaskType(), $task);
if ($request->isMethod('POST')) {
$editForm->bind($this->getRequest());
if ($editForm->isValid()) {
// supprime la relation entre le tag et la Task
foreach ($originalTags as $tag) {
if ($task->getTags()->contains($tag) == false) {
// supprime la Task du Tag
$tag->getTasks()->removeElement($task);
// si c'tait une relation ManyToOne, vous pourriez supprimer la
// relation comme ceci
// $tag->setTask(null);
$em->persist($tag);
// si vous souhaitiez supprimer totalement le Tag, vous pourriez
PDF brought to you by
generated on June 26, 2014
Chapter 43: Comment imbriquer une Collection de Formulaires | 167
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// aussi faire comme cela
// $em->remove($tag);
}
}
$em->persist($task);
$em->flush();
// redirige vers quelconque page d'dition
return $this->redirect($this->generateUrl('task_edit', array('id' =>
$id)));
}
}
// affiche un template de formulaire quelconque
}
Comme vous pouvez le voir, ajouter et supprimer les lments correctement peut ne pas tre trivial.
A moins que vous n'ayez une relation ManyToMany o Task est le ct propritaire , vous
devrez ajouter du code supplmentaire pour vous assurer que la relation soit correctement mise
jour (que ce soit pour l'ajout de nouveaux tags ou pour la suppression de tags existants) pour
chacun des objets Tag.
PDF brought to you by
generated on June 26, 2014
Chapter 43: Comment imbriquer une Collection de Formulaires | 168
Listing 44-1
Chapter 44
Comment Crer un Type de Champ de
Formulaire Personnalis
Symfony est livr avec un ensemble de types de champ essentiels disponible pour construire des
formulaires. Cependant, il y a des situations o vous voudrez crer un type de champ de formulaire
personnalis pour un besoin spcifique. Cet article part du principe que vous avez besoin d'une dfinition
de champ qui s'occupe du sexe/genre d'une personne, base sur le champ existant choice . Cette
section explique comment le champ est dfini, comment vous pouvez personnaliser son affichage et,
finalement, comment vous pouvez le dclarer afin de pouvoir l'utiliser dans votre application.
Dfinir le Type de Champ
Afin de crer le type de champ personnalis, vous devez crer tout d'abord la classe reprsentant
le champ. Dans cette situation, la classe s'occupant du type de champ sera nomme GenderType et
le fichier sera stock dans le rpertoire par dfaut pour les champs de formulaire, c'est--dire
<BundleName>\Form\Type. Assurez-vous que le champ tend AbstractType
1
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/Acme/DemoBundle/Form/Type/GenderType.php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class GenderType extends AbstractType
{
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'choices' => array(
'm' => 'Male',
'f' => 'Female',
1. http://api.symfony.com/2.3/Symfony/Component/Form/AbstractType.html
PDF brought to you by
generated on June 26, 2014
Chapter 44: Comment Crer un Type de Champ de Formulaire Personnalis | 169
15
16
17
18
19
20
21
22
23
24
25
26
27
28
)
));
}
public function getParent()
{
return 'choice';
}
public function getName()
{
return 'gender';
}
}
L'endroit o est stock ce fichier n'est pas important - le rpertoire Form\Type est seulement une
convention.
Ici, la valeur retourne par la fonction getParent indique que vous tendez le type de champ choice.
Cela signifie que, par dfaut, vous hritez de toute la logique et du rendu de l'affichage de ce type de
champ. Pour avoir un aperu de cette logique, jetez un oeil la classe ChoiceType
2
. Il y a trois mthodes
qui sont particulirement importantes :
buildForm() - Chaque type de champ possde une mthode buildForm, qui est l'endroit o
vous configurez et construisez n'importe quel(s) champ(s). Notez que c'est la mme mthode
que vous utilisez pour initialiser vos formulaires, et cela fonctionne de la mme manire ici.
buildView() - Cette mthode est utilise pour dfinir quelconques variables supplmentaires
dont vous aurez besoin lors du rendu de votre champ dans un template. Par exemple, dans
ChoiceType
3
, une variable multiple est dfinie et utilise dans le template pour dfinir (ou ne
pas dfinir) l'attribut multiple pour le champ select. Voir Crer un Template pour le Champ
pour plus de dtails.
setDefaultOptions() - Cette mthode dfinit des options pour votre formulaire qui peuvent
tre utilises dans buildForm() et buildView(). Il y a beaucoup d'options communes tous
les champs (lisez Type de champ Form), mais vous pouvez crer ici n'importe quelle autre dont
vous pourriez avoir besoin.
Si vous crez un champ qui se compose de beaucoup de champs, alors assurez-vous de dfinir votre
type parent en tant que form ou quelque chose qui tend form. Aussi, si vous avez besoin de
modifier la vue ( view en anglais) de n'importe lequel de vos types enfants depuis votre type
parent, utilisez la mthode finishView().
La mthode getName() retourne un identifiant qui devrait tre unique dans votre application. Ce dernier
est utilis dans diffrents endroits, comme par exemple lorsque vous personnalisez la manire dont votre
formulaire sera rendu.
Le but de ce champ tait d'tendre le type choice afin d'activer la slection du sexe/genre. Cela est
accompli en dfinissant choices par une liste de genres possibles.
2. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
3. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
PDF brought to you by
generated on June 26, 2014
Chapter 44: Comment Crer un Type de Champ de Formulaire Personnalis | 170
Listing 44-2
Listing 44-3
Listing 44-4
Crer un Template pour le Champ
Chaque type de champ est rendu par un fragment de template, qui est dtermin en partie par la valeur
retourne par votre mthode getName(). Pour plus d'informations, voir Que sont les thmes de formulaire
?.
Dans ce cas, comme le champ parent est choice, vous n'avez pas besoin de faire quoi que ce soit car
le type de champ personnalis sera automatiquement affich comme un type choice. Mais pour le bien
fond de cet exemple, supposons que quand votre champ est tendu (i.e. boutons radio ou checkbox,
la place d'un champ select ), vous souhaitez toujours l'afficher dans un lment ul. Dans le template
de votre thme de formulaire (voir le lien ci-dessus pour plus de dtails), crez un bloc gender_widget
pour le grer :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #}
{% block gender_widget %}
{% spaceless %}
{% if expanded %}
<ul {{ block('widget_container_attributes') }}>
{% for child in form %}
<li>
{{ form_widget(child) }}
{{ form_label(child) }}
</li>
{% endfor %}
</ul>
{% else %}
{# just let the choice widget render the select tag #}
{{ block('choice_widget') }}
{% endif %}
{% endspaceless %}
{% endblock %}
Assurez-vous que c'est le bon prfixe du widget qui est utilis. Dans cet exemple, le nom devrait
tre gender_widget, si l'on se fie la valeur retourne par getName. De plus, le fichier de
configuration principal devrait pointer vers le template du formulaire personnalis afin qu'il soit
utilis lors de l'affichage de tous les formulaires.
1
2
3
4
5
# app/config/config.yml
twig:
form:
resources:
- 'AcmeDemoBundle:Form:fields.html.twig'
Utiliser le Type de Champ
Vous pouvez ds lors utiliser votre type de champ personnalis en crant tout simplement une nouvelle
instance du type dans l'un de vos formulaires:
1
2
3
4
// src/Acme/DemoBundle/Form/Type/AuthorType.php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
PDF brought to you by
generated on June 26, 2014
Chapter 44: Comment Crer un Type de Champ de Formulaire Personnalis | 171
Listing 44-5
Listing 44-6
Listing 44-7
5
6
7
8
9
10
11
12
13
14
15
use Symfony\Component\Form\FormBuilderInterface;
class AuthorType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('gender_code', new GenderType(), array(
'empty_value' => 'Choose a gender',
));
}
}
Mais cela fonctionne uniquement car le GenderType() est trs simple. Que se passerait-il si les diffrents
genres taient stocks dans un fichier de configuration ou dans une base de donnes ? La prochaine
section explique comment des types de champ plus complexes peuvent rsoudre ce problme.
Crer votre Type de Champ en tant que Service
Jusqu'ici, cet article a suppos que vous aviez un type de champ personnalis trs simple. Mais si vous
avez besoin d'accder la configuration, une connexion la base de donnes, ou n'importe quel
autre service, alors vous allez vouloir dclarer votre type personnalis en tant que service. Par exemple,
supposons que vous stockiez les paramtres du sexe/genre dans une configuration :
1
2
3
4
5
# app/config/config.yml
parameters:
genders:
m: Male
f: Female
Pour utiliser ce paramtre, dfinissez votre type de champ personnalis en tant que service, en injectant
la valeur du paramtre genders en tant que premier argument de la fonction __construct (qui doit tre
cre) :
1
2
3
4
5
6
7
8
# src/Acme/DemoBundle/Resources/config/services.yml
services:
acme_demo.form.type.gender:
class: Acme\DemoBundle\Form\Type\GenderType
arguments:
- "%genders%"
tags:
- { name: form.type, alias: gender }
Assurez-vous que le fichier des services est import. Voir Importer la Configuration avec imports
pour plus de dtails.
Assurez vous que l'attribut alias du tag corresponde la valeur retourne par la mthode getName
dfinie plus tt. Vous allez voir l'importance de cela dans un moment quand vous utiliserez le type de
champ personnalis. Mais tout d'abord, ajoutez une mthode __construct GenderType, qui reoit la
configuration du sexe/genre:
PDF brought to you by
generated on June 26, 2014
Chapter 44: Comment Crer un Type de Champ de Formulaire Personnalis | 172
Listing 44-8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// src/Acme/DemoBundle/Form/Type/GenderType.php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
// ...
// ...
class GenderType extends AbstractType
{
private $genderChoices;
public function __construct(array $genderChoices)
{
$this->genderChoices = $genderChoices;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'choices' => $this->genderChoices,
));
}
// ...
}
Super ! Le GenderType est maintenant rempli par les paramtres de la configuration et dclar en tant
que service. De plus, parce que vous avez utilis l'alias form.type dans sa configuration, utiliser le champ
est maintenant beaucoup plus facile:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/Acme/DemoBundle/Form/Type/AuthorType.php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\FormBuilderInterface;
// ...
class AuthorType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('gender_code', 'gender', array(
'empty_value' => 'Choose a gender',
));
}
}
Notez qu' la place d'instancier une nouvelle instance, vous pouvez simplement y faire rfrence grce
l'alias utilis dans la configuration de votre service, gender. Amusez-vous !
PDF brought to you by
generated on June 26, 2014
Chapter 44: Comment Crer un Type de Champ de Formulaire Personnalis | 173
Chapter 45
Comment crer une extension de type de
formulaire
Les types de formulaire personnaliss sont supers si vous avez besoin de types de champ qui font quelque
chose de spcifique, comme un slecteur de civilit, ou un champ pour saisir la TVA.
Mais parfois, vous n'avez pas vraiment besoin d'ajouter de nouveaux types de champ, vous voulez en fait
ajouter de nouvelles fonctionnalits sur des types existant. C'est ici que les extensions de type entrent en
jeu.
Les extensions de type de formulaire ont deux utilisations principales :
1. Vous voulez ajouter une fonctionnalit gnrique sur plusieurs types (comme ajouter un
texte d' aide sur tout les types de champ);
2. Vous voulez ajouter une fonctionnalit spcifique sur un type (comme ajouter une
fonctionnalit tlchargement sur un type de champ file ).
Dans ces deux cas, vous pourrez atteindre votre objectif en personnalisant l'affichage du formulaire, ou
en personnalisant les types de champ. Mais utiliser les extensions de type de formulaire peut tre plus
propre (en limitant la part de logique mtier dans les templates) et plus flexible (vous pouvez ajouter
plusieurs extensions de type un seul type de formulaire)/
Les extensions de type de formulaire peuvent accomplir bien plus que ce que peuvent faire des types de
champ personnaliss, mais au lieu d'tre eux-mmes des types de champ, ils se branchent sur des types
existants.
Imaginez que vous devez grer une entit Media, et que chaque mdia est associ un fichier. Votre
formulaire Media utilise un type file, mais lorsque vous ditez l'entit, vous voulez avoir un aperu
automatique de l'image affich ct du champ.
Vous pourriez bien videmment faire cela en personnalisation la manire dont est affich le champ dans
le template, mais les extensions de type de champ vous permettent de le faire sans rpter le code.
PDF brought to you by
generated on June 26, 2014
Chapter 45: Comment crer une extension de type de formulaire | 174
Listing 45-1
Listing 45-2
Dfinir l'extension de type de formulaire
Votre premire tche est de crer la classe d'extension de type de formulaire. Appelons-la
ImageTypeExtension. Par convention, les extensions de formulaire se trouvent habituellement dans le
rpertoire Form\Extension de l'un de vos bundles.
Lorsque vous crez une extension de type de formulaire, vous pouvez soit implmenter l'interface
FormTypeExtensionInterface
1
, soit tendre la classe AbstractTypeExtension
2
. Dans la plupart des
cas, il est plus simple d'tendre la classe abstraite:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/Acme/DemoBundle/Form/Extension/ImageTypeExtension.php
namespace Acme\DemoBundle\Form\Extension;
use Symfony\Component\Form\AbstractTypeExtension;
class ImageTypeExtension extends AbstractTypeExtension
{
/**
* Retourne le nom du type de champ qui est tendu
*
* @return string Le nom du type qui est tendu
*/
public function getExtendedType()
{
return 'file';
}
}
La seule mthode que vous devez implmenter est la fonction getExtendedType. Elle est utilise pour
spcifier le nom du type de formulaire qui est tendu par votre extension.
La valeur que vous retournez dans la mthode getExtendedType correspond la valeur retourne
par la mthode getName de la classe de type de formulaire que vous dsirez tendre.
En plus de la fonction getExtendedType, vous allez probablement vouloir surcharger l'une des mthodes
suivantes :
buildForm()
buildView()
setDefaultOptions()
finishView()
Pour plus d'informations sur ce que ces mthodes font, vous pouvez lire l'article du Cookbook Crer des
types de champ personnaliss.
Enregistrer vos extensions de type de formulaire comme service
La prochaine tape est d'indiquer Symfony que vous avez cr une extension. Tout ce que vous devez
faire pour cela est de la dclarer comme service en utilisant le tag form.type_extension :
1. http://api.symfony.com/2.3/Symfony/Component/Form/FormTypeExtensionInterface.html
2. http://api.symfony.com/2.3/Symfony/Component/Form/AbstractTypeExtension.html
PDF brought to you by
generated on June 26, 2014
Chapter 45: Comment crer une extension de type de formulaire | 175
Listing 45-3
1
2
3
4
5
services:
acme_demo_bundle.image_type_extension:
class: Acme\DemoBundle\Form\Extension\ImageTypeExtension
tags:
- { name: form.type_extension, alias: file }
La cl alias du tag est le type de champ sur lequel appliquer votre extension. Dans cet exemple, comme
vous voulez tendre le type de champ file, vous utilisez file comme alias.
Ajouter la logique mtier l'extension
Le but de votre extension est d'afficher de jolies images ct des champs d'upload de fichier (quand
le modle associ contient des images). Pour atteindre cet objectif, supposons que vous utilisez une
approche similaire celle dcrite dans Comment grer l'upload de fichiers avec Doctrine: vous avez
un modle Mdia avec une proprit file (qui correspond au champ file) et une proprit path (qui
correspond au chemin de l'image dans la base de donnes):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// src/Acme/DemoBundle/Entity/Media.php
namespace Acme\DemoBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Media
{
// ...
/**
* @var string Le chemin stock en base de donnes
*/
private $path;
/**
* @var \Symfony\Component\HttpFoundation\File\UploadedFile
* @Assert\File(maxSize="2M")
*/
public $file;
// ...
/**
* Retourne l'url de l'image
*
* @return null|string
*/
public function getWebPath()
{
// ... $webPath est l'url complte de l'image, qui est utilise dans le template
return $webPath;
}
Votre classe d'extension de type de formulaire devra faire deux choses pour tendre le type de formulaire
file :
1. Surcharger la mthode setDefaultOptions pour ajouter une option image_path;
2. Surcharger les mthodes buildForm et buildView pour passer l'url de l'image la vue.
PDF brought to you by
generated on June 26, 2014
Chapter 45: Comment crer une extension de type de formulaire | 176
Listing 45-4
La logique est la suivante : lorsque vous ajoutez un champ de formulaire du type file, vous pourrez
alors spcifier une nouvelle option : image_path. Cette option indiquera au champ de fichier comment
rcuprer l'url de l'image actuelle pour l'afficher dans la vue:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// src/Acme/DemoBundle/Form/Extension/ImageTypeExtension.php
namespace Acme\DemoBundle\Form\Extension;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ImageTypeExtension extends AbstractTypeExtension
{
/**
* Retourne le nom du type de champ qui est tendu
*
* @return string Le nom du type tendu
*/
public function getExtendedType()
{
return 'file';
}
/**
* Ajoute l'option image_path
*
* @param \Symfony\Component\OptionsResolver\OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setOptional(array('image_path'));
}
/**
* Passe l'url de l'image la vue
*
* @param \Symfony\Component\Form\FormView $view
* @param \Symfony\Component\Form\FormInterface $form
* @param array $options
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
if (array_key_exists('image_path', $options)) {
$parentData = $form->getParent()->getData();
if (null !== $parentData) {
$accessor = PropertyAccess::createPropertyAccessor();
$imageUrl = $accessor->getValue($parentData, $options['image_path']);
} else {
$imageUrl = null;
}
// dfinit une variable "image_url" qui sera disponible l'affichage du champ
$view->vars['image_url'] = $imageUrl;
}
}
}
PDF brought to you by
generated on June 26, 2014
Chapter 45: Comment crer une extension de type de formulaire | 177
Listing 45-5
Listing 45-6
Surcharger le fragment de template du widget File
Chaque type de champ est affich grce un fragment de template. Ces fragments de templates peuvent
tre surchargs pour personnaliser l'affichage du formulaire. Pour plus d'informations, vous pouvez
consulter l'article Que sont les thmes de formulaire ?.
Dans votre classe d'extension, vous avez ajout une nouvelle variable (image_url), mais vous n'avez pas
encore tir profit de cette nouvelle variable dans vos templates. Spcifiquement, vous devez surcharger le
bloc file_widget pour le faire :
1
2
3
4
5
6
7
8
9
10
11
12
13
{# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #}
{% extends 'form_div_layout.html.twig' %}
{% block file_widget %}
{% spaceless %}
{{ block('form_widget') }}
{% if image_url is not null %}
<img src="{{ asset(image_url) }}"/>
{% endif %}
{% endspaceless %}
{% endblock %}
Vous devrez changer votre fichier de configuration ou spcifier explicitement que votre formulaire
utilise un thme pour que Symfony utilise le bloc que vous avez surcharg. Pour plus
d'informations, lisez Que sont les thmes de formulaire ?.
Utiliser l'extension de type de formulaire
A partir de maintenant, lorsque vous ajouterez un champ de type file dans un formulaire, vous pourrez
spcifier l'option image_path qui sera utilise pour afficher une image ct du champ. Par exemple:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/Acme/DemoBundle/Form/Type/MediaType.php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class MediaType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', 'text')
->add('file', 'file', array('image_path' => 'webPath'));
}
public function getName()
{
return 'media';
}
}
PDF brought to you by
generated on June 26, 2014
Chapter 45: Comment crer une extension de type de formulaire | 178
Lorsque vous afficherez le formulaire, si le modle sous-jacent a dj t associ une image, vous la
verrez affiche ct du champ d'upload.
PDF brought to you by
generated on June 26, 2014
Chapter 45: Comment crer une extension de type de formulaire | 179
Listing 46-1
Listing 46-2
Chapter 46
Comment rduire la duplication de code avec
"inherit_data"
New in version 2.3: L'option inherit_data s'appelait virtual avant Symfony 2.3.
L'option de champ de formulaire inherit_data peut tre trs utile si vous avez des champs dupliqus
dans diffrentes entites. Par exemple, imaginez que vous avez deux entits, une Company et un Customer:
1
2
3
4
5
6
7
8
9
10
11
12
13
// src/Acme/HelloBundle/Entity/Company.php
namespace Acme\HelloBundle\Entity;
class Company
{
private $name;
private $website;
private $address;
private $zipcode;
private $city;
private $country;
}
1
2
3
4
5
6
7
8
// src/Acme/HelloBundle/Entity/Customer.php
namespace Acme\HelloBundle\Entity;
class Customer
{
private $firstName;
private $lastName;
PDF brought to you by
generated on June 26, 2014
Chapter 46: Comment rduire la duplication de code avec "inherit_data" | 180
Listing 46-3
Listing 46-4
Listing 46-5
9
10
11
12
13
private $address;
private $zipcode;
private $city;
private $country;
}
Comme vous pouvez le voir, chaque entit partage quelques champs communs : address, zipcode, city,
country.
Commencez par crer deux formulaires pour ces entits, CompanyType et CustomerType:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Acme/HelloBundle/Form/Type/CompanyType.php
namespace Acme\HelloBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class CompanyType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', 'text')
->add('website', 'text');
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Acme/HelloBundle/Form/Type/CustomerType.php
namespace Acme\HelloBundle\Form\Type;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\AbstractType;
class CustomerType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstName', 'text')
->add('lastName', 'text');
}
}
Au lieu d'inclure les champs dupliqus address, zipcode, city et country dans les deux formulaires,
crez un troisime formulaire appel LocationType pour cela:
1
2
3
4
5
6
7
8
9
10
// src/Acme/HelloBundle/Form/Type/LocationType.php
namespace Acme\HelloBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class LocationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
PDF brought to you by
generated on June 26, 2014
Chapter 46: Comment rduire la duplication de code avec "inherit_data" | 181
Listing 46-6
Listing 46-7
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
$builder
->add('address', 'textarea')
->add('zipcode', 'text')
->add('city', 'text')
->add('country', 'text');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'inherit_data' => true
));
}
public function getName()
{
return 'location';
}
}
Le formulaire LocationType a une option intressante qui est dfinie, inherit_data. Cette option
permet au formulaire d'hriter des donnes de son formulaire parent. S'il est imbriqu dans le formulaire
CompanyType, les champs de LocationType accderont aux proprits de l'instance de Company. S'il est
imbriqu dans le formulaire CustomerType, il accdera aux proprits de l'instance de Customer. Facile
non ?
Au lieu de dfinir l'option inherit_data dans LocationType, vous pouvez aussi (comme toute
option) la passer comme troisime argument de $builder->add().
Finalement, ajoutez le formulaire LocationType vos deux formulaires originaux:
1
2
3
4
5
6
7
8
9
// src/Acme/HelloBundle/Form/Type/CompanyType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
$builder->add('foo', new LocationType(), array(
'data_class' => 'Acme\HelloBundle\Entity\Company'
));
}
1
2
3
4
5
6
7
8
9
// src/Acme/HelloBundle/Form/Type/CustomerType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
$builder->add('bar', new LocationType(), array(
'data_class' => 'Acme\HelloBundle\Entity\Customer'
));
}
C'est tout! Vous avez extrait les dfinitions de champs dupliqus dans un formulaire spar que vous
pouvez rutilis o vous le voulez.
PDF brought to you by
generated on June 26, 2014
Chapter 46: Comment rduire la duplication de code avec "inherit_data" | 182
Listing 47-1
Chapter 47
Comment tester unitairement vos formulaires
Le composant Formulaire consiste en 3 objets: un type de formulaire (qui implmente
FormTypeInterface
1
), le Form
2
et la FormView
3
.
La seule classe habituellement manipule par les programmeurs est la classe de type de formulaire qui sert
de plan pour le formulaire. Elle est utilise pour gnrer le Formulaire et la vue (FormView). Vous pouvez
la tester directement en bouchonnant les interactions avec la Factory mais cela risque d'tre complexe.
Il est prfrable de la passer la FormFactory comme c'est le cas dans une vritable application. C'est
simple initialiser et vous pouvez faire confiance aux composants de Symfony pour les utiliser dans vos
tests.
Il existe dj une classe dont vous pouvez tirer parti pour tester de simples types de champs :
TypeTestCase
4
. Elle est utilise pour tester les types de champ de Symfony et vous pouvez l'utiliser pour
tester vos propres types.
New in version 2.3: La classe TypeTestCase a migr vers Symfony\Component\Form\Test dans
Symfony 2.3. Auparavant, la classe tait situe dans
Symfony\Component\Form\Tests\Extension\Core\Type.
Selon la manire dont vous avez install Symfony ou le composant Form, les tests ne seront peut
tre pas tlchargs. Utilisez l'option --prefer-source de Composer si c'est le cas.
Les bases
L'implmentation la plus simple de TypeTestCase ressemble :
1. http://api.symfony.com/2.3/Symfony/Component/Form/FormTypeInterface.html
2. http://api.symfony.com/2.3/Symfony/Component/Form/Form.html
3. http://api.symfony.com/2.3/Symfony/Component/Form/FormView.html
4. http://api.symfony.com/2.3/Symfony/Component/Form/Test/TypeTestCase.html
PDF brought to you by
generated on June 26, 2014
Chapter 47: Comment tester unitairement vos formulaires | 183
Listing 47-2
Listing 47-3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// src/Acme/TestBundle/Tests/Form/Type/TestedTypeTests.php
namespace Acme\TestBundle\Tests\Form\Type;
use Acme\TestBundle\Form\Type\TestedType;
use Acme\TestBundle\Model\TestObject;
use Symfony\Component\Form\Test\TypeTestCase;
class TestedTypeTest extends TypeTestCase
{
public function testSubmitValidData()
{
$formData = array(
'test' => 'test',
'test2' => 'test2',
);
$type = new TestedType();
$form = $this->factory->create($type);
$object = new TestObject();
$object->fromArray($formData);
// submit the data to the form directly
$form->submit($formData);
$this->assertTrue($form->isSynchronized());
$this->assertEquals($object, $form->getData());
$view = $form->createView();
$children = $view->children;
foreach (array_keys($formData) as $key) {
$this->assertArrayHasKey($key, $children);
}
}
}
Alors, qu'est ce que a teste? Voici une petite explication.
Premirement, vous vrifiez que FormType compile. Cela inclut l'hritage de classe de base, la fonction
buildForm et la rsolution d'options. Ce doit tre le premier test que vous crivez.:
1
2
$type = new TestedType();
$form = $this->factory->create($type);
Ce test vrifie qu'aucune transformation de donnes utilise par le formulaire n'choue. La mthode
isSynchronized()
5
est simplement dfinie false si une transformation de donnes lance une
exception:
1
2
$form->submit($formData);
$this->assertTrue($form->isSynchronized());
5. http://api.symfony.com/2.3/Symfony/Component/Form/FormInterface.html#isSynchronized()
PDF brought to you by
generated on June 26, 2014
Chapter 47: Comment tester unitairement vos formulaires | 184
Listing 47-4
Listing 47-5
Listing 47-6
Listing 47-7
Ne testez pas la validation : elle est applique par un couteur qui n'est pas actif dans le cas de
test et est lie la configuration de validation. Au lieu de a, testez unitairement vos contraintes
directement.
Ensuite, vrifiez la soumission et le mapping du formulaire. Le test ci-dessous vrifie que tous les champs
sont correctement spcifis:
1 $this->assertEquals($object, $form->getData());
Enfin, vrifiez la cration du FormView. Vous devriez virifier que tous les widgets que vous voulez
afficher sont disponible dans la proprit children:
1
2
3
4
5
6
$view = $form->createView();
$children = $view->children;
foreach (array_keys($formData) as $key) {
$this->assertArrayHasKey($key, $children);
}
Ajouter un Type dont votre formulaire dpend
Votre formulaire peut dpendre d'autres types qui sont dfinis comme services. Cela ressemblerait ceci:
1
2
3
4
// src/Acme/TestBundle/Form/Type/TestedType.php
// ... the buildForm method
$builder->add('acme_test_child_type');
Pour crer correctement votre formulaire, vous devez rendre le type disponible la Facotry dans votre
test. La manire la plus simple est de l'enregistrer manuellement avant de crer le formulaire parent en
utilisant la classe PreloadedExtension:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/Acme/TestBundle/Tests/Form/Type/TestedTypeTests.php
namespace Acme\TestBundle\Tests\Form\Type;
use Acme\TestBundle\Form\Type\TestedType;
use Acme\TestBundle\Model\TestObject;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Form\PreloadedExtension;
class TestedTypeTest extends TypeTestCase
{
protected function getExtensions()
{
$childType = new TestChildType();
return array(new PreloadedExtension(array(
$childType->getName() => $childType,
), array()));
}
public function testSubmitValidData()
{
$type = new TestedType();
PDF brought to you by
generated on June 26, 2014
Chapter 47: Comment tester unitairement vos formulaires | 185
Listing 47-8
22
23
24
25
26
$form = $this->factory->create($type);
// ... your test
}
}
Assurez vous que le type enfant que vous ajoutez est galement test. Autrement, vous pourriez
avoir des erreurs qui ne sont pas lies au formulaire que vous testez mais ses enfants.
Ajouter des Extensions spcifiques
Il arrive souvent que vous utilisiez des options qui sont ajoutes par des extensions de formulaire. Cela
peut tre, par exemple, ValidatorExtension avec son option invalid_message. Le TypeTestCase ne
charge que les extensions de bases donc une exception "Invalid option" sera leve si vous tentez de
l'utilisez dans une classe de test qui dpend d'autres extensions. Vous devez ajouter ces extensions
l'objet Factory:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// src/Acme/TestBundle/Tests/Form/Type/TestedTypeTests.php
namespace Acme\TestBundle\Tests\Form\Type;
use Acme\TestBundle\Form\Type\TestedType;
use Acme\TestBundle\Model\TestObject;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Form\Forms;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\Extension\Validator\Type\FormTypeValidatorExtension;
class TestedTypeTest extends TypeTestCase
{
protected function setUp()
{
parent::setUp();
$this->factory = Forms::createFormFactoryBuilder()
->addExtensions($this->getExtensions())
->addTypeExtension(
new FormTypeValidatorExtension(
$this->getMock('Symfony\Component\Validator\ValidatorInterface')
)
)
->addTypeGuesser(
$this->getMockBuilder(
'Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser'
)
->disableOriginalConstructor()
->getMock()
)
->getFormFactory();
$this->dispatcher =
$this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->builder = new FormBuilder(null, null, $this->dispatcher, $this->factory);
}
PDF brought to you by
generated on June 26, 2014
Chapter 47: Comment tester unitairement vos formulaires | 186
Listing 47-9
37
38 // ... your tests
}
Tester diffrents jeux de donnes
Si vous n'tes pas familier avec le fournisseur de donnes
6
de PHPUnit, cela peut tre une bonne
opportunit de l'utiliser:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// src/Acme/TestBundle/Tests/Form/Type/TestedTypeTests.php
namespace Acme\TestBundle\Tests\Form\Type;
use Acme\TestBundle\Form\Type\TestedType;
use Acme\TestBundle\Model\TestObject;
use Symfony\Component\Form\Test\TypeTestCase;
class TestedTypeTest extends TypeTestCase
{
/**
* @dataProvider getValidTestData
*/
public function testForm($data)
{
// ... your test
}
public function getValidTestData()
{
return array(
array(
'data' => array(
'test' => 'test',
'test2' => 'test2',
),
),
array(
'data' => array(),
),
array(
'data' => array(
'test' => null,
'test2' => null,
),
),
);
}
}
Le code ci-dessus lancera votre test trois fois avec trois jeux de donnes diffrents. Cela permet de
dcoupler les donnes de test du test lui-mme et de tester facilement plusieurs jeux de donnes
Vous pouvez galement passer un autre argument, comme un boolen si le formulaire doit tre
synchronis avec le jeu de donnes ou non etc.
6. http://www.phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers
PDF brought to you by
generated on June 26, 2014
Chapter 47: Comment tester unitairement vos formulaires | 187
Listing 48-1
Listing 48-2
Chapter 48
Comment configurer des donnes vierges pour
une classe de Formulaire
L'option empty_data vous permet de spcifier un jeu de donnes vierges pour votre classe de formulaire.
Ces donnes vierges seront utilises si vous soumettez votre formulaire, mais sans appeler la mthode
setData() ou sans avoir initialis votre formulaire lors de sa cration. Par exemple:
1
2
3
4
5
6
7
8
9
10
11
12
public function indexAction()
{
$blog = ...;
// $blog est pass lors de la cration, donc l'option empty_data option
// n'est pas ncessaire
$form = $this->createForm(new BlogType(), $blog);
// Aucune donnes n'est passe, donc l'option empty_data est utilise pour
// obtenir des donnes initiales
$form = $this->createForm(new BlogType());
}
Par dfaut, empty_data est dfinie null. Ou alors, si vous avez spcifi l'option data_class de votre
classe de formulaire, l'option empty_data sera dfinie par dfaut comme une nouvelle instance de cette
classe. L'instance sera cre en appelant le constructeur sans aucun argument.
Si vous voulez surcharger ce comportement par dfaut, il y a deux manires de procder.
Possibilit 1: Instancier une nouvelle classe
L'une des raisons pour lesquelles vous voudrez utiliser cette possibilit est que vous voulez utiliser
un constructeur en passant des arguments. Souvenez-vous, par dfaut, l'option data_class appelle le
constructeur sans argument:
PDF brought to you by
generated on June 26, 2014
Chapter 48: Comment configurer des donnes vierges pour une classe de Formulaire | 188
Listing 48-3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/Acme/DemoBundle/Form/Type/BlogType.php
// ...
use Symfony\Component\Form\AbstractType;
use Acme\DemoBundle\Entity\Blog;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class BlogType extends AbstractType
{
private $someDependency;
public function __construct($someDependency)
{
$this->someDependency = $someDependency;
}
// ...
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'empty_data' => new Blog($this->someDependency),
));
}
}
Vous pouvez instancier votre classe comme vous le voulez. Dans cet exemple, nous passons une
dpendance dans la classe BlogType au moment o elle est instancie, puis nous l'utilisons pour
instancier un objet Blog. L'important est que vous pouvez dfinir empty_data comme tant un nouvel
objet qui correspond exactement ce que vous voulez utiliser.
Possibilit 2: Fournir une Closure
Il est prfrable d'utiliser une closure car l'objet ne sera cr uniquement lorsqu'il sera ncessaire.
La closure doit prendre une instance de FormInterface comme premier argument:
1
2
3
4
5
6
7
8
9
10
11
12
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormInterface;
// ...
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'empty_data' => function (FormInterface $form) {
return new Blog($form->get('title')->getData());
},
));
}
PDF brought to you by
generated on June 26, 2014
Chapter 48: Comment configurer des donnes vierges pour une classe de Formulaire | 189
Listing 49-1
Chapter 49
Comment utiliser la fonction submit() pour
grer les soumissions de formulaires
New in version 2.3: La mthode handleRequest()
1
a t ajoute dans Symfony 2.3.
Avec la mthode handleRequest() il est trs facile de grer les soumissions de formulaires:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use Symfony\Component\HttpFoundation\Request;
// ...
public function newAction(Request $request)
{
$form = $this->createFormBuilder()
// ...
->getForm();
$form->handleRequest($request);
if ($form->isValid()) {
// perform some action...
return $this->redirect($this->generateUrl('task_success'));
}
return $this->render('AcmeTaskBundle:Default:new.html.twig', array(
'form' => $form->createView(),
));
}
1. http://api.symfony.com/2.3/Symfony/Component/Form/FormInterface.html#handleRequest()
PDF brought to you by
generated on June 26, 2014
Chapter 49: Comment utiliser la fonction submit() pour grer les soumissions de formulaires | 190
Listing 49-2
Listing 49-3
Pour en savoir plus sur cette mthode, lisez Grer la Soumission des Formulaires.
Appeler Form::submit() manuellement
New in version 2.3: Avant Symfony 2.3, la mthode submit() tait connue sous le nom de bind().
Dans certains cas, vous voudrez avoir plus de contrle sur le moment o votre formulaire est soumis et
sur les donnes qui y sont passes. Au lieu d'utiliser la mthode handleRequest()
2
, passez directement
les donnes soumises submit()
3
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use Symfony\Component\HttpFoundation\Request;
// ...
public function newAction(Request $request)
{
$form = $this->createFormBuilder()
// ...
->getForm();
if ($request->isMethod('POST')) {
$form->submit($request->request->get($form->getName()));
if ($form->isValid()) {
// perform some action...
return $this->redirect($this->generateUrl('task_success'));
}
}
return $this->render('AcmeTaskBundle:Default:new.html.twig', array(
'form' => $form->createView(),
));
}
Les formulaires qui contient des champs imbriqus attendent un tableau en argument de
submit()
4
. Vous pouvez galement soumettre des champs individuels en appelant submit()
5
directement sur un champ:
1 $form->get('firstName')->submit('Fabien');
2. http://api.symfony.com/2.3/Symfony/Component/Form/FormInterface.html#handleRequest()
3. http://api.symfony.com/2.3/Symfony/Component/Form/FormInterface.html#submit()
4. http://api.symfony.com/2.3/Symfony/Component/Form/FormInterface.html#submit()
5. http://api.symfony.com/2.3/Symfony/Component/Form/FormInterface.html#submit()
PDF brought to you by
generated on June 26, 2014
Chapter 49: Comment utiliser la fonction submit() pour grer les soumissions de formulaires | 191
Listing 49-4
Passer une requte Form::submit() (deprci)
New in version 2.3: Avant Symfony 2.3, la mthode submit tait connue sous le nom de bind.
Avant Symfony 2.3, la mthode submit()
6
acceptait un objet Request
7
. Un raccourci pratique de
l'exemple prcdent:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use Symfony\Component\HttpFoundation\Request;
// ...
public function newAction(Request $request)
{
$form = $this->createFormBuilder()
// ...
->getForm();
if ($request->isMethod('POST')) {
$form->submit($request);
if ($form->isValid()) {
// perform some action...
return $this->redirect($this->generateUrl('task_success'));
}
}
return $this->render('AcmeTaskBundle:Default:new.html.twig', array(
'form' => $form->createView(),
));
}
Passer directement la Request
8
submit()
9
fonctionne toujours, mais c'est dprci et sera supprim
dans Symfony 3.0. Vous devriez plutt utiliser handleRequest()
10
.
6. http://api.symfony.com/2.3/Symfony/Component/Form/FormInterface.html#submit()
7. http://api.symfony.com/2.3/Symfony/Component/HttpFoundation/Request.html
8. http://api.symfony.com/2.3/Symfony/Component/HttpFoundation/Request.html
9. http://api.symfony.com/2.3/Symfony/Component/Form/FormInterface.html#submit()
10. http://api.symfony.com/2.3/Symfony/Component/Form/FormInterface.html#handleRequest()
PDF brought to you by
generated on June 26, 2014
Chapter 49: Comment utiliser la fonction submit() pour grer les soumissions de formulaires | 192
Listing 50-1
Listing 50-2
Chapter 50
Comment utiliser l'Option de Champ de
Formulaire Virtual
L'option de champ de formulaire virtual peut tre trs utile quand vous avez des champs dupliqus
dans diffrentes entits.
Par exemple, imaginez que vous ayez deux entits, une Company et une Customer:
1
2
3
4
5
6
7
8
9
10
11
12
13
// src/Acme/HelloBundle/Entity/Company.php
namespace Acme\HelloBundle\Entity;
class Company
{
private $name;
private $website;
private $address;
private $zipcode;
private $city;
private $country;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// src/Acme/HelloBundle/Entity/Customer.php
namespace Acme\HelloBundle\Entity;
class Customer
{
private $firstName;
private $lastName;
private $address;
private $zipcode;
private $city;
private $country;
}
PDF brought to you by
generated on June 26, 2014
Chapter 50: Comment utiliser l'Option de Champ de Formulaire Virtual | 193
Listing 50-3
Listing 50-4
Listing 50-5
Comme vous pouvez le voir, chaque entit partage quelques champs qui sont identiques : address,
zipcode, city, country.
Maintenant, vous voulez construire deux formulaires : un pour l'entit Company et un autre pour l'entit
Customer.
Commencez par crer un CompanyType et un CustomerType:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/Acme/HelloBundle/Form/Type/CompanyType.php
namespace Acme\HelloBundle\Form\Type;
use Symfony\Component\Form\FormBuilderInterface;
class CompanyType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', 'text')
->add('website', 'text');
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Acme/HelloBundle/Form/Type/CustomerType.php
namespace Acme\HelloBundle\Form\Type;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class CustomerType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstName', 'text')
->add('lastName', 'text');
}
}
Maintenant, les quatre champs dupliqus doivent tre grs. Vous pouvez voir ci-dessous un (simple)
type de formulaire location ( lieu en franais):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/Acme/HelloBundle/Form/Type/LocationType.php
namespace Acme\HelloBundle\Form\Type;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class LocationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('address', 'textarea')
->add('zipcode', 'string')
->add('city', 'string')
->add('country', 'text');
}
PDF brought to you by
generated on June 26, 2014
Chapter 50: Comment utiliser l'Option de Champ de Formulaire Virtual | 194
Listing 50-6
Listing 50-7
17
18
19
20
21
22
23
24
25
26
27
28
29
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'virtual' => true,
));
}
public function getName()
{
return 'location';
}
}
Vous n'avez en fait pas de champ location dans vos entits, donc vous ne pouvez pas lier directement
votre LocationType votre CompanyType ou votre CustomerType. Mais vous voulez absolument avoir
un type de formulaire ddi pour grer le lieu (rappelez-vous, DRY - Don't Repeat Yourself!).
L'option de champ de formulaire virtual est la solution.
Vous pouvez dfinir l'option 'virtual' => true dans la mthode setDefaultOptions() de
LocationType et commencer l'utiliser directement dans les deux types de formulaires initiaux.
Voyez le rsultat:
1
2
3
4
5
6
7
// CompanyType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('foo', new LocationType(), array(
'data_class' => 'Acme\HelloBundle\Entity\Company'
));
}
1
2
3
4
5
6
7
// CustomerType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('bar', new LocationType(), array(
'data_class' => 'Acme\HelloBundle\Entity\Customer'
));
}
Avec l'option virtual dfinie false (comportement par dfaut), le composant Form s'attend ce
que chaque objet sous-jacent ait une proprit foo (ou bar) qui soit un objet ou un tableau contenant les
quatre champs du lieu. Bien sr, vous n'avez pas cet objet/tableau dans vos entits et vous ne le voulez
pas.
Avec l'option virtual dfinie true , le composant Form ne s'occupe pas de la proprit foo (ou
bar), et la place rcupre et dfinit ( gets et sets en anglais) les 4 champs du lieu directement
sur l'objet sous-jacent.
Au lieu de dfinir l'option virtual dans le type LocationType, vous pouvez (comme pour
n'importe quelle autre option) aussi la passer comme une option sous forme de tableau en tant que
troisime argument de $builder->add().
PDF brought to you by
generated on June 26, 2014
Chapter 50: Comment utiliser l'Option de Champ de Formulaire Virtual | 195
Listing 51-1
Chapter 51
Comment utiliser Monolog pour crire des
Logs
Monolog
1
est une bibliothque pour PHP 5.3 servant crire des logs et utilise par Symfony2. Elle est
inspire de la bibliothque Python LogBook.
Utilisation
Dans Monolog, chaque logger dfinit un canal de logging . Chaque canal possde une pile de
gestionnaires pour crire les logs (les gestionnaires peuvent tre partags).
Lorsque vous injectez le logger dans un service, vous pouvez utiliser un canal personnalis pour
voir facilement quelle partie de l'application a logg le message.
Le gestionnaire par dfaut est le StreamHandler qui crit les logs dans un stream (par dfaut dans
le fichier app/logs/prod.log dans l'environnement de production et dans app/logs/dev.log dans
l'environnement de dveloppement).
Monolog est aussi livr avec un puissant gestionnaire intgr pour le logging en environnement de
production : le FingersCrossedHandler. Il vous permet de stocker les messages dans un buffer (
mmoire tampon en franais) et de les crire dans le log que si un message atteint le niveau d'action
(ERROR dans la configuration fournie dans l'dition standard) en transmettant les messages un autre
gestionnaire.
Pour logger un message, utilisez tout simplement le service logger depuis le conteneur dans un
contrleur:
1. https://github.com/Seldaek/monolog
PDF brought to you by
generated on June 26, 2014
Chapter 51: Comment utiliser Monolog pour crire des Logs | 196
Listing 51-2
Listing 51-3
1
2
3
$logger = $this->get('logger');
$logger->info('Nous avons rcupr le logger');
$logger->error('Une erreur est survenue');
Utiliser uniquement les mthodes de l'interface LoggerInterface
2
permet de changer
l'implmentation du logger sans changer votre code.
Utiliser plusieurs gestionnaires
Le logger utilise une pile de gestionnaires qui sont appels successivement. Ceci vous permet de logger
facilement les messages de plusieurs manires.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# app/config/config*.yml
monolog:
handlers:
syslog:
type: stream
path: /var/log/symfony.log
level: error
main:
type: fingers_crossed
action_level: warning
handler: file
file:
type: stream
level: debug
La configuration ci-dessus dfinit une pile de gestionnaires qui vont tre appels dans l'ordre o ils sont
dfinis.
Le gestionnaire nomm file ne va pas tre inclus dans la pile elle-mme car il est utilis comme
un gestionnaire imbriqu du gestionnaire fingers_crossed.
Si vous voulez changer la configuration de MonologBundle dans un autre fichier de configuration,
vous avez besoin de redfinir tout le bloc. Il ne peut pas tre fusionn car l'ordre importe et une
fusion ne permet pas de contrler ce dernier.
Changer la mise en forme
Le gestionnaire utilise un Formatter pour mettre en forme une entre avant de la logger . Tous
les gestionnaires Monolog utilisent une instance de Monolog\Formatter\LineFormatter par dfaut
mais vous pouvez la remplacer facilement. Votre outil de mise en forme doit implmenter
Monolog\Formatter\FormatterInterface.
1
2
# app/config/config.yml
services:
2. http://api.symfony.com/2.3/Symfony/Component/HttpKernel/Log/LoggerInterface.html
PDF brought to you by
generated on June 26, 2014
Chapter 51: Comment utiliser Monolog pour crire des Logs | 197
Listing 51-4
Listing 51-5
3
4
5
6
7
8
9
10
my_formatter:
class: Monolog\Formatter\JsonFormatter
monolog:
handlers:
file:
type: stream
level: debug
formatter: my_formatter
Ajouter des donnes supplmentaires dans les messages de log
Monolog permet de traiter l'entre avant de la logger afin d'y ajouter des donnes supplmentaires. Un
processeur peut tre appliqu pour la pile entire des gestionnaires ou uniquement pour un gestionnaire
spcifique.
Un processeur est simplement un callable recevant l'entre log en tant que son premier argument.
Les processeurs sont configurs en utilisant la balise DIC monolog.processor. Voir la rfrence propos
de celle-ci.
Ajouter un jeton de Session/Requte
Parfois il est difficile de dire quelles entres dans le log appartiennent quelle session et/ou requte.
L'exemple suivant va ajouter un jeton unique pour chaque requte en utilisant un processeur.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
namespace Acme\MyBundle;
use Symfony\Component\HttpFoundation\Session\Session;
class SessionRequestProcessor
{
private $session;
private $token;
public function __construct(Session $session)
{
$this->session = $session;
}
public function processRecord(array $record)
{
if (null === $this->token) {
try {
$this->token = substr($this->session->getId(), 0, 8);
} catch (\RuntimeException $e) {
$this->token = '????????';
}
$this->token .= '-' . substr(uniqid(), -8);
}
$record['extra']['token'] = $this->token;
return $record;
}
}
PDF brought to you by
generated on June 26, 2014
Chapter 51: Comment utiliser Monolog pour crire des Logs | 198
# app/config/config.yml
services:
monolog.formatter.session_request:
class: Monolog\Formatter\LineFormatter
arguments:
- "[%%datetime%%] [%%extra.token%%] %%channel%%.%%level_name%%: %%message%%\n"
monolog.processor.session_request:
class: Acme\MyBundle\SessionRequestProcessor
arguments: [ @session ]
tags:
- { name: monolog.processor, method: processRecord }
monolog:
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
formatter: monolog.formatter.session_request
Si vous utilisez plusieurs gestionnaires, vous pouvez aussi dclarer le processeur au niveau du
gestionnaire au lieu de le faire globalement.
PDF brought to you by
generated on June 26, 2014
Chapter 51: Comment utiliser Monolog pour crire des Logs | 199
Listing 52-1
Chapter 52
Comment configurer Monolog pour envoyer
les erreurs par Email
Monolog
1
peut tre configur pour envoyer un email lorsqu'une erreur se produit dans une application.
Pour ce faire, la configuration ncessite quelques gestionnaires imbriqus afin d'viter de recevoir trop
d'emails. Cette configuration parat complique en premier lieu mais chaque gestionnaire est facilement
comprhensible lorsqu'on les analyse un par un.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# app/config/config.yml
monolog:
handlers:
mail:
type: fingers_crossed
action_level: critical
handler: buffered
buffered:
type: buffer
handler: swift
swift:
type: swift_mailer
from_email: [email protected]
to_email: [email protected]
subject: An Error Occurred!
level: debug
Le gestionnaire mail est un gestionnaire fingers_crossed, ce qui signifie qu'il est dclench uniquement
lorsque le niveau d'action, dans notre cas critical est atteint. Il crit alors des logs pour tout, incluant
les messages en dessous du niveau d'action. Le niveau critical est dclench seulement pour les erreurs
HTTP de code 5xx. Le paramtre handler signifie que la sortie ( output en anglais) est alors
passe au gestionnaire buffered.
1. https://github.com/Seldaek/monolog
PDF brought to you by
generated on June 26, 2014
Chapter 52: Comment configurer Monolog pour envoyer les erreurs par Email | 200
Listing 52-2
Si vous souhaitez que les erreurs de niveau 400 et 500 dclenchent un email, dfinissez
action_level avec la valeur error la place de critical.
Le gestionnaire buffered garde simplement tous les messages pour une requte et les passe ensuite au
gestionnaire imbriqu en une seule fois. Si vous n'utilisez pas ce gestionnaire alors chaque message
sera envoy par email sparment. C'est donc le gestionnaire swift qui prend le relais. C'est ce dernier
qui se charge de vous envoyer l'erreur par email. Ses paramtres sont simples : to ( en franais),
from ( de en franais) et le sujet.
Vous pouvez combiner ces gestionnaires avec d'autres afin que les erreurs continuent d'tre logues sur le
serveur en mme temps qu'elles sont envoyes par email :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# app/config/config.yml
monolog:
handlers:
main:
type: fingers_crossed
action_level: critical
handler: grouped
grouped:
type: group
members: [streamed, buffered]
streamed:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
buffered:
type: buffer
handler: swift
swift:
type: swift_mailer
from_email: [email protected]
to_email: [email protected]
subject: An Error Occurred!
level: debug
Cette configuration utilise le gestionnaire group pour envoyer les messages aux deux membres du
groupe, les gestionnaires buffered et stream. Les messages vont donc maintenant tre crits dans le
fichier log et envoys par email.
PDF brought to you by
generated on June 26, 2014
Chapter 52: Comment configurer Monolog pour envoyer les erreurs par Email | 201
Listing 53-1
Chapter 53
Comment loguer des messages dans diffrents
fichiers
New in version 2.1: La possibilit de spcifier des canaux pour un gestionnaire spcifique a t
ajoute au MonologBundle dans Symfony 2.1.
L'Edition Standard de Symfony contient plusieurs canaux pour le logging : doctrine, event, security et
request. Chaque canal correspond un service de logger (monolog.logger.XXX) dans le conteneur
et est inject dans le service concern. Le but des canaux est d'tre capable d'organiser diffrents types de
messages de logging.
Par dfaut, Symfony2 logue tous les messages dans un fichier unique (peu importe le canal).
Transfrer un canal vers un gestionnaire diffrent
Maintenant, supposons que vous vouliez loguer le canal doctrine dans un fichier diffrent.
Pour faire cela, crez simplement un nouveau gestionnaire et configurez-le comme ceci :
1
2
3
4
5
6
7
8
9
10
monolog:
handlers:
main:
type: stream
path: /var/log/symfony.log
channels: !doctrine
doctrine:
type: stream
path: /var/log/doctrine.log
channels: doctrine
PDF brought to you by
generated on June 26, 2014
Chapter 53: Comment loguer des messages dans diffrents fichiers | 202
Listing 53-2
Spcification Yaml
Vous pouvez spcifier la configuration de diffrentes faons :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
channels: ~ # Inclus tous les canaux
channels: foo # Inclus seulement le canal "foo"
channels: !foo # Inclus tous les canaux, except "foo"
channels: [foo, bar] # Inclus seulement les canaux "foo" et "bar"
channels: [!foo, !bar] # Inclus tous les canaux, except "foo" et "bar"
channels:
type: inclusive # Inclus seulement ceux lists ci-dessous
elements: [ foo, bar ]
channels:
type: exclusive # Inclus tous, except ceux lists ci-dessous
elements: [ foo, bar ]
Crer votre propre Canal
Vous pouvez remplacer les logs du canal de monolog par un service la fois. Cela s'effectue en ajoutant
un tag monolog.logger votre service et en spcifiant dans quel canal le service devrait loguer. En faisant
cela, le logger qui est inject dans ce service est prconfigur pour utiliser le canal que vous avez
spcifi.
Pour plus d'informations - incluant un exemple complet - lisez monolog.logger dans la section Tags
d'Injection de Dpendance du document de rfrence.
En savoir plus grce au Cookbook
Comment utiliser Monolog pour crire des Logs
PDF brought to you by
generated on June 26, 2014
Chapter 53: Comment loguer des messages dans diffrents fichiers | 203
Listing 54-1
Chapter 54
Comment crer un Collecteur de Donnes
personnalis
Le Profiler de Symfony2 dlgue la collection de donnes aux collecteurs de donnes. Certains d'entre
eux sont fournis avec Symfony2, mais vous pouvez facilement crer le vtre.
Crer un Collecteur de Donnes Personnalis
Crer un collecteur de donnes personnalis est aussi simple que d'implmenter la classe
DataCollectorInterface
1
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface DataCollectorInterface
{
/**
* Collects data for the given Request and Response.
*
* @param Request $request A Request instance
* @param Response $response A Response instance
* @param \Exception $exception An Exception instance
*/
function collect(Request $request, Response $response, \Exception $exception = null);
/**
* Returns the name of the collector.
*
* @return string The collector name
*/
function getName();
}
La mthode getName() doit retourner un nom unique. Ceci est utilis pour accder l'information plus
tard (voir Comment utiliser le Profiler dans un test fonctionnel par exemple).
1. http://api.symfony.com/2.3/Symfony/Component/HttpKernel/DataCollector/DataCollectorInterface.html
PDF brought to you by
generated on June 26, 2014
Chapter 54: Comment crer un Collecteur de Donnes personnalis | 204
Listing 54-2
Listing 54-3
Listing 54-4
La mthode collect() est responsable de stocker les donnes auxquelles elle veut donner accs dans des
proprits locales.
Comme le profiler srialise les instances de collecteur de donnes, vous ne devriez pas stocker des
objets ne pouvant pas tre srialiss (comme des objets PDO), ou vous devrez fournir votre propre
mthode serialize().
La plupart du temps, il est pratique d'tendre la classe DataCollector
2
et de remplir la proprit $this-
>data (elle se charge de srialiser la proprit $this->data):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MemoryDataCollector extends DataCollector
{
public function collect(Request $request, Response $response, \Exception $exception =
null)
{
$this->data = array(
'memory' => memory_get_peak_usage(true),
);
}
public function getMemory()
{
return $this->data['memory'];
}
public function getName()
{
return 'memory';
}
}
Activer les Collecteurs de Donnes Personnaliss
Pour activer un collecteur de donnes, ajoutez le comme un service ordinaire dans l'une de vos
configurations, et taggez le avec data_collector :
1
2
3
4
5
services:
data_collector.your_collector_name:
class: Fully\Qualified\Collector\Class\Name
tags:
- { name: data_collector }
Ajouter des Templates de Profiler Web
Quand vous voulez afficher les donnes collectes par votre Collecteur de Donnes dans la barre d'outils
de dbuggage ou dans le profiler web, crez un template Twig en vous appuyant sur l'exemple suivant :
1
2
{% extends 'WebProfilerBundle:Profiler:layout.html.twig' %}
2. http://api.symfony.com/2.3/Symfony/Component/HttpKernel/DataCollector/DataCollector.html
PDF brought to you by
generated on June 26, 2014
Chapter 54: Comment crer un Collecteur de Donnes personnalis | 205
Listing 54-5
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{% block toolbar %}
{# le contenu de la barre d'outils de dbuggage web #}
{% endblock %}
{% block head %}
{# si le panel du profiler web ncessite des fichiers JS ou CSS spcifiques #}
{% endblock %}
{% block menu %}
{# le contenu du menu #}
{% endblock %}
{% block panel %}
{# le contenu du panel #}
{% endblock %}
Chaque bloc est optionnel. Le bloc toolbar est utilis pour la barre d'outils de dbuggage web et les blocs
menu et panel sont utiliss pour ajouter un panel au profiler web.
Tous les blocs ont accs l'objet collector.
Les templates intgrs utilisent une image encode en base64 pour la barre d'outils (<img
src="src="data:image/png;base64,..."). Vous pouvez facilement calculer la valeur en base64
d'une image avec ce petit script : echo
base64_encode(file_get_contents($_SERVER['argv'][1]));.
Pour activer le template, ajoutez un attribut template au tag data_collector dans votre configuration.
Par exemple, en supposant que votre template est dans un AcmeDebugBundle :
1
2
3
4
5
services:
data_collector.your_collector_name:
class: Acme\DebugBundle\Collector\Class\Name
tags:
- { name: data_collector, template: "AcmeDebugBundle:Collector:templatename",
id: "your_collector_name" }
PDF brought to you by
generated on June 26, 2014
Chapter 54: Comment crer un Collecteur de Donnes personnalis | 206
Listing 55-1
Chapter 55
Comment dclarer un nouveau Format de
Requte et un Type Mime
Chaque Requte a un format (par exemple : html, json), qui est utilis pour dterminer quel
type de contenu retourner dans la Rponse. En fait, le format de la requte, accessible via la mthode
getRequestFormat()
1
, est utilis pour dfinir le type MIME de l'en-tte Content-Type de l'objet
Response. En interne, Symfony contient un tableau des formats les plus communs (par exemple : text/
html, application/json). Bien sr, des types de format MIME additionnels peuvent aisment tre
ajouts. Ce document va vous montrer comment vous pouvez ajouter le format jsonp ainsi que le type
MIME correspondant.
Crez un Listener kernel.request
La solution pour dfinir un nouveau type MIME est de crer une classe qui va couter l'vnement
kernel.request dispatch ( rparti en franais) par le kernel de Symfony. L'vnement
kernel.request est dispatch trs tt dans le processus de gestion de la requte de Symfony et vous
permet de modifier l'objet requte.
Crez la classe suivante, en remplaant le chemin par un chemin vers un bundle de votre projet:
1
2
3
4
5
6
7
8
9
10
11
// src/Acme/DemoBundle/RequestListener.php
namespace Acme\DemoBundle;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class RequestListener
{
public function onKernelRequest(GetResponseEvent $event)
{
$event->getRequest()->setFormat('jsonp', 'application/javascript');
1. http://api.symfony.com/2.3/Symfony/Component/HttpFoundation/Request.html#getRequestFormat()
PDF brought to you by
generated on June 26, 2014
Chapter 55: Comment dclarer un nouveau Format de Requte et un Type Mime | 207
Listing 55-2
12
13
}
}
Dclarer votre Listener
Comme pour n'importe quel autre listener, vous avez besoin de l'ajouter dans l'un de vos fichiers de
configuration et de le dclarer comme listener en ajoutant le tag kernel.event_listener :
1
2
3
4
5
6
7
8
9
10
11
12
<!-- app/config/config.xml -->
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/
dic/services/services-1.0.xsd">
<services>
<service id="acme.demobundle.listener.request" class="Acme\DemoBundle\RequestListener">
<tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest"
/>
</service>
</services>
</container>
A ce stade, le service acme.demobundle.listener.request a t configur et sera notifi lorsque le
kernel de Symfony dispatchera l'vnement kernel.request.
Vous pouvez aussi dclarer le listener dans une configuration de classe extension (voir Importer la
Configuration via les Extensions de Conteneur pour plus d'informations).
PDF brought to you by
generated on June 26, 2014
Chapter 55: Comment dclarer un nouveau Format de Requte et un Type Mime | 208
Listing 56-1
Listing 56-2
Chapter 56
Comment forcer les routes toujours utiliser
HTTPS ou HTTP
Quelquefois, vous voulez scuriser certaines routes et tre sr qu'on y accde toujours via le protocole
HTTPS. Le composant Routing vous permet de forcer le systme de l'URI via la condition requise
_scheme :
1
2
3
4
5
secure:
pattern: /secure
defaults: { _controller: AcmeDemoBundle:Main:secure }
requirements:
_scheme: https
La configuration ci-dessus force la route nomme secure toujours utiliser HTTPS.
Pendant la gnration de l'URL de secure, et si le systme actuel est HTTP, Symfony va
automatiquement gnrer une URL absolue avec HTTPS comme scheme :
1
2
3
4
5
6
7
{# Si le scheme actuel est HTTPS #}
{{ path('secure') }}
{# gnre /secure
{# Si le scheme actuel est HTTP #}
{{ path('secure') }}
{# gnre https://example.com/secure #}
La condition requise est aussi force pour les requtes entrantes. Si vous essayez d'accder au chemin
/secure avec HTTP, vous serez automatiquement redirig la mme URL, mais avec le scheme
HTTPS.
Les exemples ci-dessus utilisent https en tant que _scheme, mais vous pouvez aussi forcer une URL
toujours utiliser http.
PDF brought to you by
generated on June 26, 2014
Chapter 56: Comment forcer les routes toujours utiliser HTTPS ou HTTP | 209
Le composant Security fournit une autre faon d'imposer HTTP ou HTTPS via le paramtre
requires_channel. Cette mthode alternative est mieux adapte pour scuriser une zone de
votre site web (toutes les URLs dans la zone /admin) ou pour scuriser les URLs dfinies dans un
bundle tiers.
PDF brought to you by
generated on June 26, 2014
Chapter 56: Comment forcer les routes toujours utiliser HTTPS ou HTTP | 210
Listing 57-1
Chapter 57
Comment autoriser un caractre / dans un
paramtre de route
Parfois, on a besoin de construire des URLs avec des paramtres qui peuvent contenir un slash /. Prenons
par exemple la route classique /hello/{name}. Par dfaut, /hello/Fabien va correspondre cette route
mais pas /hello/Fabien/Kris. Cela est d au fait que Symfony utilise ce caractre comme sparateur
entre les parties de la route.
Ce guide explique comment vous pouvez modifier une route afin que /hello/Fabien/Kris corresponde
la route /hello/{name}, o {name} quivaut Fabien/Kris.
Configurer la Route
Par dfaut, les composants de routage de Symfony requirent que les paramtres correspondent au
pattern de regex suivant : [^/]+. Cela veut dire que tous les caractres sont autoriss sauf /.
Vous devez explicitement autoriser le caractre / faire partie de votre paramtre en spcifiant un pattern
de regex plus permissif.
1
2
3
4
5
_hello:
pattern: /hello/{name}
defaults: { _controller: AcmeDemoBundle:Demo:hello }
requirements:
name: ".+"
C'est tout ! Maintenant, le paramtre {name} peut contenir le caractre /.
PDF brought to you by
generated on June 26, 2014
Chapter 57: Comment autoriser un caractre / dans un paramtre de route | 211
Listing 58-1
Chapter 58
Comment configurer une redirection vers une
autre route sans contrleur personnalis
Ce guide explique comment configurer une redirection d'une route vers une autre sans utiliser de
contrleur spcifique.
Supposez qu'il n'existe pas de contrleur par dfaut pour l'URL / de votre application et que vous voulez
rediriger ces requtes vers /app.
Votre configuration ressemblerait ceci :
1
2
3
4
5
6
7
8
9
10
11
AppBundle:
resource: "@App/Controller/"
type: annotation
prefix: /app
root:
pattern: /
defaults:
_controller: FrameworkBundle:Redirect:urlRedirect
path: /app
permanent: true
Dans cet exemple, vous configurez une route pour le chemin / et laissez la classe RedirectController
1
la grer. Ce contrleur est fourni avec Symfony et propose deux actions pour rediriger les requtes :
urlRedirect redirige vers un autre chemin. Vous devez spcifier le paramtre path pour qu'il
contienne le chemin vers la ressource vers laquelle vous voulez rediriger.
redirect (pas montr ici) redirige vers une autre route. Vous devez dfinir le paramtre route
avec le nom de la route vers laquelle vous voulez rediriger.
Le paramtre permanent indique aux deux mthodes de retourner un code de statut HTTP 301 au lieu
du code 302 par dfaut.
1. http://api.symfony.com/2.3/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.html
PDF brought to you by
generated on June 26, 2014
Chapter 58: Comment configurer une redirection vers une autre route sans contrleur personnalis |
212
Listing 59-1
Listing 59-2
Chapter 59
Comment utiliser des mthodes HTTP autres
que GET et POST dans les routes
La mthode HTTP d'une requte est l'un des prrequis qui peuvent tre vrifis pour valider si une route
correspond ou pas. Cela est abord dans le chapitre sur le routage du Book Routage avec des exemples
qui utilisent les mthodes GET et POST. Vous pouvez galement utiliser d'autres mthodes HTTP de la
mme manire. Par exemple, si vous avez un billet de blog, alors vous pourriez utiliser le mme schma
d'URL pour l'afficher, le modifier et le supprimer grce aux mthodes GET, PUT et DELETE.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
blog_show:
pattern: /blog/{slug}
defaults: { _controller: AcmeDemoBundle:Blog:show }
requirements:
_method: GET
blog_update:
pattern: /blog/{slug}
defaults: { _controller: AcmeDemoBundle:Blog:update }
requirements:
_method: PUT
blog_delete:
pattern: /blog/{slug}
defaults: { _controller: AcmeDemoBundle:Blog:delete }
requirements:
_method: DELETE
Malheureusement, la vie n'est pas toujours si simple puisque plusieurs navigateurs ne supportent pas
l'envoi de requtes PUT et DELETE. Heureusement, Symfony2 vous fournit de manire simple de
contourner cette limitation. En incluant le paramtre _method dans la chane de caractres de la requte,
ou dans les paramtres d'une requte HTTP, Symfony l'utilisera comme mthode pour trouver une
route correspondante. Cela peut tre fait trs facilement dans les formulaires grce un champ cach.
Supposons que vous ayez un formulaire pour diter un billet de blog :
PDF brought to you by
generated on June 26, 2014
Chapter 59: Comment utiliser des mthodes HTTP autres que GET et POST dans les routes | 213
Listing 59-3
1
2
3
4
5
<form action="{{ path('blog_update', {'slug': blog.slug}) }}" method="post">
<input type="hidden" name="_method" value="PUT" />
{{ form_widget(form) }}
<input type="submit" value="Update" />
</form>
La requte soumise correspondra maintenant la route blog_update et l'action updateAction sera
utilise pour traiter le formulaire.
De la mme manire, le formulaire de suppression peut tre modifi pour ressembler ceci :
1
2
3
4
5
<form action="{{ path('blog_delete', {'slug': blog.slug}) }}" method="post">
<input type="hidden" name="_method" value="DELETE" />
{{ form_widget(delete_form) }}
<input type="submit" value="Delete" />
</form>
Alors, la route blog_delete sera utilise.
PDF brought to you by
generated on June 26, 2014
Chapter 59: Comment utiliser des mthodes HTTP autres que GET et POST dans les routes | 214
Listing 60-1
Listing 60-2
Listing 60-3
Chapter 60
Comment utiliser des paramtres du conteneur
de services dans vos routes
New in version 2.1: La possibilit d'utiliser ces paramtres dans vos routes est une nouveaut de
Symfony 2.1.
Parfois, vous pouvez trouver utile de rendre certaines parties de vos routes configurables de faon globale.
Par exemple, si vous construisez un site internationalis, vous commencerez probablement pas une ou
deux locales. Vous ajouterez certainement un prrequis dans vos routes pour empcher l'utilisateur
d'accder une autre locale que celles que vous supportez.
Vous pourriez coder en dure votre prrequis _locale dans toutes vos routes. Mais une meilleure solution
sera d'utiliser un paramtre configurable du conteneur de services dans la configuration de votre routage
:
contact:
pattern: /{_locale}/contact
defaults: { _controller: AcmeDemoBundle:Main:contact }
requirements:
_locale: %acme_demo.locales%
Vous pouvez maintenant contrler et dfinir le paramtre acme_demo.locales quelque part dans votre
conteneur :
1
2
3
# app/config/config.yml
parameters:
acme_demo.locales: en|es
Vous pouvez aussi utiliser un paramtre pour dfinir votre schma de route (ou une partie de votre
schma) :
PDF brought to you by
generated on June 26, 2014
Chapter 60: Comment utiliser des paramtres du conteneur de services dans vos routes | 215
1
2
3
some_route:
pattern: /%acme_demo.route_prefix%/contact
defaults: { _controller: AcmeDemoBundle:Main:contact }
Tout comme dans les fichiers de configuration classiques du conteneur, si vous avez besoin d'un
% dans votre route, vous pouvez chapper le signe popurcentage en le doublant. Par exemple,
/score-50%% deviendra /score-50%.
PDF brought to you by
generated on June 26, 2014
Chapter 60: Comment utiliser des paramtres du conteneur de services dans vos routes | 216
Chapter 61
Comment charger les utilisateurs depuis la
base de donnes (le fournisseur d'Entit)
La couche de scurit est l'un des outils les plus intelligents de Symfony. Il gre deux choses : les
procds d'authentification et d'autorisation. Bien qu'il puisse tre difficile de comprendre comment cela
fonctionne en interne, le systme de scurit est trs flexible et vous permet d'intgrer votre application
avec n'importe quel backend d'authentification, comme Active Directory, un serveur OAuth ou une
base de donnes.
Introduction
Cet article traite de l'authentification des utilisateurs travers une table de base de donnes gre
par une classe entit Doctrine. Le contenu de cet article du Cookbook est divis en trois parties.
La premire partie parle de la conception d'une classe entit Doctrine User ainsi que du fait de la
rendre utilisable par la couche de scurit de Symfony. La deuxime partie dcrit comment authentifier
facilement un utilisateur avec l'objet Doctrine EntityUserProvider
1
fourni avec le framework et
quelques lments de configurations. Finalement, le tutoriel va dmontrer comment crer un objet
personnalis EntityUserProvider
2
pour rcuprer des utilisateurs depuis la base de donnes sous
certaines conditions personnalises.
Ce tutoriel suppose qu'il existe un bundle Acme\UserBundle dmarr et charg dans le kernel de
l'application.
Le Modle de Donnes
Pour cet article du cookbook, le bundle AcmeUserBundle contient une classe entit User avec les champs
suivants : id, username, salt, password, email et isActive. Le champ isActive prcise si oui ou non
le compte de l'utilisateur est activ.
1. http://api.symfony.com/2.3/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.html
2. http://api.symfony.com/2.3/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.html
PDF brought to you by
generated on June 26, 2014
Chapter 61: Comment charger les utilisateurs depuis la base de donnes (le fournisseur d'Entit) |
217
Listing 61-1
Pour faire court, les mthodes getter et setter de chacun des champs ont t supprimes pour se
concentrer sur les mthodes les plus importantes qui proviennent de l'interface UserInterface
3
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// src/Acme/UserBundle/Entity/User.php
namespace Acme\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* Acme\UserBundle\Entity\User
*
* @ORM\Table(name="acme_users")
* @ORM\Entity(repositoryClass="Acme\UserBundle\Entity\UserRepository")
*/
class User implements UserInterface, \Serializable
{
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(type="string", length=25, unique=true)
*/
private $username;
/**
* @ORM\Column(type="string", length=32)
*/
private $salt;
/**
* @ORM\Column(type="string", length=40)
*/
private $password;
/**
* @ORM\Column(type="string", length=60, unique=true)
*/
private $email;
/**
* @ORM\Column(name="is_active", type="boolean")
*/
private $isActive;
public function __construct()
{
$this->isActive = true;
$this->salt = md5(uniqid(null, true));
}
/**
* @inheritDoc
*/
3. http://api.symfony.com/2.3/Symfony/Component/Security/Core/User/UserInterface.html
PDF brought to you by
generated on June 26, 2014
Chapter 61: Comment charger les utilisateurs depuis la base de donnes (le fournisseur d'Entit) |
218
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
public function getUsername()
{
return $this->username;
}
/**
* @inheritDoc
*/
public function getSalt()
{
return $this->salt;
}
/**
* @inheritDoc
*/
public function getPassword()
{
return $this->password;
}
/**
* @inheritDoc
*/
public function getRoles()
{
return array('ROLE_USER');
}
/**
* @inheritDoc
*/
public function eraseCredentials()
{
}
/**
* @see \Serializable::serialize()
*/
public function serialize()
{
return serialize(array(
$this->id,
));
}
/**
* @see \Serializable::unserialize()
*/
public function unserialize($serialized)
{
list (
$this->id,
) = unserialize($serialized);
}
}
Afin d'utiliser une instance de la classe AcmeUserBundle:User dans la couche de scurit de Symfony, la
classe entit doit implmenter l'interface UserInterface
4
. Cette interface force la classe implmenter
PDF brought to you by
generated on June 26, 2014
Chapter 61: Comment charger les utilisateurs depuis la base de donnes (le fournisseur d'Entit) |
219
Listing 61-2
Listing 61-3
les cinq mthodes suivantes : * getRoles(), * getPassword(), * getSalt(), * getUsername(), *
eraseCredentials()
Pour plus de dtails sur chacune d'entre elles, voir UserInterface
5
.
L'interface Serializable
6
ainsi que ses mthodes serialize et unserialize ont t ajoutes pour
permettre la classe User d'tre srialisable dans la session. Cela peut ou non tre ncessaire en
fonction de votre configuration, mais c'est certainement une bonne ide. Seule la proprit id a
besoin d'tre srialise, car la mthode refreshUser()
7
recharge l'utilisateur chaque requte en
utilisant la proprit id.
1
2
3
4
5
6
7
8
9
10
11
// src/Acme/UserBundle/Entity/User.php
namespace Acme\UserBundle\Entity;
use Symfony\Component\Security\Core\User\EquatableInterface;
// ...
public function isEqualTo(UserInterface $user)
{
return $this->username === $user->getUsername();
}
Voici, ci-dessous, un export de ma table User depuis MySQL. Pour plus de dtails sur la cration des
entres utilisateur et l'encodage de leur mot de passe, lisez le chapitre Encoder les mots de passe.
1
2
3
4
5
6
7
8
9
10
mysql> select * from user;
+----+----------+----------------------------------+------------------------------------------+--------------------+-----------+
| id | username | salt | password | email | is_active |
+----+----------+----------------------------------+------------------------------------------+--------------------+-----------+
| 1 | hhamon | 7308e59b97f6957fb42d66f894793079 | 09610f61637408828a35d7debee5b38a8350eebe | [email protected] | 1 |
| 2 | jsmith | ce617a6cca9126bf4036ca0c02e82dee | 8390105917f3a3d533815250ed7c64b4594d7ebf | [email protected] | 1 |
| 3 | maxime | cd01749bb995dc658fa56ed45458d807 | 9764731e5f7fb944de5fd8efad4949b995b72a3c | [email protected] | 0 |
| 4 | donald | 6683c2bfd90c0426088402930cadd0f8 | 5c3bcec385f59edcc04490d1db95fdb8673bf612 | [email protected] | 1 |
+----+----------+----------------------------------+------------------------------------------+--------------------+-----------+
4 rows in set (0.00 sec)
La base de donnes contient dsormais quatre utilisateurs avec diffrents noms d'utilisateurs, emails et
statuts. La prochaine partie va traiter de l'authentification de l'un de ces utilisateurs grce au fournisseur
d'entit utilisateur Doctrine et quelques lignes de configuration.
Authentifier quelqu'un travers une base de donnes
Authentifier un utilisateur Doctrine travers une base de donnes avec la couche de scurit de Symfony
est vraiment trs facile. Tout rside dans la configuration du SecurityBundle stocke dans le fichier app/
config/security.yml.
Vous trouverez ci-dessous un exemple de configuration o l'utilisateur va entrer son nom d'utilisateur
et son mot de passe via une authentification basique HTTP. Cette information sera alors compare et
vrifie avec nos entres d'entit User de la base de donnes :
4. http://api.symfony.com/2.3/Symfony/Component/Security/Core/User/UserInterface.html
5. http://api.symfony.com/2.3/Symfony/Component/Security/Core/User/UserInterface.html
6. http://php.net/manual/en/class.serializable.php
7. http://api.symfony.com/2.3/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.html#refreshUser()
PDF brought to you by
generated on June 26, 2014
Chapter 61: Comment charger les utilisateurs depuis la base de donnes (le fournisseur d'Entit) |
220
Listing 61-4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# app/config/security.yml
security:
encoders:
Acme\UserBundle\Entity\User:
algorithm: sha1
encode_as_base64: false
iterations: 1
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH ]
providers:
administrators:
entity: { class: AcmeUserBundle:User, property: username }
firewalls:
admin_area:
pattern: ^/admin
http_basic: ~
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
La section encoders associe l'encodeur de mot de passe sha1 la classe entit. Cela signifie que Symfony
va s'attendre ce que le mot de passe stock dans la base de donnes soit encod l'aide de cet
algorithme. Pour plus de dtails sur la cration d'un nouvel objet User avec un mot de passe encrypt
correctement, lisez la section Encoder les mots de passe du chapitre sur la scurit.
La section providers dfinit un fournisseur d'utilisateur administrators. Un fournisseur d'utilisateur
est une source indiquant o les utilisateurs sont chargs lors de l'authentification. Dans ce cas, le
mot-cl entity signifie que Symfony va utiliser le fournisseur d'entit utilisateur Doctrine pour charger
des objets entit User depuis la base de donnes en utilisant le champ unique username. En d'autres
termes, cela indique Symfony comment rcuprer l'utilisateur depuis la base de donnes avant de
vrifier la validit du mot de passe.
Ce code et cette configuration fonctionnent mais ce n'est pas suffisant pour scuriser l'application pour
des utilisateurs activs. En effet, maintenant, vous pouvez toujours vous authentifier avec maxime. La
section suivante explique comment interdire l'accs aux utilisateurs non-activs.
Interdire les Utilisateurs non-activs
La manire la plus facile d'exclure des utilisateurs non-activs est d'implmenter l'interface
AdvancedUserInterface
8
qui se charge de vrifier le statut du compte de l'utilisateur. L'interface
AdvancedUserInterface
9
tend l'interface UserInterface
10
, donc vous devez simplement utiliser la
nouvelle interface dans la classe entit AcmeUserBundle:User afin de bnficier de comportements
d'authentification simples et avancs.
L'interface AdvancedUserInterface
11
ajoute quatre mthodes supplmentaires pour valider le statut du
compte :
isAccountNonExpired() vrifie si le compte de l'utilisateur a expir,
8. http://api.symfony.com/2.3/Symfony/Component/Security/Core/User/AdvancedUserInterface.html
9. http://api.symfony.com/2.3/Symfony/Component/Security/Core/User/AdvancedUserInterface.html
10. http://api.symfony.com/2.3/Symfony/Component/Security/Core/User/UserInterface.html
11. http://api.symfony.com/2.3/Symfony/Component/Security/Core/User/AdvancedUserInterface.html
PDF brought to you by
generated on June 26, 2014
Chapter 61: Comment charger les utilisateurs depuis la base de donnes (le fournisseur d'Entit) |
221
Listing 61-5
isAccountNonLocked() vrifie si l'utilisateur est verrouill,
isCredentialsNonExpired() vrifie si les informations de connexion de l'utilisateur (mot de
passe) ont expir,
isEnabled() vrifie si l'utilisateur est activ.
Pour cet exemple, les trois premires mthodes vont retourner true alors que la mthode isEnabled()
va retourner la valeur boolenne du champ isActive.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// src/Acme/UserBundle/Entity/User.php
namespace Acme\UserBundle\Entity;
// ...
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
class User implements AdvancedUserInterface, \Serializable
{
// ...
public function isAccountNonExpired()
{
return true;
}
public function isAccountNonLocked()
{
return true;
}
public function isCredentialsNonExpired()
{
return true;
}
public function isEnabled()
{
return $this->isActive;
}
}
Si vous essayez de vous authentifier avec maxime, l'accs est maintenant interdit puisque cet utilisateur
n'a pas un compte activ. La prochaine section va se concentrer sur l'implmentation d'un fournisseur
d'entit personnalis pour authentifier un utilisateur avec son nomd'utilisateur ou avec son adresse email.
Authentifier quelqu'un avec un fournisseur d'entit personnalis
La prochaine tape est de permettre un utilisateur de s'authentifier avec son nom d'utilisateur ou avec
son adresse email comme ils sont tous les deux uniques dans la base de donnes. Malheureusement, le
fournisseur d'entit natif est seulement capable de grer une proprit unique pour rcuprer l'utilisateur
depuis la base de donnes.
Pour raliser ceci, crez un fournisseur d'entit personnalis qui cherche un utilisateur dont le champ
nom d'utilisateur ou email correspond au nom d'utilisateur soumis lors de la phase de connexion/
login. La bonne nouvelle est qu'un objet Repository Doctrine peut agir comme un fournisseur d'entit
utilisateur s'il implmente UserProviderInterface
12
. Cette interface est fournie avec trois mthodes
12. http://api.symfony.com/2.3/Symfony/Component/Security/Core/User/UserProviderInterface.html
PDF brought to you by
generated on June 26, 2014
Chapter 61: Comment charger les utilisateurs depuis la base de donnes (le fournisseur d'Entit) |
222
Listing 61-6
implmenter : loadUserByUsername($username), refreshUser(UserInterface $user), et
supportsClass($class). Pour plus de dtails, lisez UserProviderInterface
13
.
Le code ci-dessous montre l'implmentation de UserProviderInterface
14
dans la classe
UserRepository:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// src/Acme/UserBundle/Entity/UserRepository.php
namespace Acme\UserBundle\Entity;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\NoResultException;
class UserRepository extends EntityRepository implements UserProviderInterface
{
public function loadUserByUsername($username)
{
$q = $this
->createQueryBuilder('u')
->where('u.username = :username OR u.email = :email')
->setParameter('username', $username)
->setParameter('email', $username)
->getQuery();
try {
// La mthode Query::getSingleResult() lance une exception
// s'il n'y a pas d'entre correspondante aux critres
$user = $q->getSingleResult();
} catch (NoResultException $e) {
throw new UsernameNotFoundException(sprintf('Unable to find an active admin
AcmeUserBundle:User object identified by "%s".', $username), 0, $e);
}
return $user;
}
public function refreshUser(UserInterface $user)
{
$class = get_class($user);
if (!$this->supportsClass($class)) {
throw new UnsupportedUserException(
sprintf(
'Instances of "%s" are not supported.',
$class
)
);
}
return $this->find($user->getId());
}
public function supportsClass($class)
{
return $this->getEntityName() === $class || is_subclass_of($class,
13. http://api.symfony.com/2.3/Symfony/Component/Security/Core/User/UserProviderInterface.html
14. http://api.symfony.com/2.3/Symfony/Component/Security/Core/User/UserProviderInterface.html
PDF brought to you by
generated on June 26, 2014
Chapter 61: Comment charger les utilisateurs depuis la base de donnes (le fournisseur d'Entit) |
223
Listing 61-7
Listing 61-8
51
52
$this->getEntityName());
}
}
Pour finir l'implmentation, la configuration de la couche de scurit doit tre modifie pour dire
Symfony d'utiliser le nouveau fournisseur d'entit personnalis la place du fournisseur d'entit
Doctrine gnrique. Ceci est facile raliser en supprimant le champ property dans la section
security.providers.administrators.entity du fichier security.yml.
1
2
3
4
5
6
7
# app/config/security.yml
security:
# ...
providers:
administrators:
entity: { class: AcmeUserBundle:User }
# ...
En faisant cela, la couche de scurit va utiliser une instance de UserRepository et appeler sa mthode
loadUserByUsername() pour rcuprer un utilisateur depuis la base de donnes, qu'il ait saisi son nom
d'utilisateur ou son adresse email.
Grer les rles via la Base de Donnes
La fin de ce tutoriel se concentre sur comment stocker et rcuprer une liste de rles depuis la base de
donnes. Comme prcis prcdemment, lorsque votre utilisateur est charg , sa mthode getRoles()
retourne le tableau contenant ses rles de scurit qui doivent lui tre assigns. Vous pouvez charger ces
donnes depuis n'importe o - une liste code en dur et utilise pour tous les utilisateurs (par exemple
: array('ROLE_USER')), un tableau Doctrine en tant que proprit nomme roles, ou via une relation
Doctrine, comme vous allez le voir dans cette section.
Avec une installation typique, vous devriez toujours retourner au moins 1 rle avec la mthode
getRoles(). Par convention, un rle appel ROLE_USER est gnralement retourn. Si vous ne
russissez pas retourner un quelconque rle, cela voudrait dire que votre utilisateur n'est pas
authentifi du tout.
Dans cet exemple, la classe entit AcmeUserBundle:User dfinit une relation many-to-many avec une
classe entit AcmeUserBundle:Group. Un utilisateur peut tre reli plusieurs groupes et un groupe peut
tre compos d'un ou plusieurs utilisateurs. Comme un groupe est aussi un rle, la mthode prcdente
getRoles() retourne maintenant la liste des groupes relis:
1
2
3
4
5
6
7
8
9
10
11
12
// src/Acme/UserBundle/Entity/User.php
namespace Acme\UserBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
// ...
class User implements AdvancedUserInterface
{
/**
* @ORM\ManyToMany(targetEntity="Group", inversedBy="users")
*
PDF brought to you by
generated on June 26, 2014
Chapter 61: Comment charger les utilisateurs depuis la base de donnes (le fournisseur d'Entit) |
224
Listing 61-9
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
*/
private $groups;
public function __construct()
{
$this->groups = new ArrayCollection();
}
// ...
public function getRoles()
{
return $this->groups->toArray();
}
}
La classe entit AcmeUserBundle:Group dfinit trois champs de table (id, name et role). Le champ
unique role contient le nom du rle utilis par la couche de scurit de Symfony pour scuriser
des parties de l'application. La chose la plus importante noter est que la classe entit
AcmeUserBundle:Group implmente l'interface RoleInterface
15
qui la force avoir une mthode
getRole():
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// src/Acme/Bundle/UserBundle/Entity/Group.php
namespace Acme\UserBundle\Entity;
use Symfony\Component\Security\Core\Role\RoleInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Table(name="acme_groups")
* @ORM\Entity()
*/
class Group implements RoleInterface
{
/**
* @ORM\Column(name="id", type="integer")
* @ORM\Id()
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(name="name", type="string", length=30)
*/
private $name;
/**
* @ORM\Column(name="role", type="string", length=20, unique=true)
*/
private $role;
/**
* @ORM\ManyToMany(targetEntity="User", mappedBy="groups")
*/
private $users;
15. http://api.symfony.com/2.3/Symfony/Component/Security/Core/Role/RoleInterface.html
PDF brought to you by
generated on June 26, 2014
Chapter 61: Comment charger les utilisateurs depuis la base de donnes (le fournisseur d'Entit) |
225
Listing 61-10
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public function __construct()
{
$this->users = new ArrayCollection();
}
// ... getters and setters for each property
/**
* @see RoleInterface
*/
public function getRole()
{
return $this->role;
}
}
Pour amliorer les performances et viter le lazy loading de groupes lors de la rcupration d'un
utilisateur depuis le fournisseur d'entit personnalis, la meilleure solution est d'effectuer une jointure
avec la relation des groupes dans la mthode UserRepository::loadUserByUsername(). Cela va
rcuprer l'utilisateur ainsi que ses rles/groupes associs avec une requte unique:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/Acme/UserBundle/Entity/UserRepository.php
namespace Acme\UserBundle\Entity;
// ...
class UserRepository extends EntityRepository implements UserProviderInterface
{
public function loadUserByUsername($username)
{
$q = $this
->createQueryBuilder('u')
->select('u, g')
->leftJoin('u.groups', 'g')
->where('u.username = :username OR u.email = :email')
->setParameter('username', $username)
->setParameter('email', $username)
->getQuery();
// ...
}
// ...
}
La mthode QueryBuilder::leftJoin() joint et cherche les groupes lis depuis la classe modle
AcmeUserBundle:User lorsqu'un utilisateur est rcupr grce son adresse email ou son nom
d'utilisateur.
PDF brought to you by
generated on June 26, 2014
Chapter 61: Comment charger les utilisateurs depuis la base de donnes (le fournisseur d'Entit) |
226
Listing 62-1
Listing 62-2
Chapter 62
Comment ajouter la fonctionnalit de login
Se souvenir de moi
Une fois qu'un utilisateur est authentifi, ses informations de connexion sont gnralement stockes dans
la session. Cela signifie que lorsque la session se termine, cet utilisateur sera dconnect et devra fournir
de nouveau ses informations de login la prochaine fois qu'il voudra accder l'application. Vous pouvez
laisser aux utilisateurs la possibilit de choisir de rester connect plus longtemps que la dure d'une
session en utilisant un cookie avec l'option pare-feu remember_me. Le pare-feu a besoin d'avoir une cl
secrte configure, qui est utilise pour encrypter le contenu du cookie. Il possde aussi plusieurs options
avec des valeurs par dfaut qui sont dtailles ici :
1
2
3
4
5
6
7
8
# app/config/security.yml
firewalls:
main:
remember_me:
key: "%secret%"
lifetime: 31536000 # 365 jours en secondes
path: /
domain: ~ # Prend la valeur par dfaut du domaine courant depuis $_SERVER
C'est une bonne ide de fournir la possibilit l'utilisateur de choisir s'il veut utiliser la fonctionnalit
remember me ou non, comme cela ne sera pas toujours appropri. La manire usuelle d'effectuer ceci
est d'ajouter une checkbox au formulaire de login. En donnant le nom _remember_me la checkbox
, le cookie va automatiquement tre dfini lorsque la checkbox sera coche et que l'utilisateur se sera
connect avec succs. Donc, votre formulaire de login spcifique pourrait au final ressembler cela :
1
2
3
4
5
6
7
8
{# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #}
{% if error %}
<div>{{ error.message }}</div>
{% endif %}
<form action="{{ path('login_check') }}" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="_username" value="{{ last_username }}" />
PDF brought to you by
generated on June 26, 2014
Chapter 62: Comment ajouter la fonctionnalit de login Se souvenir de moi | 227
Listing 62-3
9
10
11
12
13
14
15
16
17
<label for="password">Password:</label>
<input type="password" id="password" name="_password" />
<input type="checkbox" id="remember_me" name="_remember_me" checked />
<label for="remember_me">Keep me logged in</label>
<input type="submit" name="login" />
</form>
L'utilisateur va donc tre connect automatiquement lors de ses prochaines visites tant que le cookie
restera valide.
Forcer l'utilisateur se r-authentifier avant d'accder certaines ressources
Lorsque l'utilisateur retourne sur votre site, il ou elle est authentifi automatiquement en se basant sur
les informations stockes dans le cookie remember me . Cela permet l'utilisateur d'accder des
ressources protges comme si l'utilisateur s'tait authentifi lors de sa visite sur le site.
Cependant, dans certains cas, vous pourriez vouloir forcer l'utilisateur se r-authentifier avant d'accder
certains ressources. Par exemple, vous pourriez autoriser un utilisateur avec un cookie remember me
voir les informations basiques de son compte, mais par contre vous pourriez lui imposer de se r-
authentifier avant de modifier cette information.
Le composant de scurit fournit une manire simple de faire cela. En plus des rles qui leurs sont
explicitement assigns, les utilisateurs possdent automatiquement l'un des rles suivants dpendant de
la manire dont ils sont authentifis :
IS_AUTHENTICATED_ANONYMOUSLY - automatiquement assign un utilisateur qui se trouve
dans une zone protge du site par un pare-feu mais qui ne s'est pas connect/logu. Cela est
possible uniquement si l'accs anonyme a t autoris.
IS_AUTHENTICATED_REMEMBERED - automatiquement assign un utilisateur qui a t
authentifi via un cookie remember me .
IS_AUTHENTICATED_FULLY - automatiquement assign un utilisateur qui a fourni ses
informations de login durant la session courante.
Vous pouvez utiliser ces rles pour contrler l'accs en plus des autres rles explicitement assigns.
Si vous avez le rle IS_AUTHENTICATED_REMEMBERED, alors vous avez aussi le rle
IS_AUTHENTICATED_ANONYMOUSLY. Si vous avez le rle IS_AUTHENTICATED_FULLY, alors vous
possdez aussi les deux autres rles. En d'autres termes, ces rles reprsentent trois niveaux
croissants de force d'authentification.
Vous pouvez utiliser ces rles additionnels pour effectuer un contrle d'une granularit plus fine sur
l'accs certaines parties d'un site. Par exemple, vous pourriez souhaiter que votre utilisateur soit capable
de voir son compte en se rendant /account lorsqu'il est authentifi par cookie, mais qu'il doive fournir
ses informations de login pour pouvoir diter les dtails de son compte. Vous pouvez effectuer ceci en
scurisant certaines actions d'un contrleur spcifique en utilisant ces rles. L'action edit dans le
contrleur pourrait tre scurise en utilisant le contexte du service.
Dans l'exemple suivant, l'action est autorise seulement si l'utilisateur possde le rle
IS_AUTHENTICATED_FULLY.
PDF brought to you by
generated on June 26, 2014
Chapter 62: Comment ajouter la fonctionnalit de login Se souvenir de moi | 228
Listing 62-4
1
2
3
4
5
6
7
8
9
10
11
12
13
// ...
use Symfony\Component\Security\Core\Exception\AccessDeniedException
public function editAction()
{
if (false === $this->get('security.context')->isGranted(
'IS_AUTHENTICATED_FULLY'
)) {
throw new AccessDeniedException();
}
// ...
}
Vous pouvez aussi choisir d'installer et d'utiliser le bundle optionnel JMSSecurityExtraBundle
1
qui peut
scuriser votre contrleur en utilisant des annotations :
1
2
3
4
5
6
7
8
9
use JMS\SecurityExtraBundle\Annotation\Secure;
/**
* @Secure(roles="IS_AUTHENTICATED_FULLY")
*/
public function editAction($name)
{
// ...
}
Si vous aviez aussi un contrle d'accs dans votre configuration de scurit qui requiert qu'un
utilisateur possde un rle ROLE_USER afin d'accder n'importe quelle partie de la zone account
, alors vous auriez la situation suivante :
Si un utilisateur non-authentifi (ou authentifi anonymement) essaye d'accder la
zone account , il sera demand cet utilisateur de s'authentifier.
Une fois que l'utilisateur a entr son nom d'utilisateur et son mot de passe, et en
supposant que l'utilisateur reoive le rle ROLE_USER par votre configuration, ce dernier
aura le rle IS_AUTHENTICATED_FULLY et sera capable d'accder n'importe quelle page
de la section account , incluant l'action editAction du contrleur.
Enfin, supposons que la session de l'utilisateur se termine ; quand ce dernier retourne
sur le site, il sera capable d'accder chaque page de la partie account - excepte
la page edit - sans tre oblig de se r-authentifier. Cependant, quand il essaye
d'accder l'action editAction du contrleur, il sera oblig de se r-authentifier,
puisqu'il n'est pas (encore) totalement authentifi.
Pour plus d'informations sur la scurisation de services ou de mthodes de cette manire, lisez Comment
scuriser n'importe quel service ou mthode de votre application.
1. https://github.com/schmittjoh/JMSSecurityExtraBundle
PDF brought to you by
generated on June 26, 2014
Chapter 62: Comment ajouter la fonctionnalit de login Se souvenir de moi | 229
Listing 63-1
Chapter 63
Comment implmenter votre propre Voteur
pour ajouter des adresses IP sur une liste noire
Le composant de scurit de Symfony2 fournit plusieurs couches pour authentifier les utilisateurs. L'une
de ces couches est appele un voteur. Un voteur est une classe ddie qui vrifie si l'utilisateur possde
les droits ncessaires pour se connecter l'application. Par exemple, Symfony2 fournit une couche qui
vrifie si l'utilisateur est totalement authentifi ou s'il possde quelques rles attendus.
Il est parfois utile de crer un voteur personnalis pour grer un cas spcifique non-gr par le framework.
Dans cette section, vous allez apprendre comment crer un voteur qui vous permettra d'ajouter des
utilisateurs sur une liste noire suivant leur adresse IP.
L'Interface Voter
Un voteur personnalis doit implmenter VoterInterface
1
, qui requiert les trois mthodes suivantes :
1
2
3
4
5
6
interface VoterInterface
{
function supportsAttribute($attribute);
function supportsClass($class);
function vote(TokenInterface $token, $object, array $attributes);
}
La mthode supportsAttribute() est utilise pour vrifier si le voteur supporte l'attribut de l'utilisateur
donn (c-a-d un rle, une ACL, etc.).
La mthode supportsClass() est utilise pour vrifier si le voteur supporte la classe du token de
l'utilisateur courant.
La mthode vote() doit implmenter la logique mtier qui vrifie si oui ou non un utilisateur est autoris
accder l'application. Cette mthode doit retourner l'une des valeurs suivantes :
VoterInterface::ACCESS_GRANTED: L'utilisateur est autoris accder l'application
1. http://api.symfony.com/2.3/Symfony/Component/Security/Core/Authorization/Voter/VoterInterface.html
PDF brought to you by
generated on June 26, 2014
Chapter 63: Comment implmenter votre propre Voteur pour ajouter des adresses IP sur une liste
noire | 230
Listing 63-2
VoterInterface::ACCESS_ABSTAIN: Le voteur ne peut pas dcider si l'utilisateur est oui ou
non autoris accder l'application
VoterInterface::ACCESS_DENIED: L'utilisateur n'est pas autoris accder l'application
Dans cet exemple, vous allez vrifier si l'adresse IP de l'utilisateur correspond l'une des adresses de la
liste noire. Si c'est le cas, vous retournerez VoterInterface::ACCESS_DENIED, sinon vous retournerez
VoterInterface::ACCESS_ABSTAIN comme le but de ce voteur est uniquement de refuser l'accs, et non
pas de l'autoriser.
Crer un Voteur personnalis
Pour ajouter un utilisateur sur la liste noire en se basant sur son IP, vous pouvez utiliser le service request
et comparer l'adresse IP avec un ensemble d'adresses IP de la liste noire :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// src/Acme/DemoBundle/Security/Authorization/Voter/ClientIpVoter.php
namespace Acme\DemoBundle\Security\Authorization\Voter;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class ClientIpVoter implements VoterInterface
{
public function __construct(ContainerInterface $container, array $blacklistedIp =
array())
{
$this->container = $container;
$this->blacklistedIp = $blacklistedIp;
}
public function supportsAttribute($attribute)
{
// vous n'allez pas vrifier l'attribut de l'utilisateur, alors retournez true
return true;
}
public function supportsClass($class)
{
// votre voteur supporte tous les types de classes de token, donc retournez true
return true;
}
function vote(TokenInterface $token, $object, array $attributes)
{
$request = $this->container->get('request');
if (in_array($request->getClientIp(), $this->blacklistedIp)) {
return VoterInterface::ACCESS_DENIED;
}
return VoterInterface::ACCESS_ABSTAIN;
}
}
C'est tout ! Votre voteur est termin. La prochaine tape est d'injecter le voteur dans la couche de scurit.
Cela peut tre effectu facilement l'aide du conteneur de service.
PDF brought to you by
generated on June 26, 2014
Chapter 63: Comment implmenter votre propre Voteur pour ajouter des adresses IP sur une liste
noire | 231
Listing 63-3
Listing 63-4
Dclarer le Voteur comme service
Pour injecter le voteur dans la couche de scurit, vous devez le dclarer en tant que service, et le tagger
comme un security.voter :
# src/Acme/AcmeBundle/Resources/config/services.yml
services:
security.access.blacklist_voter:
class: Acme\DemoBundle\Security\Authorization\Voter\ClientIpVoter
arguments: [@service_container, [123.123.123.123, 171.171.171.171]]
public: false
tags:
- { name: security.voter }
Soyez sr d'importer ce fichier de configuration depuis le fichier de configuration de votre
application principale (par exemple : app/config/config.yml). Pour plus d'informations, lisez
Importer la Configuration avec imports. Pour en savoir plus concernant la dfinition de services en
gnral, lisez le chapitre Service Container.
Changer la stratgie de dcision d'accs
Afin que votre nouveau voteur soit utilis, vous devez changer la stratgie de dcision d'accs par dfaut,
qui d'habitude autorise l'accs si n'importe quel voteur autorise l'accs.
Dans ce cas, choisissez la stratgie unanimous. Contrairement la stratgie par dfaut affirmative, avec
la stratgie unanimous, si seulement un voteur refuse l'accs (par exemple : le ClientIpVoter), alors
l'accs n'est pas autoris pour l'utilisateur final.
Pour faire cela, surchargez la section par dfaut access_decision_manager du fichier de configuration
de votre application avec le code suivant.
1
2
3
4
5
# app/config/security.yml
security:
access_decision_manager:
# La valeur de Strategy peut tre : affirmative, unanimous ou consensus
strategy: unanimous
C'est tout ! Maintenant, lors de la dcision de savoir si oui ou non un utilisateur devrait avoir accs, le
nouveau voteur va refuser l'accs quiconque possdant une IP qui se trouve dans la liste noire.
PDF brought to you by
generated on June 26, 2014
Chapter 63: Comment implmenter votre propre Voteur pour ajouter des adresses IP sur une liste
noire | 232
Listing 64-1
Chapter 64
Comment utiliser les Access Control Lists
(ACLs) ( liste de contrle d'accs en franais)
Dans les applications complexes, vous allez souvent rencontrer le problme que les dcisions d'accs ne
peuvent pas uniquement se baser sur la personne (Token) qui demande l'accs, mais qu'elles impliquent
aussi un objet domaine auquel l'accs est demand. C'est l o le systme des ACL intervient.
Imaginez que vous tes en train de crer un systme de blog dans lequel vos utilisateurs peuvent
commenter vos posts. Maintenant, vous voulez qu'un utilisateur puisse diter ses propres commentaires,
mais pas ceux d'autres utilisateurs ; en outre, vous voulez vous-mme tre capable d'diter tous les
commentaires. Dans ce scnario, Comment (commentaire) serait l'objet auquel vous souhaitez restreindre
l'accs. Vous pouvez envisager plusieurs approches pour accomplir cela en utilisant Symfony2 ; les deux
approches basiques sont (liste non-exhaustive) :
Forcer la scurit dans vos mthodes mtier : cela signifie garder une rfrence dans chaque
Comment de tous les utilisateurs qui ont accs, et alors de comparer ces utilisateurs avec le
Token fourni.
Forcer la scurit avec des rles : avec cette approche, vous ajouteriez un rle pour chaque objet
Comment, i.e. ROLE_COMMENT_1, ROLE_COMMENT_2, etc.
Les deux approches sont parfaitement valides. Cependant, elles associent votre logique d'autorisation
votre code mtier, ce qui rend le tout moins rutilisable ailleurs, et qui augmente aussi la difficult
d'effectuer des tests unitaires. En outre, vous pourriez rencontrer des problmes de performance si
beaucoup d'utilisateurs accdaient un mme et unique objet domaine.
Heureusement, il y a une meilleure faon de faire, que vous allez dcouvrir maintenant.
Initialisation ( Bootstrapping en anglais)
Maintenant, avant que vous puissiez finalement passer l'action, vous avez besoin d'effectuer certaines
initialisations. Premirement, vous devez configurer la connexion que le systme d'ACL est suppos
utiliser :
PDF brought to you by
generated on June 26, 2014
Chapter 64: Comment utiliser les Access Control Lists (ACLs) ( liste de contrle d'accs en
franais) | 233
Listing 64-2
Listing 64-3
1
2
3
4
# app/config/security.yml
security:
acl:
connection: default
Le systme ACL requiert qu'une connexion DBAL Doctrine (utilisable par dfaut) ou qu'une
connexion ODM Doctrine (utilisable avec MongoDBAclBundle
1
) soit configure. Cependant, cela
ne veut pas dire que vous devez utiliser l'ORM ou l'ODM Doctrine pour faire correspondre vos
objets. Vous pouvez utiliser l'outil de correspondance de votre choix pour vos objets, que ce soit
l'ORM Doctrine, l'ODM Mongo, Propel, ou du SQL brut, le choix reste le vtre.
Une fois la connexion configure, vous devez importer la structure de la base de donnes. Heureusement,
il existe une tche pour cela. Excutez simplement la commande suivante :
1 $ php app/console init:acl
Dmarrage
Revenez ce petit exemple du dbut et implmentez ses ACLs.
Crer un ACL, et ajouter un ACE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// src/Acme/DemoBundle/Controller/BlogController.php
namespace Acme\DemoBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
use Symfony\Component\Security\Acl\Permission\MaskBuilder;
class BlogController
{
// ...
public function addCommentAction(Post $post)
{
$comment = new Comment();
// prparation du $form et liaison (bind) des donnes
if ($form->isValid()) {
$entityManager = $this->get('doctrine.orm.default_entity_manager');
$entityManager->persist($comment);
$entityManager->flush();
// cration de l'ACL
$aclProvider = $this->get('security.acl.provider');
$objectIdentity = ObjectIdentity::fromDomainObject($comment);
1. https://github.com/IamPersistent/MongoDBAclBundle
PDF brought to you by
generated on June 26, 2014
Chapter 64: Comment utiliser les Access Control Lists (ACLs) ( liste de contrle d'accs en
franais) | 234
Listing 64-4
28
29
30
31
32
33
34
35
36
37
38
39
40
$acl = $aclProvider->createAcl($objectIdentity);
// retrouve l'identifiant de scurit de l'utilisateur actuellement connect
$securityContext = $this->get('security.context');
$user = $securityContext->getToken()->getUser();
$securityIdentity = UserSecurityIdentity::fromAccount($user);
// donne accs au propritaire
$acl->insertObjectAce($securityIdentity, MaskBuilder::MASK_OWNER);
$aclProvider->updateAcl($acl);
}
}
}
Il y a plusieurs dcisions d'implmentation importantes dans ce petit bout de code. Pour le moment, je
veux mettre en vidence seulement deux d'entre elles :
Tout d'abord, vous avez peut-tre remarqu que ->createAcl() n'accepte pas d'objets de domaine
directement, mais uniquement des implmentations de ObjectIdentityInterface. Cette tape
additionnelle d'indirection vous permet de travailler avec les ACLs mme si vous n'avez pas d'instance
d'objet domaine sous la main. Cela va tre extrmement utile si vous voulez vrifier les permissions pour
un grand nombre d'objets sans avoir les dsrialiser.
L'autre partie intressante est l'appel ->insertObjectAce(). Dans l'exemple, vous accordez
l'utilisateur connect un accs propritaire au Comment. Le MaskBuilder::MASK_OWNER est un masque
binaire prdfini ; ne vous inquitez pas, le constructeur de masque va abstraire la plupart des dtails
techniques, mais en utilisant cette technique vous pouvez stocker plein de permissions diffrentes dans
une mme ligne de base de donnes ; ce qui vous offre un avantage considrable au niveau performance.
L'ordre dans lequel les ACEs sont vrifies est important. En tant que rgle gnrale, vous devriez
placer les entres les plus spcifiques au dbut.
Vrification des Accs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/Acme/DemoBundle/Controller/BlogController.php
// ...
class BlogController
{
// ...
public function editCommentAction(Comment $comment)
{
$securityContext = $this->get('security.context');
// check for edit access
if (false === $securityContext->isGranted('EDIT', $comment))
{
throw new AccessDeniedException();
}
// ... rcuprez le bon objet comment , et ditez-le ici
PDF brought to you by
generated on June 26, 2014
Chapter 64: Comment utiliser les Access Control Lists (ACLs) ( liste de contrle d'accs en
franais) | 235
Listing 64-5
Listing 64-6
20
21
}
}
Dans cet exemple, vous vrifiez que l'utilisateur possde la permission EDIT. En interne, Symfony2 fait
correspondre la permission avec plusieurs masques binaires, et vrifie si l'utilisateur possde l'un d'entre
eux.
Vous pouvez dfinir jusqu' 32 permissions de base (dpendant du PHP de votre OS, cela pourrait
varier entre 30 et 32). De plus, vous pouvez aussi dfinir des permissions cumules.
Permissions Cumules
Dans le premier exemple ci-dessus, vous avez accord uniquement la permission basique OWNER
l'utilisateur. Bien que cela permette aussi l'utilisateur d'effectuer n'importe quelle opration telle que
la lecture, l'dition, etc. sur l'objet domaine, il y a des cas o vous voudrez accorder ces permissions
explicitement.
Le MaskBuilder peut tre utilis pour crer des masques binaires facilement en combinant plusieurs
permissions de base :
1
2
3
4
5
6
7
8
$builder = new MaskBuilder();
$builder
->add('view')
->add('edit')
->add('delete')
->add('undelete')
;
$mask = $builder->get(); // int(29)
Ce masque binaire reprsent par un entier peut ainsi tre utilis pour accorder un utilisateur les
permissions de base que vous avez ajoutes ci-dessus :
1
2
$identity = new UserSecurityIdentity('johannes', 'Acme\UserBundle\Entity\User');
$acl->insertObjectAce($identity, $mask);
L'utilisateur a dsormais le droit de lire, diter, supprimer, et annuler une suppression sur des objets.
PDF brought to you by
generated on June 26, 2014
Chapter 64: Comment utiliser les Access Control Lists (ACLs) ( liste de contrle d'accs en
franais) | 236
Chapter 65
Comment utiliser les concepts d'ACL avancs
Le but de ce chapitre est de donner une vision plus profonde du systme des ACL ( Liste de Contrle
d'Accs en franais), et aussi d'expliquer certaines dcisions prises en ce qui concerne sa conception.
Concepts d'Architecture
Les capacits de scurit de l'instance objet de Symfony2 sont bases sur le concept d'une Access
Control List . Chaque instance d'un objet domaine a sa propre ACL. L'instance ACL contient une
liste dtaille des Access Control Entries (ACEs ou Entres de Contrle d'Accs en franais) qui
sont utilises pour prendre les dcisions d'accs. Le systme d'ACL de Symfony2 se concentre sur deux
objectifs :
fournir un moyen de rcuprer de manire efficace un grand nombre d'ACLs/ACEs pour vos
objets domaine, et de les modifier ;
fournir un moyen de prendre les dcisions facilement quant savoir si une personne est
autorise effectuer une action sur un objet domaine ou non.
Comme spcifi dans le premier point, l'une des principales facults du systme ACL de Symfony2 est
de fournir une manire trs performante de rcuprer des ACLs/ACEs. Ceci est extrmement important
sachant que chaque ACL pourrait avoir plusieurs ACEs, et hriter d'une autre ACL la manire d'une
structure en arbre. Donc, nous ne nous servons pas d'un ORM spcifique, mais l'implmentation par
dfaut intragit avec votre connexion en utilisant directement le DBAL de Doctrine.
Identits d'Objet
Le systme ACL est compltement dcoupl de vos objets domaine. Ils ne doivent mme pas tre stocks
dans la mme base de donnes, ou sur le mme serveur. Pour pouvoir accomplir ce dcouplage, vos
objets sont reprsents dans le systme ACL par des objets d'identit d'objet. Chaque fois que vous voulez
rcuprer une ACL pour un objet domaine, le systme ACL va d'abord crer une identit d'objet pour
votre objet domaine, et va ensuite passer cette identit d'objet au fournisseur d'ACL pour un traitement
ultrieur.
PDF brought to you by
generated on June 26, 2014
Chapter 65: Comment utiliser les concepts d'ACL avancs | 237
Identits de Scurit
Ceci est similaire l'identit d'objet, mais reprsente un utilisateur, ou un rle dans votre application.
Chaque rle ou utilisateur possde sa propre identit de scurit.
Structure de Table dans la Base de Donnes
L'implmentation par dfaut utilise cinq tables de base de donnes qui sont listes ci-dessous. Les tables
sont classes par nombre de lignes, de celle contenant le moins de lignes celle en contenant le plus dans
une application classique :
acl_security_identities : Cette table enregistre toutes les identits de scurit (SID) qui
dtiennent les ACEs. L'implmentation par dfaut est fournie avec deux identits de scurit :
RoleSecurityIdentity, et UserSecurityIdentity ;
acl_classes : Cette table fait correspondre les noms de classe avec un id unique qui peut tre
rfrenc depuis d'autres tables ;
acl_object_identities : Chaque ligne dans cette table reprsente une unique instance d'objet
domaine ;
acl_object_identity_ancestors : Cette table nous autorise dterminer tous les anctres d'une
ACL de manire trs efficace ;
acl_entries : Cette table contient toutes les ACEs. C'est typiquement la table avec le plus de
lignes. Elle peut en contenir des dizaines de millions sans impacter de faon significative les
performances.
Porte des Access Control Entries
Les entres de contrle d'accs peuvent avoir diffrentes portes dans lesquelles elles s'appliquent. Dans
Symfony2, il existe principalement deux portes diffrentes :
Porte de la Classe : Ces entres s'appliquent tous les objets ayant la mme classe.
Porte de l'Objet : Ceci est la porte utilise dans le chapitre prcdent, et elle s'applique
uniquement un objet spcifique.
Parfois, vous aurez besoin d'appliquer une ACE uniquement sur le champ spcifique d'un objet.
Supposons que vous voulez que l'ID soit uniquement visible par un administrateur mais pas par votre
service client. Pour solutionner ce problme commun, deux sous-portes supplmentaires ont t
ajoutes :
Porte d'un Champ de Classe : Ces entres s'appliquent tous les objets ayant la mme classe,
mais uniquement un champ spcifique de ces objets.
Porte d'un Champ d'Objet : Ces entres s'appliquent un objet spcifique, et uniquement
un champ spcifique de cet objet.
Dcisions de pr-autorisation
Pour les dcisions de pr-autorisation, que ce soit des dcisions prises avant quelconque mthode ou
bien une action scurise qui est invoque, le service AccessDecisionManager est utilis. Ce service
est galement utilis pour connatre les dcisions d'autorisation bases sur des rles. Comme les rles,
le systme d'ACL ajoute plusieurs nouveaux attributs qui pourraient tre utiliss pour vrifier diffrentes
permissions.
PDF brought to you by
generated on June 26, 2014
Chapter 65: Comment utiliser les concepts d'ACL avancs | 238
Table de Permission Intgre
Attribut Signification Masques Binaires
VIEW Si quelqu'un est autoris voir
l'objet domaine.
VIEW, EDIT, OPERATOR,
MASTER, or OWNER
EDIT Si quelqu'un est autoris effectuer
des changements sur l'objet
domaine.
EDIT, OPERATOR, MASTER, or
OWNER
CREATE Si quelqu'un est autoris crer
l'objet domaine.
CREATE, OPERATOR, MASTER, or
OWNER
DELETE Si quelqu'un est autoris
supprimer l'objet domaine.
DELETE, OPERATOR, MASTER, or
OWNER
UNDELETE Si quelqu'un est autoris restaurer
un objet domaine prcdemment
supprim.
UNDELETE, OPERATOR,
MASTER, or OWNER
OPERATOR Si quelqu'un est autoris effectuer
toutes les actions ci-dessus.
OPERATOR, MASTER, or OWNER
MASTER Si quelqu'un est autoris effectuer
toutes les actions ci-dessus, et en
plus a le droit d'affecter n'importe
laquelle d'entre elles quelqu'un
d'autre.
MASTER, or OWNER
OWNER Si quelqu'un possde l'objet
domaine. Un propritaire peut
effectuer n'importe laquelle des
actions ci-dessus et affecter les
permissions master et owner.
OWNER
Attributs de Permission vs. Masques Binaires de Permission
Les attributs sont utiliss par l' AccessDecisionManager , tout comme les rles. Souvent, ces attributs
reprsentent en fait une agrgation de masques binaires. D'un autre ct, les masques binaires sous forme
d'entier sont utiliss par le systme d'ACL en interne pour stocker de manire efficace les permissions de
vos utilisateurs dans la base de donnes, et pour effectuer des vrifications en utilisant des oprations sur
les masques binaires extrmement rapides.
Extensibilit
La table de permissions ci-dessus n'est en rien statique, et pourrait thoriquement tre compltement
remplace. Cependant, elle devrait couvrir la plupart des problmes que vous pourriez rencontrer,
et pour des raisons d'introprabilit avec d'autres bundles, vous tes encourag conserver les
significations initialement prvues pour ces permissions.
Dcisions de post-autorisation
Les dcisions de post-autorisation sont effectues aprs qu'une mthode scurise a t invoque, et
impliquent typiquement l'objet domaine qui est retourn par une telle mthode. Aprs invocations, les
fournisseurs permettent aussi de modifier, ou de filtrer l'objet domaine avant qu'il ne soit retourn.
PDF brought to you by
generated on June 26, 2014
Chapter 65: Comment utiliser les concepts d'ACL avancs | 239
A cause de limitations actuelles du langage PHP, il n'y a pas de fonctionnalits de post-autorisation
implmentes dans le composant coeur Security . Nanmoins, il y a un bundle exprimental appel
JMSSecurityExtraBundle
1
qui ajoute ces fonctionnalits. Lisez sa documentation pour avoir plus
d'informations pour comprendre comment ceci est ralis.
Processus pour connatre les dcisions d'autorisation
La classe ACL fournit deux mthodes pour dterminer si une identit de scurit possde les masques
binaires requis, isGranted et isFieldGranted. Lorsque l'ACL reoit une requte d'autorisation travers
l'une de ces mthodes, elle dlgue cette requte une implmentation de PermissionGrantingStrategy
. Cela vous permet de remplacer la manire dont les dcisions d'accs sont atteintes sans modifier la
classe ACL elle-mme.
La PermissionGrantingStrategy vrifie en premier toutes les ACEs de vos portes d'objet ; si aucune
n'est applicable, les ACEs de vos portes de classe vont tre vrifies, et si aucune n'est applicable, alors
le processus va tre rpt avec les ACEs du parent de l'ACL. Si aucun parent de l'ACL n'existe, une
exception sera lance.
1. https://github.com/schmittjoh/JMSSecurityExtraBundle
PDF brought to you by
generated on June 26, 2014
Chapter 65: Comment utiliser les concepts d'ACL avancs | 240
Listing 66-1
Listing 66-2
Chapter 66
Comment forcer HTTPS ou HTTP pour des URLs
Diffrentes
Vous pouvez forcer certaines parties de votre site utiliser le protocole HTTPS dans la configuration de
la scurit. Cela s'effectue grce aux rgles access_control en utilisant l'option requires_channel. Par
exemple, si vous voulez forcer toutes les URLs commenant par /secure utiliser HTTPS, alors vous
pourriez utiliser la configuration suivante :
1
2
3
4
access_control:
- path: ^/secure
roles: ROLE_ADMIN
requires_channel: https
Le formulaire de login lui-mme a besoin d'autoriser un accs anonyme, sinon les utilisateurs seront
incapables de s'authentifier. Pour le forcer utiliser HTTPS vous pouvez toujours utiliser les rgles de
access_control en vous servant du rle IS_AUTHENTICATED_ANONYMOUSLY :
1
2
3
4
access_control:
- path: ^/login
roles: IS_AUTHENTICATED_ANONYMOUSLY
requires_channel: https
Il est aussi possible de spcifier l'utilisation d'HTTPS dans la configuration de routage, lisez Comment
forcer les routes toujours utiliser HTTPS ou HTTP pour plus de dtails.
PDF brought to you by
generated on June 26, 2014
Chapter 66: Comment forcer HTTPS ou HTTP pour des URLs Diffrentes | 241
Listing 67-1
Chapter 67
Comment personnaliser votre formulaire de
login
Utiliser un formulaire de login est une mthode classique et flexible pour grer l'authentification dans
Symfony2. Quasiment chaque aspect du formulaire de login peut tre personnalis. La configuration
complte par dfaut est dtaille dans la prochaine section.
Rfrence de Configuration du formulaire de login
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# app/config/security.yml
security:
firewalls:
main:
form_login:
# l'utilisateur est redirig ici quand il/elle a besoin de se connecter
login_path: /login
# si dfini true, forward l'utilisateur vers le formulaire de
# login au lieu de le rediriger
use_forward: false
# soumet le formulaire de login vers cette URL
check_path: /login_check
# par dfaut, le formulaire de login *doit* tre un POST,
# et pas un GET
post_only: true
# options de redirection lorsque le login a russi (vous
# pouvez en lire plus ci-dessous)
always_use_default_target_path: false
default_target_path: /
target_path_parameter: _target_path
PDF brought to you by
generated on June 26, 2014
Chapter 67: Comment personnaliser votre formulaire de login | 242
Listing 67-2
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
use_referer: false
# options de redirection lorsque le login choue (vous
# pouvez en lire plus ci-dessous)
failure_path: null
failure_forward: false
# noms des champs pour le nom d'utilisateur et le mot
# de passe
username_parameter: _username
password_parameter: _password
# options du token csrf
csrf_parameter: _csrf_token
intention: authenticate
Rediriger aprs un succs
Vous pouvez changer l'URL de redirection aprs que le formulaire de login a t soumis avec succs
via plusieurs options de configuration. Par dfaut, le formulaire va rediriger l'utilisateur vers l'URL
qu'il a demande (c-a-d l'URL qui a dclenche le formulaire de login qui est montr). Par exemple,
si l'utilisateur a demand http://www.example.com/admin/post/18/edit, alors aprs, il sera
ventuellement redirig vers http://www.example.com/admin/post/18/edit dans le cas d'un succs de
connexion. Cela est effectu en stockant l'URL demande dans la session. Si aucune URL n'est prsente
dans la session (peut-tre que l'utilisateur a t directement sur la page de login), alors l'utilisateur est
redirig vers la page par dfaut, qui est / (c-a-d. la page d'accueil) par dfaut. Vous pouvez changer ce
comportement de diffrentes faons.
Comme prcis, par dfaut, l'utilisateur est redirig vers la page qu'il avait demande la base.
Quelquefois, cela peut poser des problmes, comme par exemple si une requte AJAX en arrire-
plan apparat comme tant la dernire URL visite, redirigeant l'utilisateur vers cette dernire.
Pour plus d'informations sur comment contrler ce comportement, lisez Comment changer le
comportement par dfaut du chemin cible.
Changer la page par dfaut
Tout d'abord, la page par dfaut peut tre dfinie (c-a-d la page vers laquelle l'utilisateur est redirige
si aucune page n'avait t prcdemment stocke dans la session). Pour la dfinir en tant que /admin,
utilisez la configuration suivante :
1
2
3
4
5
6
7
# app/config/security.yml
security:
firewalls:
main:
form_login:
# ...
default_target_path: /admin
Maintenant, quand aucune URL n'est dfinie dans la session, l'utilisateur va tre envoy vers /admin.
PDF brought to you by
generated on June 26, 2014
Chapter 67: Comment personnaliser votre formulaire de login | 243
Listing 67-3
Listing 67-4
Listing 67-5
Toujours rediriger vers la page par dfaut
Vous pouvez faire en sorte que les utilisateurs soient toujours redirigs vers la page par dfaut sans tenir
compte de l'URL qu'ils avaient demande en dfinissant l'option always_use_default_target_path
true :
1
2
3
4
5
6
7
# app/config/security.yml
security:
firewalls:
main:
form_login:
# ...
always_use_default_target_path: true
Utiliser l'URL rfrante
Dans le cas o aucune URL n'a t stocke dans la session, vous pourriez souhaiter essayer d'utiliser
HTTP_REFERER la place, comme ce dernier sera souvent identique. Vous pouvez effectuer cela en
dfinissant use_referer true (par dfaut la valeur est false ) :
1
2
3
4
5
6
7
# app/config/security.yml
security:
firewalls:
main:
form_login:
# ...
use_referer: true
New in version 2.1: Depuis la version 2.1, si le rfrant est gal l'option login_path, l'utilisateur
sera redirig vers le default_target_path.
Contrler l'URL de redirection depuis le formulaire
Vous pouvez aussi surcharger le chemin vers lequel l'utilisateur est redirig via le formulaire lui-mme en
incluant un champ cach avec le nom _target_path. Par exemple, pour rediriger vers l'URL dfinie par
une route account, utilisez ce qui suit :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #}
{% if error %}
<div>{{ error.message }}</div>
{% endif %}
<form action="{{ path('login_check') }}" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="_username" value="{{ last_username }}" />
<label for="password">Password:</label>
<input type="password" id="password" name="_password" />
<input type="hidden" name="_target_path" value="account" />
<input type="submit" name="login" />
</form>
PDF brought to you by
generated on June 26, 2014
Chapter 67: Comment personnaliser votre formulaire de login | 244
Listing 67-6
Listing 67-7
Maintenant, l'utilisateur va tre redirig vers la valeur du champ cach du formulaire. La valeur de
l'attribut peut tre un chemin relatif, une URL absolue, ou un nom de route. Vous pouvez mme changer
le nom du champ cach du formulaire en changeant l'option target_path_parameter.
1
2
3
4
5
6
# app/config/security.yml
security:
firewalls:
main:
form_login:
target_path_parameter: redirect_url
Redirection en cas d'chec du login
En plus de la redirection lorsqu'un utilisateur russit se connecter, vous pouvez aussi dfinir l'URL
vers laquelle l'utilisateur devrait tre redirig aprs un chec lors de la phase de login (par exemple :
un nom d'utilisateur ou mot de passe non-valide a t soumis). Par dfaut, l'utilisateur est redirig vers
le formulaire de login lui-mme. Vous pouvez dfinir une URL diffrente en utilisant la configuration
suivante :
1
2
3
4
5
6
7
# app/config/security.yml
security:
firewalls:
main:
form_login:
# ...
failure_path: /login_failure
PDF brought to you by
generated on June 26, 2014
Chapter 67: Comment personnaliser votre formulaire de login | 245
Listing 68-1
Listing 68-2
Chapter 68
Comment scuriser n'importe quel service ou
mthode de votre application
Dans le chapitre sur la scurit, vous pouvez voir comment scuriser un contrleur en rcuprant le service
security.context depuis le Conteneur de Service et en vrifiant le rle actuel de l'utilisateur:
1
2
3
4
5
6
7
8
9
10
11
// ...
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
public function helloAction($name)
{
if (false === $this->get('security.context')->isGranted('ROLE_ADMIN')) {
throw new AccessDeniedException();
}
// ...
}
Vous pouvez aussi scuriser n'importe quel service d'une manire similaire en lui injectant le service
security.context. Pour une introduction gnrale sur l'injection de dpendances dans un service,
voyez le chapitre Service Container du book. Par exemple, supposons que vous ayez une classe
NewsletterManager qui envoie des emails et que vous souhaitiez restreindre son utilisation aux
utilisateurs ayant un rle nomm ROLE_NEWSLETTER_ADMIN uniquement. Avant l'ajout de cette scurit,
la classe ressemble ceci :
1
2
3
4
5
6
7
8
9
// src/Acme/HelloBundle/Newsletter/NewsletterManager.php
namespace Acme\HelloBundle\Newsletter;
class NewsletterManager
{
public function sendNewsletter()
{
// c'est ici que vous effectuez le travail
PDF brought to you by
generated on June 26, 2014
Chapter 68: Comment scuriser n'importe quel service ou mthode de votre application | 246
Listing 68-3
Listing 68-4
Listing 68-5
10
11
12
13
}
// ...
}
Votre but est de vrifier le rle de l'utilisateur lorsque la mthode sendNewsletter() est appele. La
premire tape pour y parvenir est d'injecter le service security.context dans l'objet. Sachant qu'il
serait assez risqu de ne pas effectuer de vrification de scurit, nous avons ici un candidat idal pour
l'injection via le constructeur, qui garantit que l'objet du contexte de scurit sera disponible dans la
classe NewsletterManager:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace Acme\HelloBundle\Newsletter;
use Symfony\Component\Security\Core\SecurityContextInterface;
class NewsletterManager
{
protected $securityContext;
public function __construct(SecurityContextInterface $securityContext)
{
$this->securityContext = $securityContext;
}
// ...
}
Puis, dans votre configuration de service, vous pouvez injecter le service :
# src/Acme/HelloBundle/Resources/config/services.yml
parameters:
newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager
services:
newsletter_manager:
class: "%newsletter_manager.class%"
arguments: [@security.context]
Le service inject peut ds lors tre utilis pour effectuer la vrification de scurit lorsque la mthode
sendNewsletter() est appele:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace Acme\HelloBundle\Newsletter;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\SecurityContextInterface;
// ...
class NewsletterManager
{
protected $securityContext;
public function __construct(SecurityContextInterface $securityContext)
{
$this->securityContext = $securityContext;
}
public function sendNewsletter()
PDF brought to you by
generated on June 26, 2014
Chapter 68: Comment scuriser n'importe quel service ou mthode de votre application | 247
Listing 68-6
Listing 68-7
17
18
19
20
21
22
23
24
25
26
{
if (false === $this->securityContext->isGranted('ROLE_NEWSLETTER_ADMIN')) {
throw new AccessDeniedException();
}
//...
}
// ...
}
Si l'utilisateur actuel ne possde pas le rle ROLE_NEWSLETTER_ADMIN, il lui sera demand de se connecter.
Scuriser des mthodes en utilisant des annotations
Vous pouvez aussi scuriser des appels de mthodes dans n'importe quel service avec des annotations
en utilisant le bundle facultatif JMSSecurityExtraBundle
1
. Ce bundle n'est pas inclus dans la Distribution
Standard de Symfony2, mais vous pouvez choisir de l'installer.
Pour activer la fonctionnalit des annotations, taggez le service que vous voulez scuriser avec le tag
security.secure_service (vous pouvez aussi activer automatiquement cette fonctionnalit pour tous
les services, voir l'encadr ci-dessous) :
1
2
3
4
5
6
7
8
# src/Acme/HelloBundle/Resources/config/services.yml
# ...
services:
newsletter_manager:
# ...
tags:
- { name: security.secure_service }
Vous pouvez ainsi parvenir aux mmes rsultats que ci-dessus en utilisant une annotation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace Acme\HelloBundle\Newsletter;
use JMS\SecurityExtraBundle\Annotation\Secure;
// ...
class NewsletterManager
{
/**
* @Secure(roles="ROLE_NEWSLETTER_ADMIN")
*/
public function sendNewsletter()
{
//...
}
// ...
}
1. https://github.com/schmittjoh/JMSSecurityExtraBundle
PDF brought to you by
generated on June 26, 2014
Chapter 68: Comment scuriser n'importe quel service ou mthode de votre application | 248
Listing 68-8
Les annotations fonctionnent car une classe proxy est cre pour votre classe qui effectue les
vrifications de scurit. Cela signifie que vous pouvez utiliser les annotations sur des mthodes
public ou protected , mais que vous ne pouvez pas les utiliser avec des mthodes private
ou avec des mthodes marques comme final
Le JMSSecurityExtraBundle vous permet aussi de scuriser les paramtres et les valeurs retournes par
les mthodes. Pour plus d'informations, lisez la documentation du JMSSecurityExtraBundle
2
.
Activer la Fonctionnalit des Annotations pour tous les Services
Quand vous scurisez la mthode d'un service (comme expliqu ci-dessus), vous pouvez soit tagger
chaque service individuellement, ou activer la fonctionnalit pour tous les services en une seule
fois. Pour ce faire, dfinissez l'option de configuration secure_all_services true :
1
2
3
4
# app/config/config.yml
jms_security_extra:
# ...
secure_all_services: true
L'inconvnient de cette mthode est que, si elle est active, le chargement initial de la page pourrait
tre trs lent selon le nombre de services que vous avez dfini.
2. https://github.com/schmittjoh/JMSSecurityExtraBundle
PDF brought to you by
generated on June 26, 2014
Chapter 68: Comment scuriser n'importe quel service ou mthode de votre application | 249
Listing 69-1
Chapter 69
Comment crer un Fournisseur d'Utilisateur
personnalis
Une partie du processus d'authentification standard de Symfony dpend des fournisseurs d'utilisateur
. Lorsqu'un utilisateur soumet un nom d'utilisateur et un mot de passe, la couche d'authentification
demande au fournisseur d'utilisateur configur de retourner un objet utilisateur pour un nom d'utilisateur
donn. Symfony vrifie alors si le mot de passe de cet utilisateur est correct ou non et gnre un token
de scurit afin que l'utilisateur reste authentifi pendant la session courante. Symfony possde par
dfaut un fournisseur d'utilisateur in_memory et entity . Dans cet article, vous verrez comment
vous pouvez crer votre propre fournisseur d'utilisateur, ce qui pourrait tre utile si vous accdez vos
utilisateurs via une base de donnes personnalise, un fichier, ou - comme le montre cet exemple -
travers un service web.
Crer une Classe Utilisateur
Tout d'abord, quelle que soit la source de vos donnes utilisateurs, vous allez avoir besoin de crer
une classe User qui reprsente ces donnes. Le User peut ressembler ce que vous voulez et contenir
n'importe quelles donnes. La seule condition requise est que la classe implmente UserInterface
1
.
Les mthodes de cette interface doivent donc tre dfinies dans la classe utilisateur personnalise :
getRoles(), getPassword(), getSalt(), getUsername(), eraseCredentials(), equals().
Voyons cela en action:
1
2
3
4
5
6
7
8
// src/Acme/WebserviceUserBundle/Security/User/WebserviceUser.php
namespace Acme\WebserviceUserBundle\Security\User;
use Symfony\Component\Security\Core\User\UserInterface;
class WebserviceUser implements UserInterface
{
private $username;
1. http://api.symfony.com/2.3/Symfony/Component/Security/Core/User/UserInterface.html
PDF brought to you by
generated on June 26, 2014
Chapter 69: Comment crer un Fournisseur d'Utilisateur personnalis | 250
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
private $password;
private $salt;
private $roles;
public function __construct($username, $password, $salt, array $roles)
{
$this->username = $username;
$this->password = $password;
$this->salt = $salt;
$this->roles = $roles;
}
public function getRoles()
{
return $this->roles;
}
public function getPassword()
{
return $this->password;
}
public function getSalt()
{
return $this->salt;
}
public function getUsername()
{
return $this->username;
}
public function eraseCredentials()
{
}
public function equals(UserInterface $user)
{
if (!$user instanceof WebserviceUser) {
return false;
}
if ($this->password !== $user->getPassword()) {
return false;
}
if ($this->getSalt() !== $user->getSalt()) {
return false;
}
if ($this->username !== $user->getUsername()) {
return false;
}
return true;
}
}
PDF brought to you by
generated on June 26, 2014
Chapter 69: Comment crer un Fournisseur d'Utilisateur personnalis | 251
Listing 69-2
Si vous avez plus d'informations propos de vos utilisateurs - comme un prnom - alors vous pouvez
ajouter un champ firstName pour contenir cette donne.
Pour plus de dtails sur chacune de ces mthodes, voir l'interface UserInterface
2
.
Crer un Fournisseur d'Utilisateur
Maintenant que vous avez une classe User, vous allez crer un fournisseur d'utilisateur qui va rcuprer
les informations utilisateur depuis un service web, et nous allons aussi crer un objet WebserviceUser et
le remplir avec des donnes.
Le fournisseur d'utilisateur est juste une classe PHP qui doit implmenter UserProviderInterface
3
,
qui requiert que trois mthodes soient dfinies : loadUserByUsername($username),
refreshUser(UserInterface $user), et supportsClass($class). Pour plus de dtails, voir l'interface
UserProviderInterface
4
.
Voici un exemple de ce quoi cela pourrait ressembler:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// src/Acme/WebserviceUserBundle/Security/User/WebserviceUserProvider.php
namespace Acme\WebserviceUserBundle\Security\User;
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 WebserviceUserProvider implements UserProviderInterface
{
public function loadUserByUsername($username)
{
// effectuez un appel votre service web ici
$userData = ...
// supposons qu'il retourne un tableau en cas de succs, ou bien
// false s'il n'y a pas d'utilisateur
if ($userData) {
$password = '...';
// ...
return new WebserviceUser($username, $password, $salt, $roles)
}
throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.',
$username));
}
public function refreshUser(UserInterface $user)
{
if (!$user instanceof WebserviceUser) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not
supported.', get_class($user)));
}
return $this->loadUserByUsername($user->getUsername());
2. http://api.symfony.com/2.3/Symfony/Component/Security/Core/User/UserInterface.html
3. http://api.symfony.com/2.3/Symfony/Component/Security/Core/User/UserProviderInterface.html
4. http://api.symfony.com/2.3/Symfony/Component/Security/Core/User/UserProviderInterface.html
PDF brought to you by
generated on June 26, 2014
Chapter 69: Comment crer un Fournisseur d'Utilisateur personnalis | 252
Listing 69-3
Listing 69-4
Listing 69-5
38
39
40
41
42
}
public function supportsClass($class)
{
return $class === 'Acme\WebserviceUserBundle\Security\User\WebserviceUser';
}
}
Crer un Service pour le Fournisseur d'Utilisateur
Maintenant, vous allez rendre le fournisseur d'utilisateur disponible en tant que service.
1
2
3
4
5
6
7
# src/Acme/WebserviceUserBundle/Resources/config/services.yml
parameters:
webservice_user_provider.class:
Acme\WebserviceUserBundle\Security\User\WebserviceUserProvider
services:
webservice_user_provider:
class: "%webservice_user_provider.class%"
L'implmentation relle du fournisseur d'utilisateur aura probablement certaines dpendances, des
options de configuration ou d'autres services. Ajoutez ces derniers en tant qu'arguments dans la
dfinition du service.
Assurez-vous que le fichier des services est import. Lisez Importer la Configuration avec imports
pour plus de dtails.
Modifier security.yml
Dans le fichier /app/config/security.yml, tout vient ensemble. Ajoutez le fournisseur d'utilisateur la
liste des fournisseurs dans la section security . Choisissez un nom pour le fournisseur d'utilisateur (par
exemple : webservice ) et spcifiez l'id du service que vous venez de dfinir.
1
2
3
4
security:
providers:
webservice:
id: webservice_user_provider
Symfony a aussi besoin de savoir comment encoder les mots de passe qui sont soumis par les utilisateurs
du site web, par exemple lorsque ces derniers remplissent un formulaire de login. Vous pouvez effectuer
cela en ajoutant une ligne dans la section encoders dans le fichier /app/config/security.yml.
1
2
3
security:
encoders:
Acme\WebserviceUserBundle\Security\User\WebserviceUser: sha512
PDF brought to you by
generated on June 26, 2014
Chapter 69: Comment crer un Fournisseur d'Utilisateur personnalis | 253
Listing 69-6
La valeur spcifie ici devrait correspondre l'algorithme utilis initialement pour l'encodage des mots de
passe lors de la cration de vos utilisateurs (qu'importe la manire dont vous avez cr ces utilisateurs).
Quand un utilisateur soumet son mot de passe, ce dernier est rajout la valeur du salt et puis encod
en utilisant cet algorithme avant d'tre compar avec le mot de passe crypt retourn par votre mthode
getPassword(). De plus, en fonction de vos options, le mot de passe pourrait tre encod plusieurs fois
et encod ensuite en base64.
Dtails sur la manire dont les mots de passe sont encrypts
Symfony utilise une mthode spcifique pour combiner le salt et encoder le mot de passe avant
de le comparer avec votre mot de passe encod. Si getSalt() ne retourne rien, alors le mot de
passe soumis est simplement encod en utilisant l'algorithme que vous avez spcifi dans le fichier
security.yml. Si un salt est spcifi, alors la valeur suivante est cre et ensuite assemble pour
former un hash via l'algorithme :
$password.'{'.$salt.'}';
Si vos utilisateurs externes ont leurs mots de passe encods d'une faon diffrente, alors vous
aurez besoin de travailler un peu plus afin que Symfony encode correctement le mot de passe.
Ceci est au-del de la porte de cet article, mais devrait inclure de crer une sous-classe
MessageDigestPasswordEncoder ainsi que surcharger la mthode mergePasswordAndSalt.
De surcrot, le hash , par dfaut, est encod plusieurs fois puis encod en base64. Pour des
dtails plus spcifiques, voir MessageDigestPasswordEncoder
5
. Pour viter cela, configurez-le dans
le fichier security.yml :
1
2
3
4
5
6
security:
encoders:
Acme\WebserviceUserBundle\Security\User\WebserviceUser:
algorithm: sha512
encode_as_base64: false
iterations: 1
5. https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Core/Encoder/MessageDigestPasswordEncoder.php
PDF brought to you by
generated on June 26, 2014
Chapter 69: Comment crer un Fournisseur d'Utilisateur personnalis | 254
Chapter 70
Comment crer un Fournisseur
d'Authentification Personnalis
Si vous avez lu le chapitre La scurit, vous comprenez la distinction que Symfony2 fait entre
authentification et autorisation dans l'implmentation de la scurit. Ce chapitre traite des classes
noyaux impliques dans le processus d'authentification, et de l'implmentation d'un fournisseur
d'authentification personnalis. Comme l'authentification et l'autorisation sont des concepts spars,
cette extension sera indpendante du fournisseur d'utilisateur , et fonctionnera avec les fournisseurs
d'utilisateur de votre application, qu'ils soient stocks en mmoire, dans une base de donnes, ou
n'importe o ailleurs.
A la rencontre de WSSE
Le chapitre suivant dmontre comment crer un fournisseur d'authentification personnalis pour une
authentification WSSE. Le protocole de scurit pour WSSE fournit plusieurs avantages concernant la
scurit :
1. Encryptage du Nom d'utilisateur / Mot de passe
2. Scurit contre les attaques de type replay
3. Aucune configuration de serveur web requise
WSSE est trs utile pour scuriser des services web, qu'ils soient SOAP ou REST.
Il existe de nombreuses et trs bonnes documentations sur WSSE
1
, mais cet article ne va pas se focaliser
sur le protocole de scurit, mais plutt sur la manire dont un protocole personnalis peut tre ajout
votre application Symfony2. La base de WSSE est qu'un en-tte de requte est vrifi pour y trouver des
informations de connexions encryptes en utilisant un timestamp et un nonce
2
, et est authentifi
pour l'utilisateur demand en utilisant un digest du mot de passe.
1. http://www.xml.com/pub/a/2003/12/17/dive.html
2. http://en.wikipedia.org/wiki/Cryptographic_nonce
PDF brought to you by
generated on June 26, 2014
Chapter 70: Comment crer un Fournisseur d'Authentification Personnalis | 255
Listing 70-1
WSSE supporte aussi la validation par cl d'application, qui est utile pour les services web, mais
qui est hors-sujet dans ce chapitre.
Le Token
Le rle du token dans le contexte de scurit de Symfony2 est important. Un token reprsente les donnes
d'authentification de l'utilisateur prsentes dans la requte. Une fois qu'une requte est authentifie, le
token conserve les donnes de l'utilisateur, et dlivre ces donnes au travers du contexte de scurit.
Premirement, vous allez crer votre classe token. Cela permettra de passer toutes les informations
pertinentes votre fournisseur d'authentification.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/Acme/DemoBundle/Security/Authentication/Token/WsseUserToken.php
namespace Acme\DemoBundle\Security\Authentication\Token;
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
class WsseUserToken extends AbstractToken
{
public $created;
public $digest;
public $nonce;
public function __construct(array $roles = array())
{
parent::__construct($roles);
// Si l'utilisateur a des rles, on le considre comme authentifi
$this->setAuthenticated(count($roles) > 0);
}
public function getCredentials()
{
return '';
}
}
La classe WsseUserToken tend la classe du composant de scurit AbstractToken
3
, qui fournit la
fonctionnalit basique du token. Implmentez l'interface TokenInterface
4
dans chaque classe que
vous souhaitez utiliser comme token.
Le Listener
Ensuite, vous avez besoin d'un listener pour couter le contexte de scurit. Le listener est charg
de transmettre les requtes au pare-feu et d'appeler le fournisseur d'authentification. Un listener doit
tre une instance de ListenerInterface
5
. Un listener de scurit devrait grer l'vnement
GetResponseEvent
6
, et dfinir un token authentifi dans le contexte de scurit en cas de succs.
3. http://api.symfony.com/2.3/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.html
4. http://api.symfony.com/2.3/Symfony/Component/Security/Core/Authentication/Token/TokenInterface.html
5. http://api.symfony.com/2.3/Symfony/Component/Security/Http/Firewall/ListenerInterface.html
6. http://api.symfony.com/2.3/Symfony/Component/HttpKernel/Event/GetResponseEvent.html
PDF brought to you by
generated on June 26, 2014
Chapter 70: Comment crer un Fournisseur d'Authentification Personnalis | 256
Listing 70-2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// src/Acme/DemoBundle/Security/Firewall/WsseListener.php
namespace Acme\DemoBundle\Security\Firewall;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Acme\DemoBundle\Security\Authentication\Token\WsseUserToken;
class WsseListener implements ListenerInterface
{
protected $securityContext;
protected $authenticationManager;
public function __construct(SecurityContextInterface $securityContext,
AuthenticationManagerInterface $authenticationManager)
{
$this->securityContext = $securityContext;
$this->authenticationManager = $authenticationManager;
}
public function handle(GetResponseEvent $event)
{
$request = $event->getRequest();
$wsseRegex = '/UsernameToken Username="([^"]+)", PasswordDigest="([^"]+)",
Nonce="([^"]+)", Created="([^"]+)"/';
if (!$request->headers->has('x-wsse') || 1 !== preg_match($wsseRegex,
$request->headers->get('x-wsse'), $matches)) {
return;
}
$token = new WsseUserToken();
$token->setUser($matches[1]);
$token->digest = $matches[2];
$token->nonce = $matches[3];
$token->created = $matches[4];
try {
$authToken = $this->authenticationManager->authenticate($token);
$this->securityContext->setToken($authToken);
} catch (AuthenticationException $failed) {
// ... you might log something here
// To deny the authentication clear the token. This will redirect to the login
page.
// $this->securityContext->setToken(null);
// return;
// Deny authentication with a '403 Forbidden' HTTP response
$response = new Response();
$response->setStatusCode(403);
$event->setResponse($response);
}
PDF brought to you by
generated on June 26, 2014
Chapter 70: Comment crer un Fournisseur d'Authentification Personnalis | 257
Listing 70-3
}
}
Ce listener vrifie l'en-tte X-WSSE attendu dans la rponse, fait correspondre la valeur retourne pour
l'information WSSE attendue, cre un token utilisant cette information, et passe le token au gestionnaire
d'authentification. Si la bonne information n'est pas fournie, ou si le gestionnaire d'authentification lance
une AuthenticationException
7
, alors une rponse 403 est retourne.
Une classe non utilise ci-dessus, la classe AbstractAuthenticationListener
8
, est une classe de
base trs utile qui fournit certaines fonctionnalits communes pour les extensions de scurit. Ceci
inclut le fait de maintenir le token dans la session, fournir des gestionnaires en cas de succs/chec,
des URLs de formulaire de login, et plus encore. Comme WSSE ne requiert pas de maintenir les
sessions d'authentification ou les formulaires de login, cela ne sera pas utilis dans cet exemple.
Le Fournisseur d'Authentification
Le fournisseur d'authentification va effectuer la vrification du WsseUserToken. C'est--dire que le
fournisseur va vrifier que la valeur de l'en-tte Created est valide dans les cinq minutes, que la valeur de
l'en-tte Nonce est unique dans les cinq minutes, et que la valeur de l'en-tte PasswordDigest correspond
au mot de passe de l'utilisateur.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// src/Acme/DemoBundle/Security/Authentication/Provider/WsseProvider.php
namespace Acme\DemoBundle\Security\Authentication\Provider;
use
Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\NonceExpiredException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Acme\DemoBundle\Security\Authentication\Token\WsseUserToken;
class WsseProvider implements AuthenticationProviderInterface
{
private $userProvider;
private $cacheDir;
public function __construct(UserProviderInterface $userProvider, $cacheDir)
{
$this->userProvider = $userProvider;
$this->cacheDir = $cacheDir;
}
public function authenticate(TokenInterface $token)
{
$user = $this->userProvider->loadUserByUsername($token->getUsername());
if ($user && $this->validateDigest($token->digest, $token->nonce, $token->created,
$user->getPassword())) {
$authenticatedToken = new WsseUserToken($user->getRoles());
$authenticatedToken->setUser($user);
7. http://api.symfony.com/2.3/Symfony/Component/Security/Core/Exception/AuthenticationException.html
8. http://api.symfony.com/2.3/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.html
PDF brought to you by
generated on June 26, 2014
Chapter 70: Comment crer un Fournisseur d'Authentification Personnalis | 258
Listing 70-4
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
return $authenticatedToken;
}
throw new AuthenticationException('The WSSE authentication failed.');
}
protected function validateDigest($digest, $nonce, $created, $secret)
{
// Expire le timestamp aprs 5 minutes
if (time() - strtotime($created) > 300) {
return false;
}
// Valide que le nonce est unique dans les 5 minutes
if (file_exists($this->cacheDir.'/'.$nonce) &&
file_get_contents($this->cacheDir.'/'.$nonce) + 300 > time()) {
throw new NonceExpiredException('Previously used nonce detected');
}
file_put_contents($this->cacheDir.'/'.$nonce, time());
// Valide le Secret
$expected = base64_encode(sha1(base64_decode($nonce).$created.$secret, true));
return $digest === $expected;
}
public function supports(TokenInterface $token)
{
return $token instanceof WsseUserToken;
}
}
La classe AuthenticationProviderInterface
9
requiert une mthode authenticate sur le token
de l'utilisateur ainsi qu'une mthode supports, qui dit au gestionnaire d'authentification d'utiliser
ou non ce fournisseur pour le token donn. Dans le cas de fournisseurs multiples, le gestionnaire
d'authentification se dplacera alors jusqu'au prochain fournisseur dans la liste.
La Factory ( l'usine en franais)
Vous avez cr un token personnalis, un listener personnalis, et un fournisseur personnalis.
Maintenant, vous avez besoin de les relier tous ensemble. Comment mettez-vous votre fournisseur
disposition de votre configuration de scurit ? La rponse est : en utilisant une factory. Une factory
est l o vous intervenez dans le composant de scurit en lui disant le nom de votre fournisseur
ainsi que toutes ses options de configuration disponibles. Tout d'abord, vous devez crer une classe qui
implmente SecurityFactoryInterface
10
.
1
2
3
// src/Acme/DemoBundle/DependencyInjection/Security/Factory/WsseFactory.php
namespace Acme\DemoBundle\DependencyInjection\Security\Factory;
9. http://api.symfony.com/2.3/Symfony/Component/Security/Core/Authentication/Provider/AuthenticationProviderInterface.html
10. http://api.symfony.com/2.3/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.html
PDF brought to you by
generated on June 26, 2014
Chapter 70: Comment crer un Fournisseur d'Authentification Personnalis | 259
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use
Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
class WsseFactory implements SecurityFactoryInterface
{
public function create(ContainerBuilder $container, $id, $config, $userProvider,
$defaultEntryPoint)
{
$providerId = 'security.authentication.provider.wsse.'.$id;
$container
->setDefinition($providerId, new
DefinitionDecorator('wsse.security.authentication.provider'))
->replaceArgument(0, new Reference($userProvider))
;
$listenerId = 'security.authentication.listener.wsse.'.$id;
$listener = $container->setDefinition($listenerId, new
DefinitionDecorator('wsse.security.authentication.listener'));
return array($providerId, $listenerId, $defaultEntryPoint);
}
public function getPosition()
{
return 'pre_auth';
}
public function getKey()
{
return 'wsse';
}
public function addConfiguration(NodeDefinition $node)
{
}
}
La SecurityFactoryInterface
11
requiert les mthodes suivantes :
la mthode create, qui ajoute le listener et le fournisseur d'authentification au conteneur
d'Injection de Dpendances pour le contexte de scurit appropri ;
la mthode getPosition, qui doit tre de type pre_auth, form, http et remember_me et qui
dfinit le moment auquel le fournisseur est appel ;
la mthode getKey qui dfinit la cl de configuration utilise pour rfrencer le fournisseur ;
la mthode addConfiguration, qui est utilise pour dfinir les options de configuration en
dessous de la cl de configuration dans votre configuration de scurit. Comment dfinir les
options de configuration sera expliqu plus tard dans ce chapitre.
Une classe non utilise dans cet exemple, AbstractFactory
12
, est une classe de base trs utile qui
fournit certaines fonctionnalits communes pour les factories de scurit. Cela pourrait tre
utile lors de la dfinition d'un fournisseur d'authentification d'un type diffrent.
11. http://api.symfony.com/2.3/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.html
PDF brought to you by
generated on June 26, 2014
Chapter 70: Comment crer un Fournisseur d'Authentification Personnalis | 260
Listing 70-5
Listing 70-6
Maintenant que vous avez cr une classe factory, la cl wsse peut tre utilise comme un pare-feu dans
votre configuration de scurit.
Vous vous demandez peut-tre pourquoi avez vous besoin d'une classe factory spciale pour
ajouter des listeners et fournisseurs un conteneur d'injection de dpendances ? . C'est une trs
bonne question. La raison est que vous pouvez utiliser votre pare-feu plusieurs fois afin de scuriser
plusieurs parties de votre application. Grce cela, chaque fois que votre pare-feu sera utilis, un
nouveau service sera cr dans le conteneur d'injection de dpendances. La factory est ce qui cre
ces nouveaux services.
Configuration
Il est temps de voir votre fournisseur d'authentification en action. Vous allez avoir besoin de faire
quelques petites choses afin qu'il fonctionne. La premire chose est d'ajouter les services ci-dessus
dans le conteneur d'injection de dpendances. Votre classe factory ci-dessus fait rfrence des IDs
de service qui n'existent pas encore : wsse.security.authentication.provider et
wsse.security.authentication.listener. Il est temps de dfinir ces services.
# src/Acme/DemoBundle/Resources/config/services.yml
services:
wsse.security.authentication.provider:
class: Acme\DemoBundle\Security\Authentication\Provider\WsseProvider
arguments: ['', %kernel.cache_dir%/security/nonces]
wsse.security.authentication.listener:
class: Acme\DemoBundle\Security\Firewall\WsseListener
arguments: [@security.context, @security.authentication.manager]
Maintenant que vos services sont dfinis, informez votre contexte de scurit de l'existence de votre
factory dans la classe de votre bundle :
New in version 2.1: Avant 2.1, la factory ci-dessous tait ajoute via le fichier security.yml la
place.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Acme/DemoBundle/AcmeDemoBundle.php
namespace Acme\DemoBundle;
use Acme\DemoBundle\DependencyInjection\Security\Factory\WsseFactory;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class AcmeDemoBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$extension = $container->getExtension('security');
$extension->addSecurityListenerFactory(new WsseFactory());
12. http://api.symfony.com/2.3/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.html
PDF brought to you by
generated on June 26, 2014
Chapter 70: Comment crer un Fournisseur d'Authentification Personnalis | 261
Listing 70-7
Listing 70-8
Listing 70-9
16
17
}
}
Vous avez termin ! Vous pouvez maintenant dfinir des parties de votre application comme tant sous
la protection de WSSE.
1
2
3
4
5
security:
firewalls:
wsse_secured:
pattern: /api/.*
wsse: true
Flicitations ! Vous avez crit votre tout premier fournisseur d'authentification de scurit personnalis !
Un Petit Extra
Que diriez-vous de rendre votre fournisseur d'authentification WSSE un peu plus excitant ? Les
possibilits sont sans fin. Voyons comment nous pouvons apporter plus d'clat tout cela !
Configuration
Vous pouvez ajouter des options personnalises sous la cl wsse de votre configuration de scurit. Par
exemple, le temps allou avant que l'en-tte Created expire est, par dfaut, 5 minutes. Rendez cela
configurable, afin que diffrents pares-feu puissent avoir des longueurs de timeout diffrentes.
Vous allez tout d'abord avoir besoin d'diter WsseFactory puis ensuite de dfinir la nouvelle option dans
la mthode addConfiguration.
1
2
3
4
5
6
7
8
9
10
11
12
class WsseFactory implements SecurityFactoryInterface
{
// ...
public function addConfiguration(NodeDefinition $node)
{
$node
->children()
->scalarNode('lifetime')->defaultValue(300)
->end();
}
}
Maintenant, dans la mthode create de la factory, l'argument $config va contenir une cl lifetime ,
dclare 5 minutes (300 secondes) moins qu'elle soit dfinie ailleurs dans la configuration. Passez cet
argument votre fournisseur d'authentification afin qu'il l'utilise.
1
2
3
4
5
6
7
8
9
class WsseFactory implements SecurityFactoryInterface
{
public function create(ContainerBuilder $container, $id, $config, $userProvider,
$defaultEntryPoint)
{
$providerId = 'security.authentication.provider.wsse.'.$id;
$container
->setDefinition($providerId,
new DefinitionDecorator('wsse.security.authentication.provider'))
PDF brought to you by
generated on June 26, 2014
Chapter 70: Comment crer un Fournisseur d'Authentification Personnalis | 262
Listing 70-10
10
11
12
13
14
15
->replaceArgument(0, new Reference($userProvider))
->replaceArgument(2, $config['lifetime']);
// ...
}
// ...
}
Vous allez aussi avoir besoin d'ajouter un troisime argument la configuration du service
wsse.security.authentication.provider, qui peut tre vide, mais qui sera rempli avec la valeur
lifetime dans la factory. La classe WsseProvider va maintenant avoir besoin d'accepter un
troisime argument dans son constructeur - la valeur lifetime - qu'elle devrait utiliser la place
des 300 secondes codes en dur. Ces deux tapes ne sont pas montres ici.
La valeur lifetime de chaque requte wsse est maintenant configurable, et peut tre dfinie par
quelconque valeur que ce soit par pare-feu.
1
2
3
4
5
security:
firewalls:
wsse_secured:
pattern: /api/.*
wsse: { lifetime: 30 }
Le reste dpend de vous ! N'importe quels autres points de configuration peuvent tre dfinis dans la
factory et consomm ou pass d'autres classes dans le conteneur.
PDF brought to you by
generated on June 26, 2014
Chapter 70: Comment crer un Fournisseur d'Authentification Personnalis | 263
Listing 71-1
Listing 71-2
Chapter 71
Comment changer le comportement par
dfaut du chemin cible
Par dfaut, le composant de scurit conserve l'information de l'URI de la dernire requte dans une
variable de session nomme _security.target_path. Lors d'une connexion russie, l'utilisateur est
redirig vers ce chemin afin de l'aider continuer sa visite en le renvoyant vers la dernire page connue
qu'il a parcourue.
Dans certains cas, ce n'est pas la meilleure solution. Par exemple, quand l'URI de la dernire requte
est une mthode POST HTTP avec une route configure pour accepter seulement une mthode POST,
l'utilisateur est redirig vers cette route et se retrouvera confront invitablement une erreur 404.
Pour contourner ce comportement, vous auriez simplement besoin d'tendre la classe
ExceptionListener et surcharger la mthode par dfaut nomme setTargetPath().
Tout d'abord, surchargez le paramtre security.exception_listener.class dans votre fichier de
configuration. Cela peut tre effectu depuis votre fichier de configuration principal (dans app/config) ou
depuis un fichier de configuration import depuis un bundle :
1
2
3
4
# src/Acme/HelloBundle/Resources/config/services.yml
parameters:
# ...
security.exception_listener.class: Acme\HelloBundle\Security\Firewall\ExceptionListener
Ensuite, crez votre propre ExceptionListener:
1
2
3
4
5
6
7
8
9
10
// src/Acme/HelloBundle/Security/Firewall/ExceptionListener.php
namespace Acme\HelloBundle\Security\Firewall;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Firewall\ExceptionListener as BaseExceptionListener;
class ExceptionListener extends BaseExceptionListener
{
protected function setTargetPath(Request $request)
{
PDF brought to you by
generated on June 26, 2014
Chapter 71: Comment changer le comportement par dfaut du chemin cible | 264
11
12
13
14
15
16
17
18
19
20
// Ne conservez pas de chemin cible pour les requtes XHR et non-GET
// Vous pouvez ajouter n'importe quelle logique supplmentaire ici
// si vous le voulez
if ($request->isXmlHttpRequest() || 'GET' !== $request->getMethod()) {
return;
}
$request->getSession()->set('_security.target_path', $request->getUri());
}
}
Ajoutez plus ou moins de logique ici comme votre scnario le requiert !
PDF brought to you by
generated on June 26, 2014
Chapter 71: Comment changer le comportement par dfaut du chemin cible | 265
Listing 72-1
Chapter 72
Comment utiliser le Serializer
Srialiser et dsrialiser vers et depuis des objets et diffrents formats (ex JSON ou XML) est un sujet trs
complexe. Symfony est fourni avec un Composant Serializer, qui vous donne certains outils pour rsoudre
vos problmes.
En fait, avant de commencer, familiarisez vous avec le srialiseur, les normaliseurs et les encodeurs en
lisant la documentation du Serializer. Vous pouvez galement vous renseigner sur le JMSSerializerBundle
1
qui tend certaines des fonctionnalits offertes par le Serializer de Symfony.
Activer le Serializer
New in version 2.3: Le Serializer a toujours exist dans Symfony mais, avant la version 2.3, vous
deviez construire le service serializer vous mme.
Le service serializer n'est pas disponible par dfaut. Pour le rendre disponible, vous devez l'activer dans
votre configuration :
1
2
3
4
5
# app/config/config.yml
framework:
# ...
serializer:
enabled: true
1. http://jmsyst.com/bundles/JMSSerializerBundle
PDF brought to you by
generated on June 26, 2014
Chapter 72: Comment utiliser le Serializer | 266
Listing 72-2
Ajouter des Normaliseurs et des Encodeurs
Une fois activ, le service serializer sera disponible dans le conteneur et sera charg avec deux
encodeurs (JsonEncoder
2
et XmlEncoder
3
) mais aucun normaliseurs, ce qui veut dire que vous devrez
charger le vtre.
Vous pouvez charger des normaliseurs et/ou des encodeurs en les taggant comme serializer.normalizer et
serializer.encoder. Il est galement possible de dfinir la priorit du tag pour influencer l'ordre dans lequel
il seront pris.
Voici un exemple de chargement du GetSetMethodNormalizer
4
:
1
2
3
4
5
6
# app/config/config.yml
services:
get_set_method_normalizer:
class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer
tags:
- { name: serializer.normalizer }
La classe GetSetMethodNormalizer
5
est inefficace de par sa conception. Ds que vous aurez un
modle de donnes circulaire, une boucle infinie sera cre l'appel des getters. Vous tes donc
invit ajouter vos propres normaliseurs pour rpondre ce besoin.
2. http://api.symfony.com/2.3/Symfony/Component/Serializer/Encoder/JsonEncoder.html
3. http://api.symfony.com/2.3/Symfony/Component/Serializer/Encoder/XmlEncoder.html
4. http://api.symfony.com/2.3/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.html
5. http://api.symfony.com/2.3/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.html
PDF brought to you by
generated on June 26, 2014
Chapter 72: Comment utiliser le Serializer | 267
Listing 73-1
Chapter 73
Comment crer un listener ( couteur en
franais) d'vnement
Symfony possde divers vnements et hooks qui peuvent tre utiliss pour dclencher un
comportement personnalis dans votre application. Ces vnements sont lancs par le composant
HttpKernel et peuvent tre consults dans la classe KernelEvents
1
.
Afin de personnaliser un vnement avec votre propre logique, vous devez crer un service qui va agir
en tant que listener d'vnement pour cet vnement. Dans cet article, nous allons crer un service
qui agit en tant que Listener d'Exception, vous permettant de modifier comment les exceptions
sont affiches par notre application. L'vnement KernelEvents::EXCEPTION est l'un des vnements du
coeur du noyau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/Acme/DemoBundle/Listener/AcmeExceptionListener.php
namespace Acme\DemoBundle\Listener;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
class AcmeExceptionListener
{
public function onKernelException(GetResponseForExceptionEvent $event)
{
// nous rcuprons l'objet exception depuis l'vnement reu
$exception = $event->getException();
$message = 'My Error says: ' . $exception->getMessage() . ' with code: ' .
$exception->getCode();
// personnalise notre objet rponse pour afficher les dtails de notre exception
$response = new Response();
$response->setContent($message);
// HttpExceptionInterface est un type d'exception spcial qui
1. http://api.symfony.com/2.3/Symfony/Component/HttpKernel/KernelEvents.html
PDF brought to you by
generated on June 26, 2014
Chapter 73: Comment crer un listener ( couteur en franais) d'vnement | 268
Listing 73-2
Listing 73-3
22
23
24
25
26
27
28
29
30
31
32
// contient le code statut et les dtails de l'entte
if ($exception instanceof HttpExceptionInterface) {
$response->setStatusCode($exception->getStatusCode());
$response->headers->replace($exception->getHeaders());
} else {
$response->setStatusCode(500);
}
// envoie notre objet rponse modifi l'vnement
$event->setResponse($response);
}
}
Chaque vnement reoit un objet de type $event lgrement diffrent. Pour l'vnement
kernel.exception, c'est GetResponseForExceptionEvent
2
. Pour voir quel est le type d'objet que
chaque listener d'vnement reoit, voyez KernelEvents
3
.
Maintenant que la classe est cre, nous devons juste la dfinir en tant que service et notifier Symfony
que c'est un listener de l'vnement kernel.exception en utilisant un tag spcifique :
1
2
3
4
5
6
# app/config/config.yml
services:
kernel.listener.your_listener_name:
class: Acme\DemoBundle\Listener\AcmeExceptionListener
tags:
- { name: kernel.event_listener, event: kernel.exception, method:
onKernelException }
Il y a une autre option priority pour le tag qui est optionnelle et qui a pour valeur par dfaut 0.
Cette valeur peut aller de -255 255, et les listeners seront excutes dans l'ordre de leur priorit
(le plus grand est le plus prioritaire). Cela est utile lorsque vous avez besoin de garantir qu'un
listener est excut avant un autre.
vnement de requte, vrification des types
Une mme page peut faire plusieurs requtes (une requte principale et plusieurs sous-requtes, c'est
pourquoi, lorsque vous travaillez avec l'vnement KernelEvents::REQUEST, vous pourriez avoir besoin
de vrifier le type de la requte. Cela peut tre effectu trs facilement comme ceci:
1
2
3
4
5
6
7
8
9
// src/Acme/DemoBundle/Listener/AcmeRequestListener.php
namespace Acme\DemoBundle\Listener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernel;
class AcmeRequestListener
{
public function onKernelRequest(GetResponseEvent $event)
2. http://api.symfony.com/2.3/Symfony/Component/HttpKernel/Event/GetResponseForExceptionEvent.html
3. http://api.symfony.com/2.3/Symfony/Component/HttpKernel/KernelEvents.html
PDF brought to you by
generated on June 26, 2014
Chapter 73: Comment crer un listener ( couteur en franais) d'vnement | 269
10
11
12
13
14
15
16
17
18
{
if (HttpKernel::MASTER_REQUEST != $event->getRequestType()) {
// ne rien faire si ce n'est pas la requte principale
return;
}
// ...
}
}
Deux types de requte sont disponibles dans l'interface HttpKernelInterface
4
:
HttpKernelInterface::MASTER_REQUEST et HttpKernelInterface::SUB_REQUEST.
4. http://api.symfony.com/2.3/Symfony/Component/HttpKernel/HttpKernelInterface.html
PDF brought to you by
generated on June 26, 2014
Chapter 73: Comment crer un listener ( couteur en franais) d'vnement | 270
Chapter 74
Comment travailler avec les champs
d'applications ( scopes en anglais)
Nous traiterons ici des champs d'applications, un sujet d'un niveau avanc en relation avec Service
Container. Si vous avez dj observ une erreur mentionnant le terme scopes lors de la cration de vos
services, ou avez eu besoin de crer des services qui dpendent du service request, cet article est fait pour
vous.
Comprendre les champs d'applications
Le champs d'application d'un service contrle les intractions d'une instance de ce service avec son
conteneur. Le composant d'injection de dpendance fourni deux champs d'applications gnriques :
container (valeur par dfaut): la mme instance est utilise chaque requte.
prototype: Une nouvelle instance est cre chaque requte.
Le FrameworkBundle dfinit lui un troisime champ d'application : request. Celui-ci le lie la requte.
Ainsi pour chaque sous-requte une nouvelle instance est cre. La consquence est qu'il est indisponible
en dehors de la requte (en ligne de commande par example).
Les champs d'applications ajoutent une contrainte sur les dpendances d'un service : un service ne peut
pas dpendre d'un champ d'application moins large. Par exemple, si vous crez un service gnrique
my_foo, mais essayez d'injecter le composant request, vous recevrez une
ScopeWideningInjectionException
1
au moment de la compilation du conteneur. Pour plus de dtails,
lisez les notes dans la barre latrale.
1. http://api.symfony.com/2.3/Symfony/Component/DependencyInjection/Exception/ScopeWideningInjectionException.html
PDF brought to you by
generated on June 26, 2014
Chapter 74: Comment travailler avec les champs d'applications ( scopes en anglais) | 271
Listing 74-1
Champs d'application et dpendances
Imaginez que vous ayez configur un service my_mailer, sans configurer de champs d'application
pour ce service ; par dfaut il sera rgl sur container. Ainsi chaque fois que vous appellerez le
conteneur du service my_mailer, vous recevrez le mme objet. Ce qui est habituel dans l'utilisation
d'un service.
Imaginez cependant que vous ayez besoin du service request dans votre service my_mailer, peut
tre parce que vous lisez une URL de la requte courante. Vous l'ajoutez comme un argument du
constructeur. Observons quelles pourraient tre les consquences :
Quand vous appelez my_mailer, une instance de my_mailer (appelons le MailerA) est
cre et un service request (RequestA) lui est envoy. Facile!
Vous effectuez maintenant une sous-requte dans Symfony, ce qui est une faon
sympatique de dire que vous avez appelez, par exemple, la fonction twig {% render ...
%}, partir d'un autre contrleur. En interne, l'ancien service request (RequestA) est
ainsi remplac par une nouvelle instance (RequestB). Cela se droule en arrire-plan et
est tout fait normal.
Dans votre contrleur intgr, vous interrogez une nouvelle fois le service my_mailer.
Votre service ayant le champ d'application container, la mme instance (MailerA) est
rutilise. Et voil le problme : l'instance MailerA contient toujours l'ancien objet
RequestA, qui ne correspond plus maintenant l'objet requte mis jour (RequestB est
maintenant le service courant request). C'est subtile mais l'erreur pourrait engendrer des
problmes majeurs, et cela explique pourquoi cela est interdit.
Ainsi, voil pourquoi les champs d'applications existent, et comment il peuvent causer
des problmes. En continuant cette lecture nous vous indiquerons les solutions
prconises.
Un service peut bien entendu dpendre d'un service provenant d'un champ d'application plus
tendu.
Configurer le champ d'application dans la dfinition
Le champ d'application d'un service est indiqu dans la dfinition de ce service l'aide du paramtre scope
:
1
2
3
4
5
# src/Acme/HelloBundle/Resources/config/services.yml
services:
greeting_card_manager:
class: Acme\HelloBundle\Mail\GreetingCardManager
scope: request
Si vous n'indiquez pas ce paramtre, il sera li par dfaut au conteneur, ce qui est le fonctionnement
habituel d'un service. A moins que votre service ne dpende d'un autre service qui soit dans un champ
d'application plus restreint (le plus courant tant request), vous n'aurez probablement pas modifier
votre configuration.
PDF brought to you by
generated on June 26, 2014
Chapter 74: Comment travailler avec les champs d'applications ( scopes en anglais) | 272
Listing 74-2
Listing 74-3
Utiliser un service provenant d'un champ d'application restreint
Si votre service dpend d'un autre service au champ d'application dtermin, la meilleure solution est
de dfinir le mme champ d'application pour celui-ci (ou un champ d'application encore plus restreint).
Habituellement, cela implique de placer votre service dans le champ d'application request.
Mais cel n'est pas toujours possible (par exemple, une extension twig doit tre dans le champ
d'application conteneur au regard de lenvironnement Twig dont elle est dpendante). Dans ces cas de
figure, vous devrez configurer votre conteneur en tant que service et charger les dpendances provenant
d'un champ d'application restreint chaque appel, afin d'tre certain d'obtenir les instances mises jour:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/Acme/HelloBundle/Mail/Mailer.php
namespace Acme\HelloBundle\Mail;
use Symfony\Component\DependencyInjection\ContainerInterface;
class Mailer
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function sendEmail()
{
$request = $this->container->get('request');
// Utilisez la requte ici
}
}
Faites attention ne pas enregistrer la requte dans une proprit de votre objet pour un appel
futur ; cela engendrerait les mmes inconsistances que celles dcrites prcdemment (except que
dans ce cas, Symfony ne pourrait dtecter cette erreur).
La configuration du service pour cette classe :
1
2
3
4
5
6
7
8
9
10
# src/Acme/HelloBundle/Resources/config/services.yml
parameters:
# ...
my_mailer.class: Acme\HelloBundle\Mail\Mailer
services:
my_mailer:
class: "%my_mailer.class%"
arguments:
- "@service_container"
# scope: container can be omitted as it is the default
Injecter le container entier dans un service est gnralement proscrire (injectez seulement les
paramtres utiles). Dans quelques rares cas, cela est ncessaire quand vous avez un service dans un
champ d'application container qui a besoin d'un service du champ d'application request.
PDF brought to you by
generated on June 26, 2014
Chapter 74: Comment travailler avec les champs d'applications ( scopes en anglais) | 273
Si vous dfinissez un contrleur comme un service, alors vous pourrez appelez l'objet Request sans
injecter le conteneur comme un argument de votre mthode action. Voir La Requte en tant qu'argument
du Contrleur pour plus de dtails.
PDF brought to you by
generated on June 26, 2014
Chapter 74: Comment travailler avec les champs d'applications ( scopes en anglais) | 274
Listing 75-1
Chapter 75
Comment travailler avec les Passes de
Compilation dans les Bundles
Les passes de compilation vous donnent l'opportunit de manipuler d'autres dfinitions de service qui
ont t dfinies via le conteneur de service. Pour savoir comment les crer, vous pouvez lire la section des
composants Compiler le Conteneur . Pour dfinir une passe de compilation depuis un bundle, vous
devez l'ajouter la mthode build se situant dans la classe du bundle:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/Acme/MailerBundle/AcmeMailerBundle.php
namespace Acme\MailerBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Acme\MailerBundle\DependencyInjection\Compiler\CustomCompilerPass;
class AcmeMailerBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new CustomCompilerPass());
}
}
L'un des cas d'utilisation les plus frquents des passes de compilation est lorsque vous travaillez avec des
services taggs (apprenez-en plus propos des tags en lisant la section sur les composants Travailler
avec des Services Taggs ). Si vous utilisez des tags personnaliss dans un bundle, alors par convention,
les noms de tag se constituent du nom du bundle (en minuscules, avec des tirets du bas en tant que
sparateurs), suivi par un point, et finalement le nom rel . Par exemple, si vous voulez introduire un
tag transport dans votre AcmeMailerBundle, vous devriez l'appeler acme_mailer.transport.
PDF brought to you by
generated on June 26, 2014
Chapter 75: Comment travailler avec les Passes de Compilation dans les Bundles | 275
Listing 76-1
Listing 76-2
Chapter 76
Exemple de Session Proxy
Le mcanisme de proxy de session possde une varit d'utilisations et cet exemple prsente deux
utilisations communes. Plutt que d'injecter un session handler comme d'habitude, un handler est inject
dans le proxy et est enregistr avec un pilote de stockage de session
1
2
3
4
5
6
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
$proxy = new YourProxy(new PdoSessionHandler());
$session = new Session(new NativeSessionStorage(array(), $proxy));
Plus bas, vous apprendrez deux rels exemples qui peuvent tre utiliss pour la classe YourProxy : le
chiffrement des donnes de session et les sessions invites en lecture seules.
Chiffrement des Donnes en Session
Si vous vouliez chiffrer les donnes en session, vous pouvez utiliser le proxy pour chiffrer ou dchiffrer la
session comme suit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
class EncryptedSessionProxy extends SessionHandlerProxy
{
private $key;
public function __construct(\SessionHandlerInterface $handler, $key)
{
$this->key = $key;
parent::__construct($handler);
}
public function read($id)
PDF brought to you by
generated on June 26, 2014
Chapter 76: Exemple de Session Proxy | 276
Listing 76-3
15
16
17
18
19
20
21
22
23
24
25
26
27
{
$data = parent::read($id);
return mcrypt_decrypt(\MCRYPT_3DES, $this->key, $data);
}
public function write($id, $data)
{
$data = mcrypt_encrypt(\MCRYPT_3DES, $this->key, $data);
return parent::write($id, $data);
}
}
Les sessions invites en lecture seule
Il y a quelques applications o une session est requise pour les utilisateurs invits, mais il n'y a pas
spcialement besoin de persister cette session. Dans ce cas vous pouvez intercepter la session avant qu'elle
ne soit crite
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use Foo\User;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
class ReadOnlyGuestSessionProxy extends SessionHandlerProxy
{
private $user;
public function __construct(\SessionHandlerInterface $handler, User $user)
{
$this->user = $user;
parent::__construct($handler);
}
public function write($id, $data)
{
if ($this->user->isGuest()) {
return;
}
return parent::write($id, $data);
}
}
PDF brought to you by
generated on June 26, 2014
Chapter 76: Exemple de Session Proxy | 277
Listing 77-1
Chapter 77
Faire que la Locale soit "persistente" durant la
session de l'utilisateur
Avant Symfony 2.1, la locale tait enregistre dans un attribut de session appel _locale. Depuis 2.1,
c'est stock dans la Request, ce qui signifie qu'elle n'est pas "persistante" durant la requte de l'utilisateur.
Dans cet article, vous allez apprendre comment faire que la locale de l'utilisateur soit persistante pour
que, une fois fixe, la mme locale soit utilise pour chaque requte suivante.
Crer un LocaleListener
Pour simuler le fait que la locale soit stocke en session, vous devez crer et enregistrer un nouvel
event listener (couteur d'vnement). Le listener ressemblera quelque chose comme le code qui suit.
Typiquement, _locale est utilis comme paramtre du routing pour indiquer la locale, ceci dit la manire
dont vous dterminez la locale dsire pour une requte n'est pas important
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/Acme/LocaleBundle/EventListener/LocaleListener.php
namespace Acme\LocaleBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class LocaleListener implements EventSubscriberInterface
{
private $defaultLocale;
public function __construct($defaultLocale = 'en')
{
$this->defaultLocale = $defaultLocale;
}
public function onKernelRequest(GetResponseEvent $event)
{
PDF brought to you by
generated on June 26, 2014
Chapter 77: Faire que la Locale soit "persistente" durant la session de l'utilisateur | 278
Listing 77-2
Listing 77-3
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
$request = $event->getRequest();
if (!$request->hasPreviousSession()) {
return;
}
// on essaie de voir si la locale a t fixe dans le paramtre de routing _locale
if ($locale = $request->attributes->get('_locale')) {
$request->getSession()->set('_locale', $locale);
} else {
// si aucune locale n'a t fixe explicitement dans la requte, on utilise
celle de la session
$request->setLocale($request->getSession()->get('_locale',
$this->defaultLocale));
}
}
public static function getSubscribedEvents()
{
return array(
// doit tre enregistr avant le Locale listener par dfaut
KernelEvents::REQUEST => array(array('onKernelRequest', 17)),
);
}
}
Puis enregistrez le listener :
1
2
3
4
5
6
services:
acme_locale.locale_listener:
class: Acme\LocaleBundle\EventListener\LocaleListener
arguments: ["%kernel.default_locale%"]
tags:
- { name: kernel.event_subscriber }
Et voil ! Maintenant clbrons en changeant la locale de l'utilisateur qui est persist au fil des requtes.
Souvenez-vous, pour rcuprer la locale de l'utilisateur, utilisez toujours la mthode
Request::getLocale
1
1
2
3
4
5
6
7
// depuis un controller...
use Symfony\Component\HttpFoundation\Request;
public function indexAction(Request $request)
{
$locale = $request->getLocale();
}
1. http://api.symfony.com/2.3/Symfony/Component/HttpFoundation/Request.html#getLocale()
PDF brought to you by
generated on June 26, 2014
Chapter 77: Faire que la Locale soit "persistente" durant la session de l'utilisateur | 279
Listing 78-1
Listing 78-2
Chapter 78
Configurer le Dossier o les Fichiers pour les
Sessions sont Enregistrs
Par dfaut, la Symfony Standard Edition utilise les valeurs par dfaut de php.ini pour
session.save_handler et session.save_path pour dterminer l'endroit o persister les donnes de
session. Cela est possible grce la configuration suivante :
1
2
3
4
5
# app/config/config.yml
framework:
session:
# handler_id fix null utilisera le session handler de php.ini
handler_id: ~
Avec cette configuration, c'est vous de changer le fichier php.ini` pour changer l'endroit o les metadata
de votre session seront stockes.
Nanmoins, si vous disposez de la configuration suivante, Symfony stockera les donnes de la session
dans des fichiers contenus dans le dossier de cache %kernel.cache_dir%/sessions. Cela signifie que
lorsque vous purgez le cache, toutes les sessions courantes seront galement effaces :
1
2
3
# app/config/config.yml
framework:
session: ~
Utiliser un dossier diffrent pour enregistrer les donnes de session est l'une des mthodes permettant
d'assurer que les sessions courantes ne seront pas perdues lorsque le vous nettoierez le cache Symfony.
Une excellente mthode (la plus complexe) de gestion de session avec Symfony est d'utiliser un
handler d'enregistrement de session diffrent. Consultez Configurer les Sessions et les gestionnaires
de sauvegarde pour aller plus loin avec les handlers de sauvegarde de session. Il existe galement
un cookbook pour le stockage de sessions en base de donnes.
PDF brought to you by
generated on June 26, 2014
Chapter 78: Configurer le Dossier o les Fichiers pour les Sessions sont Enregistrs | 280
Listing 78-3
Pour modifier le dossier dans lequel Symfony enregistre les donnes de session, vous avez uniquement
besoin de changer la configuration du framework. Dans cet exemple, vous allez changer le dossier de
session pour app/sessions :
1
2
3
4
5
# app/config/config.yml
framework:
session:
handler_id: session.handler.native_file
save_path: "%kernel.root_dir%/sessions"
PDF brought to you by
generated on June 26, 2014
Chapter 78: Configurer le Dossier o les Fichiers pour les Sessions sont Enregistrs | 281
Listing 79-1
Listing 79-2
Chapter 79
Combler une application legacy avec les
sessions de Symfony
New in version 2.3: Cette intgration avec la session PHP legacy a t introduite en Symfony 2.3.
Si vous intgrez le framework Symfony full-stack dans une application legacy qui dmarre la session
avec session_start(), vous pouvez encore tre capable d'utiliser la gestion de session de Symfony en
utilisant le PHP Bridge session.
Si l'application a fix son propre handler PHP d'enregistrement vous pouvez spcifier null le paramtre
handler_id :
1
2
3
4
framework:
session:
storage_id: session.storage.php_bridge
handler_id: ~
Autrement, si le problme est simplement que vous ne pouvez pas viter que l'application ne dmarre la
session avec session_start(), vous pouvez encore utiliser un handler de sauvegarde bas sur Symfony
en spcifiant le handler de sauvergarde comme dans l'exemple qui suit :
1
2
3
4
framework:
session:
storage_id: session.storage.php_bridge
handler_id: session.handler.native_file
PDF brought to you by
generated on June 26, 2014
Chapter 79: Combler une application legacy avec les sessions de Symfony | 282
Si l'application legacy requiert son propose handler de sauvegarde de session, ne le surchargez
pas. A la place, configurez handler_id: ~. Notez qu'un handler de sauvegarde ne peut pas tre
chang une fois la session dmarre. Si l'application dmarre la session avant que Symfony ne soit
initialis, le handler de sauvegarde sera dja fix. Dans ce cas, vous aurez besoin de handler_id:
~`. Surchargez le handler de sauvegarde uniquement si vous tes sr que l'application legacy peut
utiliser le handler de sauvegarde de Symfony sans effets de bord et que la session n'ait pas t
dmarre avant que Symfony n'ait t initialis.
Pour plus de dtails, consultez /components/http_foundation/session_php_bridge.
PDF brought to you by
generated on June 26, 2014
Chapter 79: Combler une application legacy avec les sessions de Symfony | 283
Chapter 80
En quoi Symfony2 diffre de Symfony1
Le framework Symfony2 correspond une volution majeure si on le compare la premire version du
framework. Heureusement, comme il est bas sur l'architecture MVC, les qualits utilises pour matriser
un projet symfony1 continuent d'tre pertinentes pour dvelopper avec Symfony2. Bien entendu,
app.yml n'existe plus mais le routage, les contrleurs et les templates sont toujours prsents.
Dans ce chapitre, nous allons parcourir les diffrences entre symfony1 et Symfony2. Comme vous allez
le voir, de nombreuses oprations sont effectues de manire lgrement diffrente. Vous apprcierez
ces diffrences mineures car elles permettent des comportements stables, sans surprises, testables et une
sparation claire des logiques utilises au sein de vos applications Symfony2.
Ainsi, asseyez vous et relaxez vous pendant votre voyage du pass au prsent .
Arborescence des rpertoires
Quand vous observez un projet Symfony2 - par exemple, l'dition Standard Symfony2
1
- vous pouvez
noter une structure de rpertoire trs diffrente de celle prsente dans symfony1. Les differences sont
cependant superficielles.
Le dossier app/
Dans symfony1, vos projets avaient une ou plusieurs applications, et chacune se situait dans le rpertoire
apps/ (ex. apps/frontend). Par dfaut, dans Symfony2, vous avez une seule application reprsente
par le dossier app/. Comme dans symfony1, le dossier app/ contient une configuration spcifique
l'application. Il contient aussi le cache, les logs et les templates spcifiques l'application ainsi qu'une
classe Kernel (AppKernel), qui est l'objet de base reprsentant l'application.
A la diffrence de symfony1, trs peu de code PHP se trouve dans le dossier app/. Ce rpertoire n'est
pas destin contenir les modules maison ou les fichiers des bibliothques comme il le faisait dans
symfony1. Il correspond au rpertoire o se situent les fichiers de configuration et autres ressources
gnrales (templates, fichiers de traduction).
1. https://github.com/symfony/symfony-standard
PDF brought to you by
generated on June 26, 2014
Chapter 80: En quoi Symfony2 diffre de Symfony1 | 284
Listing 80-1
Le dossier src/
En rsum, votre code se trouve ici. Dans Symfony2, tout le code applicatif se trouve dans un bundle
(plus ou moins quivalent aux plugins de symfony1) et, par dfaut, chaque bundle se place dans le dossier
src. Sur cet aspect, le rpertoire src est un peu comme le dossier plugins dans symfony1, tout en
comportant beaucoup plus de flexibilit. De plus, pendant que vos bundles sont dans le rpertoire src/,
les bundles de bibliothques tierces se situeront dans le rpertoire vendor/.
Afin d'avoir une ide plus prcise du rpertoire src/ , pensez d'abord une application symfony1.
Premirement, certaines parties de votre code sont situes dans une ou plusieurs applications. Ces
applications incluent, le plus souvent, des modules mais peuvent galement inclure n'importe quelle
classe PHP. Vous avez probablement cr un fichier schema.yml dans le rpertoire config de votre
projet et gnr de nombreux fichiers de modles. Finalement, afin d'utiliser certaines fonctionnalits
communes, vous avez utilis de nombreuses bibliothques externes prsentes dans le rpertoire
plugins/. En d'autres termes, le code qui fait fonctionner votre application se trouve plusieurs endroits.
Dans Symfony2, la vie est plus simple car tout le code Symfony2 doit tre plac dans un bundle. Dans les
projets symfony1, tout le code applicatif pourrait tre dplac dans un ou plusieurs plugins (ce qui est une
bonne pratique en fait). Supposons que tous les modules, les classes PHP, les schmas, les configurations
des routes, etc... soient dplaces dans un plugin, alors le dossier symfony1 plugins/ serait trs similaire
au dossier Symfony2 src/.
Rsumons de manire simple, le dossier src/ est l'endroit o placer tout le code, les ressources, les
templates et tous les outils spcifiques votre projet.
Le dossier vendor/
Le dossier vendor/ est grossirement l'quivalent du dossier lib/vendor/ prsent dans symfony1, qui
tait le dossier conventionnel pour toutes les bibliothques et les bundles externes. Par dfaut, vous
trouverez les fichiers de la bibliothque Symfony2 dans ce rpertoire, ainsi que de nombreuses autres
bibliothques comme Doctrine2, Twig et Swiftmailer. Les bundles tierces intgrs Symfony2 se situent
quelque part dans le rpertoire vendor/.
Le dossier web/
Peu de choses ont chang dans le dossier web/. Les diffrences les plus notables sont l'absence des
dossiers css/, js/ et images/. C'est intentionnel. Tout comme le code PHP, les ressources devraient
galement se placer dans un bundle. Avec l'aide des commandes de la console, le dossier Resources/
public/ de chaque bundle est copi ou symboliquement li (ln -s) au dossier web/bundles/. Cela permet
de conserver vos ressources organises dans votre bundle, tout en permettant de les rendre publiques.
Afin de vous assurer que tous les bundles soient disponibles, lancez la commande suivante:
1 php app/console assets:install web
Cette commande est l'quivalent Symfony2 de la commande symfony1 plugin:publish-assets.
Auto-chargement
Un des avantages d'un framework moderne est de ne jamais s'occuper des imports de fichiers. En utilisant
un autoloader (chargeur automatique), vous pouvez faire rfrence n'importe quelle classe de votre
PDF brought to you by
generated on June 26, 2014
Chapter 80: En quoi Symfony2 diffre de Symfony1 | 285
Listing 80-2
Listing 80-3
project et ainsi tre certain qu'elle est disponible. L'autoloader a chang dans Symfony2 afin d'tre plus
universel, plus rapide, et moins dpendant du nettoyage du cache.
Dans symfony1, le chargement automatique tait ralis en recherchant, dans tout le projet, la prsence
de classes PHP et en mettant en cache cette information dans un gigantesque tableau. Ce tableau
disait symfony1 les correspondances exactes entre les fichiers et les classes. Dans l'environnement de
production, cela impliquait de nettoyer le cache lorsque des classes taient ajoutes ou dplaces.
Dans Symfony2, une nouvelle classe - UniversalClassLoader - effectue ce travail. L'ide derrire
l'autoloadeur est simple : le nom de votre classe (incluant l'espace de nom) doit correspondre avec
le chemin du fichier contenant la classe. Prenez le FrameworkExtraBundle faisant partie de l'dition
standard Symfony2 comme exemple:
1
2
3
4
5
6
7
8
9
namespace Sensio\Bundle\FrameworkExtraBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
// ...
class SensioFrameworkExtraBundle extends Bundle
{
// ...
}
Le fichier lui mme est prsent dans vendor/sensio/framework-extra-bundle/Sensio/Bundle/
FrameworkExtraBundle/SensioFrameworkExtraBundle.php. Comme vous pouvez le voir,
l'emplacement de ce fichier suit l'espace de nom de la classe. Plus prcisment, l'espace de nom
Sensio\Bundle\FrameworkExtraBundle, correspond au rpertoire o le fichier doit tre trouv (vendor/
sensio/framework-extra-bundle/Sensio/Bundle/FrameworkExtraBundle/). Cela s'explique par le
fait que dans le fichier app/autoload.php, vous avec configur Symfony pour qu'il recherche l'espace de
nom Sensio dans le rpertoire vendor/sensio:
1
2
3
4
5
6
7
// app/autoload.php
// ...
$loader->registerNamespaces(array(
...,
'Sensio' => __DIR__.'/../vendor/sensio/framework-extra-bundle',
));
Si ce fichier ne se trouve pas cette position exacte, vous recevrez une erreur Class
"Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle" does not exist.. Dans
Symfony2, une erreur "class does not exist" implique que l'espace de nom de la classe incrimine et son
emplacement physique ne correspondent pas. Plus simplement, Symfony2 recherche cette classe dans
un emplacement prcis, mais cet emplacement n'existe pas (ou contient une classe diffrente). Pour
qu'une classe soit charge automatiquement, vous n'avez jamais besoin de nettoyer le cache dans
Symfony2.
Comme mentionn prcdemment, pour que le chargement automatique fontionne, il a besoin de savoir
que l'espace de nom Sensio se trouve dans le dossier vendor/bundles et que, par exemple, l'espace de
nom Doctrine se trouve dans le dossier vendor/doctrine/orm/lib/. Cette association est entirement
sous votre contrle via le fichier app/autoload.php.
Si vous observez le contrleur HelloController de l'dition Standard de Symfony2 vous remarquerez
qu'il est plac dans l'espace de nom Acme\DemoBundle\Controller. Cependant, l'espace de nom Acme
n'est pas dfini dans le fichier app/autoload.php. En effet, par dfaut vous n'avez pas dfinir
explicitement l'emplacement de vos bundles prsents l'intrieur du rpertoire src/.
L'UniversalClassLoader est configur pour rechercher par dfaut dans le rpertoire src/ en utilisant la
mthode registerNamespaceFallbacks:
PDF brought to you by
generated on June 26, 2014
Chapter 80: En quoi Symfony2 diffre de Symfony1 | 286
Listing 80-4
Listing 80-5
Listing 80-6
Listing 80-7
1
2
3
4
5
6
// app/autoload.php
// ...
$loader->registerNamespaceFallbacks(array(
__DIR__.'/../src',
));
Utilisation de la console
Dans symfony1, la console est dans le rpertoire racine de votre projet et est appele symfony:
1 php symfony
Dans Symfony2, la console est maintenant dans le sous-dossier app et est appele console:
1 php app/console
Applications
Dans un projet symfony1, il est commun d'avoir plusieurs applications : une pour la partie
front(frontend) et une pour la partie administrative (backend) par exemple.
Dans un projet Symfony2, vous n'avez besoin de crer qu'une application (un blog, une application
intranet, ...). Le plus souvent, si vous voulez crer une seconde application, vous devriez plutt crer un
autre projet et partager certains bundles entre eux.
Et si vous avez besoin de sparer la partie frontend de la partie backend de certains bundles, vous pouvez
crer des sous-espaces de noms pour les contrleurs, des sous-rpertoires pour les templates, diffrentes
configurations smantiques, sparer les configurations de routages, et bien plus encore.
Bien sur, il n'y a rien de mal avoir plusieurs applications dans votre projet, c'est vous de dcider.
Une deuxime application impliquerait un nouveau rpertoire, par exemple my_app/, avec la mme
configuration que le rpertoire app/.
Vous pouvez lire ce sujet la dfinition des termes Projet, Application, et Bundle dans le glossaire.
Bundles et Plugins
Dans un projet symfony1, un plugin pouvait contenir de la configuration, des modules, des bibliothques
PHP, des ressources ou tout autre fichier en relation avec votre projet. Dans Symfony2, l'ide de plugin est
remplace par celle de bundle . Un bundle est encore plus puissant qu'un plugin, la preuve le coeur du
framework Symfony2 est compos d'une srie de bundles. Dans Symfony2, les bundles sont les citoyens
de premire classe si flexibles que mme le coeur de Symfony2 est lui-mme un bundle.
Dans symfony1, un plugin doit tre activ l'intrieur de la classe ProjectConfiguration:
PDF brought to you by
generated on June 26, 2014
Chapter 80: En quoi Symfony2 diffre de Symfony1 | 287
Listing 80-8
Listing 80-9
Listing 80-10
Listing 80-11
1
2
3
4
5
// config/ProjectConfiguration.class.php
public function setup()
{
$this->enableAllPluginsExcept(array(/* some plugins here */));
}
Dans Symfony2, les bundles sont activs l'intrieur du noyau applicatif:
1
2
3
4
5
6
7
8
9
10
11
12
// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new Symfony\Bundle\TwigBundle\TwigBundle(),
...,
new Acme\DemoBundle\AcmeDemoBundle(),
);
return $bundles;
}
Routage (routing.yml) et Configuration (config.yml)
Dans symfony1, les fichiers de configurations routing.yml et app.yml taient automatiquement chargs
depuis un plugin. Dans Symfony2, les configurations de routages et d'applications inclues dans un bundle
doivent tre charges manuellement. Par exemple, pour inclure un fichier de routage partir d'un bundle
appel AcmeDemoBundle, vous devez faire:
1
2
3
# app/config/routing.yml
_hello:
resource: "@AcmeDemoBundle/Resources/config/routing.yml"
Cela chargera automatiquement les routes trouves dans le fichier Resources/config/routing.yml
du bundle AcmeDemoBundle. La convention @AcmeDemoBundle` est un raccourci qui, en interne, est
remplac par le chemin complet du bundle.
Vous pouvez utiliser la mme stratgie pour charger une configuration provenant d'un bundle:
1
2
3
# app/config/config.yml
imports:
- { resource: "@AcmeDemoBundle/Resources/config/config.yml" }
Dans Symfony2, la configuration ressemble au app.yml prsent dans symfony1, except qu'elle est mieux
encadre. Dans app.yml, vous pouviez crer toutes les clefs dont vous aviez besoin. Par dfaut, ces
entres taient dnues de sens et dpendaient entirement de comment vous les utilisiez dans votre
application :
1
2
3
4
# un fichier app.yml provenant de symfony1
all:
email:
from_address: [email protected]
Dans Symfony2, vous pouvez aussi crer des clefs arbitraires l'intrieur de la clef parameters de votre
configuration:
PDF brought to you by
generated on June 26, 2014
Chapter 80: En quoi Symfony2 diffre de Symfony1 | 288
Listing 80-12
Listing 80-13
1
2
parameters:
email.from_address: [email protected]
Vous pouvez maintenant accder cette valeur depuis votre contrleur, par exemple:
1
2
3
4
public function helloAction($name)
{
$fromAddress = $this->container->getParameter('email.from_address');
}
En ralit, la configuration de Symfony2 est beaucoup plus puissante et est utilise principalement pour
configurer les objets que vous pouvez utiliser. Pour plus d'informations, consultez le chapitre intitul
Service Container .
PDF brought to you by
generated on June 26, 2014
Chapter 80: En quoi Symfony2 diffre de Symfony1 | 289
Listing 81-1
Listing 81-2
Listing 81-3
Listing 81-4
Chapter 81
Comment injecter des variables dans tous les
modles (i.e. Variables Globales)
Parfois vous voulez qu'une variable soit accessible dans tous les modles que vous utilisez. C'est possible
l'intrieur du fichier app/config/config.yml:
1
2
3
4
5
# app/config/config.yml
twig:
# ...
globals:
ga_tracking: UA-xxxxx-x
Maintenant, la variable ga_tracking est disponible dans tous les modles Twig :
1 <p>Our google tracking code is: {{ ga_tracking }} </p>
C'est aussi simple que cela! Vous pouvez aussi tirer parti du systme intgr Paramtres de Service, qui
vous permet d'isoler ou de rutiliser une valeur :
; app/config/parameters.yml
[parameters]
ga_tracking: UA-xxxxx-x
1
2
3
4
# app/config/config.yml
twig:
globals:
ga_tracking: "%ga_tracking%"
La mme variable est disponible exactement comme prcdemment.
PDF brought to you by
generated on June 26, 2014
Chapter 81: Comment injecter des variables dans tous les modles (i.e. Variables Globales) | 290
Des variables globales complexes
Si vous voulez utiliser une variable globale plus complexe, comme un objet, alors vous devez utiliser une
autre mthode. Ainsi vous aurez besoin de crer une Extension Twig et de retourner la variable globale
comme une des valeurs du tableau retourn par la mthode getGlobals.
PDF brought to you by
generated on June 26, 2014
Chapter 81: Comment injecter des variables dans tous les modles (i.e. Variables Globales) | 291
Listing 82-1
Listing 82-2
Listing 82-3
Chapter 82
Comment utiliser et enregistrer des chemins
Twig namespacs
Habituellement, lorsque vous vous rfrez un template, vous allez utiliser le format
MyBundle:Subdir:filename.html.twig (consultez Nommage de template et Emplacements).
Twig offre galement nativement une fonctionnalit appele "chemins namespacs" ("namespaced paths"
en anglais), et elle est intgre automatiquement dans vos bundles.
Prenons les chemins suivants comme exemple :
1
2
{% extends "AcmeDemoBundle::layout.html.twig" %}
{% include "AcmeDemoBundle:Foo:bar.html.twig" %}
Avec les chemins namespacs, ce qui suit fonctionne galement :
1
2
{% extends "@AcmeDemo/layout.html.twig" %}
{% include "@AcmeDemo/Foo/bar.html.twig" %}
Les deux chemins sont valides et fonctionnel par dfaut en Symfony2.
Comme bonus, la syntaxe namespace est plus rapide.
Enregistrer vos propres namespaces
Vous pouvez galement enregistrer vos propes namespaces. Supposez que vous utilisez une quelconque
bibliothque tierce incluant des templates Twig placs dans vendor/acme/foo-bar/templates.
Premirement, enregistrez un namespace pour pour ce dossier :
PDF brought to you by
generated on June 26, 2014
Chapter 82: Comment utiliser et enregistrer des chemins Twig namespacs | 292
Listing 82-4
1
2
3
4
5
# app/config/config.yml
twig:
# ...
paths:
"%kernel.root_dir%/../vendor/acme/foo-bar/templates": foo_bar
Le namespace enregistr est appel foo_bar, se rfrant au dossier vendor/acme/foo-bar/templates.
Supposant qu'il y est un fichier nomm sidebar.twig dans ce dossier, vous pouvez utiliser facilement :
1 {% include '@foo_bar/side.bar.twig' %}
PDF brought to you by
generated on June 26, 2014
Chapter 82: Comment utiliser et enregistrer des chemins Twig namespacs | 293
Listing 83-1
Listing 83-2
Chapter 83
Comment utiliser PHP plutt que Twig dans les
templates
Mme si Symfony2 utilise Twig en tant que moteur de rendu par dfaut, vous pouvez utiliser du code
PHP si vous le dsirez. Ces deux moteurs de rendu sont en effet supports part gal au sein de
Symfony2. Symfony2 ajoute mme PHP quelques possibilits utiles permettant d'crire des modles
encore plus puissants.
Rendu des templates PHP
Si vous voulez utiliser le moteur de rendu PHP, il vous faut d'abord vous assurer d'activer celui-ci dans le
fichier de configuration de votre application:
1
2
3
4
# app/config/config.yml
framework:
# ...
templating: { engines: ['twig', 'php'] }
Vous pouvez maintenant utiliser le moteur de rendu en utilisant des templates PHP plutt que des
templates Twig simplement en utilisant l'extension .php dans le nom de vos templates la place de
l'extension .twig.
Le contrleur suivant dlivre ainsi le template index.html.php
1
2
3
4
5
6
7
8
// src/Acme/HelloBundle/Controller/HelloController.php
// ...
public function indexAction($name)
{
return $this->render('AcmeHelloBundle:Hello:index.html.php', array('name' => $name));
}
PDF brought to you by
generated on June 26, 2014
Chapter 83: Comment utiliser PHP plutt que Twig dans les templates | 294
Listing 83-3
Listing 83-4
Listing 83-5
Listing 83-6
Ou vous pouvez utiliser le raccourci @Template pour afficher le template par dfaut
AcmeHelloBundle:Hello:index.html.php:
1
2
3
4
5
6
7
8
9
10
11
// src/Acme/HelloBundle/Controller/HelloController.php
// ...
/**
* @Template(engine="php")
*/
public function indexAction($name)
{
return array('name' => $name);
}
Templates de dcorations
Trs souvent, les templates au sein d'un mme projet partagent des composants communs comme l'en-
tte bien connu ou le pied de page. Chez Symfony, nous aimons penser ce problme diffremment : un
template peut tre dcor par un autre.
Le template index.html.php est dcor par layout.html.php, grce l'appel de la mthode extend() :
1
2
3
4
<!-- src/Acme/HelloBundle/Resources/views/Hello/index.html.php -->
<?php $view->extend('AcmeHelloBundle::layout.html.php') ?>
Hello <?php echo $name ?>!
La notation AcmeHelloBundle::layout.html.php vous parait peut tre familire ; c'est en effet la
mme notation qui est utilise pour rfrencer un template l'intrieur d'un contrleur. La partie ::
s'expliquant simplement par l'absence d'un sous-dossier correspondant habituellement au contrleur et
qui sera donc cherch directement la racine du dossier views/.
Maintenant, regardons d'un peu plus prs le fichier layout.html.php :
1
2
3
4
5
6
<!-- src/Acme/HelloBundle/Resources/views/layout.html.php -->
<?php $view->extend('::base.html.php') ?>
<h1>Hello Application</h1>
<?php $view['slots']->output('_content') ?>
Le dcorateur ou layout est lui-mme dcor par un autre (::base.html.php). Symfony2 supporte en
effet de multiples niveaux de dcoration : un dcorateur peut lui-mme tre dcor par un autre, et cel
indfiniment. Quand la partie bundle du nom du template est vide, les vues sont recherches dans le
dossier app/Resources/views/. Ce dossier contient donc les vues globales utilises dans tout le projet.
1
2
3
4
5
6
7
8
<!-- app/Resources/views/base.html.php -->
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title><?php $view['slots']->output('title', 'Hello Application') ?></title>
</head>
<body>
PDF brought to you by
generated on June 26, 2014
Chapter 83: Comment utiliser PHP plutt que Twig dans les templates | 295
Listing 83-7
Listing 83-8
Listing 83-9
Listing 83-10
9
10
11
<?php $view['slots']->output('_content') ?>
</body>
</html>
Pour les deux dcorateurs, l'expression $view['slots']->output('_content') est remplace par le
contenu du template fils, respectivement index.html.php et layout.html.php (voir la prochaine section
sur les slots).
Comme vous pouvez le voir, Symfony2 fourni des mthodes sur l'objet $view. Dans un template, la
variable $view est toujours disponible et rfre un objet fournissant un ensemble de mthodes rendant
le moteur de rendu puissant.
Travailler avec les slots
Un slot est un bout de code dfini dans un template et rutilisable dans tous les dcorateurs de ce
template. Ainsi dans le template index.html.php un slot title correspond :
1
2
3
4
5
6
<!-- src/Acme/HelloBundle/Resources/views/Hello/index.html.php -->
<?php $view->extend('AcmeHelloBundle::layout.html.php') ?>
<?php $view['slots']->set('title', 'Hello World Application') ?>
Hello <?php echo $name ?>!
Le dcorateur de base a dj le code pour afficher le titre dans le header html :
1
2
3
4
5
<!-- app/Resources/views/base.html.php -->
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title><?php $view['slots']->output('title', 'Hello Application') ?></title>
</head>
La mthode output() insert le contenu d'un slot et optionnellement prend une valeur par dfaut si le slot
n'est pas dfini. _content est quand lui un slot special qui contient le rendu du template enfant.
Pour les slots plus longs, il existe aussi une syntaxe tendue :
1
2
3
<?php $view['slots']->start('title') ?>
Du code html sur de nombreuses lignes
<?php $view['slots']->stop() ?>
Inclure d'autres templates
La meilleure faon de partager une partie d'un template est de dfinir un template qui pourra tre inclus
dans d'autres.
Crez un template hello.html.php :
1
2
<!-- src/Acme/HelloBundle/Resources/views/Hello/hello.html.php -->
Hello <?php echo $name ?>!
Et changez le template index.html.php pour qu'il comporte :
PDF brought to you by
generated on June 26, 2014
Chapter 83: Comment utiliser PHP plutt que Twig dans les templates | 296
Listing 83-11
Listing 83-12
Listing 83-13
1
2
3
4
<!-- src/Acme/HelloBundle/Resources/views/Hello/index.html.php -->
<?php $view->extend('AcmeHelloBundle::layout.html.php') ?>
<?php echo $view->render('AcmeHelloBundle:Hello:hello.html.php', array('name' => $name)) ?>
La mthode render() value et retourne le contenu d'un autre template (c'est exactement la mme
mthode que celle utilise dans le contrleur).
Intgrer d'autre contrleurs
Intgrer le rsultat d'un contrleur dans un template peut tre trs utile afin de factoriser certaines partie
de l'application, en particulier lors de traitements Ajax, ou quand les templates intgrs ont besoin de
certaines variables non-incluses dans le template principal.
Si vous crez une action nomme fancy, et que vous voulez l'inclure dans le template index.html.php,
utilisez simplement le code suivant :
1
2
<!-- src/Acme/HelloBundle/Resources/views/Hello/index.html.php -->
<?php echo $view['actions']->render('AcmeHelloBundle:Hello:fancy', array('name' => $name,
'color' => 'green')) ?>
Ici, la chane de caractres AcmeHelloBundle:Hello:fancy fait rfrence l'action fancy du contrleur
Hello
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/Acme/HelloBundle/Controller/HelloController.php
class HelloController extends Controller
{
public function fancyAction($name, $color)
{
// create some object, based on the $color variable
$object = ...;
return $this->render('AcmeHelloBundle:Hello:fancy.html.php', array('name' =>
$name, 'object' => $object));
}
// ...
}
Mais o est dfini le tableau d'lments $view['actions'] ? Comme $view['slots'], c'est un template
helper et la section suivante vous en apprendra plus son propos.
Utiliser les templates helpers
Le systme de rendu par template utilis par Symfony peut tre tendu facilement grace des helpers .
Les helpers sont des objets PHP qui fournissent des possibilits utiles dans le contexte des templates.
actions et slots sont ainsi deux des nombreux helpers intgrs dans Symfony2.
Crer des liens entre les pages
A l'intrieur d'une application web, crez des liens entre les pages ncessite d'utiliser des mthode
propres l'application si l'on souhaite conserver une volutivit et une maintenabilit sans failles. Ainsi
PDF brought to you by
generated on June 26, 2014
Chapter 83: Comment utiliser PHP plutt que Twig dans les templates | 297
Listing 83-14
Listing 83-15
Listing 83-16
Listing 83-17
Listing 83-18
l'utilisation d'un helper router l'intrieur des template permet de gnrer des URLs bases sur
la configuration du routage. De cette faon, toutes les URLs peuvent facilement tre mises jour
directement en changeant simplement la configuration:
1
2
3
<a href="<?php echo $view['router']->generate('hello', array('name' => 'Thomas')) ?>">
Greet Thomas!
</a>
La mthode generate() prend comme arguments le nom de la route et un tableau de paramtres. Le
nom de la route est la cl principale sous laquelle celle-ci est dfinie, les paramtres sont des valeurs
remplaant les paramtres inclus dans celle-ci :
1
2
3
4
# src/Acme/HelloBundle/Resources/config/routing.yml
hello: # The route name
pattern: /hello/{name}
defaults: { _controller: AcmeHelloBundle:Hello:index }
Utiliser des assets : images, JavaScripts, et feuilles de style
Que serait Internet sans images, sans JavaScript ou sans feuille de style ? Symfony2 fourni le tag assets
pour les utiliser facilement :
1
2
3
<link href="<?php echo $view['assets']->getUrl('css/blog.css') ?>" rel="stylesheet"
type="text/css" />
<img src="<?php echo $view['assets']->getUrl('images/logo.png') ?>" />
Les helpers assets ont pour but principal de rendre votre application plus portable. Grce ceux-ci,
vous pouvez dplacer le rpertoire principal de votre application o vous le souhaitez l'intrieur d'un
dossier web sans changer quoique ce soit dans le code de vos templates.
Echappement des variables de sortie ( Output Escaping en anglais)
Quand vous utilisez des templates, les variables peuvent tre conserves tant qu'elles ne sont pas affiches
l'utilisateur:
1 <?php echo $view->escape($var) ?>
Par dfaut, la mthode escape() assume que la variable est affiche dans un contexte HTML. Le second
argument vous permet de dfinir le contexte. Par exemple, pour afficher cette variable dans un script
JavaScript, il est possible d'utiliser le contexte js:
1 <?php echo $view->escape($var, 'js') ?>
PDF brought to you by
generated on June 26, 2014
Chapter 83: Comment utiliser PHP plutt que Twig dans les templates | 298
Listing 84-1
Chapter 84
Comment crire une Extension Twig
personnalise
La motivation principale d'crire une extension est de dplacer du code souvent utilis dans une classe
rutilisable, comme par exemple ajouter le support pour l'internationalisation. Une extension peut dfinir
des tags, des filtres, des tests, des oprateurs, des variables globales, des fonctions et des noeuds visiteurs.
Crer une extension permet aussi une meilleure sparation du code qui est excut au moment de la
compilation et du code ncessaire lors de l'excution. De ce fait, cela rend votre code plus rapide.
Avant d'crire vos propres extensions, jetez un oeil au dpt officiel des extensions Twig
1
.
Crer la Classe Extension
Pour avoir votre fonctionnalit personnalise, vous devez crer en premier lieu une classe Extension
Twig. En tant qu'exemple, nous allons crer un filtre prix afin de formatter un nombre donn en un
prix:
1
2
3
4
5
6
7
8
9
10
// src/Acme/DemoBundle/Twig/AcmeExtension.php
namespace Acme\DemoBundle\Twig;
class AcmeExtension extends \Twig_Extension
{
public function getFilters()
{
return array(
new \Twig_SimpleFilter('price', array($this, 'priceFilter')),
);
1. https://github.com/fabpot/Twig-extensions
PDF brought to you by
generated on June 26, 2014
Chapter 84: Comment crire une Extension Twig personnalise | 299
Listing 84-2
Listing 84-3
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
}
public function priceFilter($number, $decimals = 0, $decPoint = '.', $thousandsSep =
',')
{
$price = number_format($number, $decimals, $decPoint, $thousandsSep);
$price = '$' . $price;
return $price;
}
public function getName()
{
return 'acme_extension';
}
}
Dans le mme style que les filtres personnaliss, vous pouvez aussi ajouter des fonctions
personnalises et dfinir des variables globales.
Dfinir une Extension en tant que Service
Maintenant, vous devez informer le Conteneur de Service de l'existence de votre Extension Twig
nouvellement cre :
1
2
3
4
5
6
<!-- src/Acme/DemoBundle/Resources/config/services.xml -->
<services>
<service id="acme.twig.acme_extension" class="Acme\DemoBundle\Twig\AcmeExtension">
<tag name="twig.extension" />
</service>
</services>
Gardez en mmoire que les Extensions Twig ne sont pas charges de manire paresseuse (
lazy loading en anglais). Cela signifie qu'il y a de grandes chances que vous obteniez une
CircularReferenceException ou une ScopeWideningInjectionException si quelconques
services (ou votre Extension Twig dans ce cas) sont dpendants du service de requte. Pour plus
d'informations, jetez un oeil sur Comment travailler avec les champs d'applications ( scopes en
anglais).
Utiliser l'Extension personnalise
Utiliser votre Extension Twig nouvellement cre n'est en rien diffrent des autres :
1
2
{# affiche $5,500.00 #}
{{ '5500'|price }}
Passez d'autres arguments votre filtre :
PDF brought to you by
generated on June 26, 2014
Chapter 84: Comment crire une Extension Twig personnalise | 300
Listing 84-4
1
2
{# affiche $5500,2516 #}
{{ '5500.25155'|price(4, ',', '') }}
En savoir plus
Pour tudier le sujet des Extensions Twig plus en dtail, veuillez jeter un coup d'oeil la documentation
des extensions Twig
2
.
2. http://twig.sensiolabs.org/doc/advanced.html#creating-an-extension
PDF brought to you by
generated on June 26, 2014
Chapter 84: Comment crire une Extension Twig personnalise | 301
Listing 85-1
Listing 85-2
Chapter 85
Comment rendre un template sans passer par
un contrleur
Normalement, quand vous avez besoin de crer une page, vous devez crer un contrleur et rendre un
template depuis ce contrleur. Mais si vous avez besoin d'afficher un simple template, qui ne ncessite
pas de passage de paramtre, vous pouvez vous passer compltement de la cration d'un contrleur, en
utilisant le contrleur intgr FrameworkBundle:Template:template.
Par exemple, supposons que vous vouliez afficher un template
AcmeBundle:Static:privacy.html.twig, qui ne ncessite aucun passage de variable. Vous pouvez le
faire sans crer de contrleur.
1
2
3
4
5
acme_privacy:
path: /privacy
defaults:
_controller: FrameworkBundle:Template:template
template: 'AcmeBundle:Static:privacy.html.twig'
Le contrleur FrameworkBundle:Template:template va simplement afficher le template que vous aurez
dfinit comme paramtre template.
Vous pouvez aussi, bien sr, utiliser cette astuce lors du rendu de contrleurs imbriqus dans un
template. Le but de rendre un controleur depuis un template est typiquement de prparer des donnes
pour un contrleur personalis. C'est probablement utile, uniquement, si vous voulez mettre
partiellement en cache cette page (voir Mettre en cache les templates statiques).
1 {{ render(url('acme_privacy')) }}
PDF brought to you by
generated on June 26, 2014
Chapter 85: Comment rendre un template sans passer par un contrleur | 302
Listing 85-3
Mettre en cache les templates statiques
New in version 2.2: La possibilit de mettre en cache les templates rendu par
FrameworkBundle:Template:template est nouvelle en Symfony 2.2.
Puisque les templates affichs par cette mthode sont souvent statiques, il pourrait tre judicieux de les
mettre en cache. Par chance, c'est facile! En configurant quelques variables dans votre route, vous pourrez
finement contrler la faon dont vos pages sont mise en cache:
1
2
3
4
5
6
7
acme_privacy:
path: /privacy
defaults:
_controller: FrameworkBundle:Template:template
template: 'AcmeBundle:Static:privacy.html.twig'
maxAge: 86400
sharedMaxAge: 86400
Les valeurs de maxAge et sharedMaxAge sont utilises pour modifier l'objet Response cr dans le
contrleur. Pour plus d'informations sur la mise en cache, voir /book/http_cache.
Il existe galement un paramtre private (non prsent ici). Par dfaut, la rponse est faite de manire
public (la rponse peut tre mise en cache, la fois par les caches privs et les caches publics) tant que les
paramtres maxAge ou sharedMaxAge sont renseigns. Si elle est dfinie true la rponse devient priv
(la rponse concerne un unique utilisateur et ne doit pas tre stocke dans les caches publics).
PDF brought to you by
generated on June 26, 2014
Chapter 85: Comment rendre un template sans passer par un contrleur | 303
Listing 86-1
Listing 86-2
Listing 86-3
Chapter 86
Comment simuler une authentification HTTP
dans un Test Fonctionnel
Si votre application requiert une authentification HTTP, vous pouvez transmettre le nom d'utilisateur et
le mot de passe comme variable serveurs la mthode createClient():
1
2
3
4
$client = static::createClient(array(), array(
'PHP_AUTH_USER' => 'username',
'PHP_AUTH_PW' => 'pa$$word',
));
Vous pouvez aussi les surcharger directement dans l'objet requte:
1
2
3
4
$client->request('DELETE', '/post/12', array(), array(), array(
'PHP_AUTH_USER' => 'username',
'PHP_AUTH_PW' => 'pa$$word',
));
Quand votre application utilise un form_login, vous pouvez effectuer vos tests plus simplement en
permettant la configuration de test d'utiliser l'authentification HTTP. De cette manire, vous pouvez
utiliser le code dcrit ci-dessus pour vous authentifier dans les tests, mais nanmoins conserver le fait
que vos utilisateurs doivent se connecter via l'usuel form_login. Pour cela, dans la configuration de test
vous devez inclure la cl http_basic dans votre pare-feu Symfony, l'intrieur du conteneur traitant le
form_login :
1
2
3
4
5
# app/config/config_test.yml
security:
firewalls:
your_firewall_name:
http_basic:
PDF brought to you by
generated on June 26, 2014
Chapter 86: Comment simuler une authentification HTTP dans un Test Fonctionnel | 304
Listing 87-1
Chapter 87
Comment simuler une authentification avec un
token dans un test fonctionnel
Les requtes d'authentification dans les tests fonctionnels peuvent ralentir la suite de tests. Cela peut
devenir un problme particulirement lorsque le form_login est utilis, puisque cela requiert des
requtes additionnelles pour remplir et soumettre le formulaire.
L'une des solutions est de configurer votre firewall utiliser http_basic en environnement de test
comme expliqu dans Comment simuler une authentification HTTP dans un Test Fonctionnel. Une autre
faon de faire serait de crer un token vous-mme et de le stocker dans une session. En faisant cela, vous
devez vous assurer que le cookie appropri est envoy avec la requte. L'exemple suivant vous montre
comment faire
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php
namespace Acme\DemoBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\BrowserKit\Cookie;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
class DemoControllerTest extends WebTestCase
{
private $client = null;
public function setUp()
{
$this->client = static::createClient();
}
public function testSecuredHello()
{
$this->logIn();
$this->client->request('GET', '/demo/secured/hello/Fabien');
$this->assertTrue($this->client->getResponse()->isSuccessful());
PDF brought to you by
generated on June 26, 2014
Chapter 87: Comment simuler une authentification avec un token dans un test fonctionnel | 305
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
$this->assertGreaterThan(0, $crawler->filter('html:contains("Hello
Fabien")')->count());
}
private function logIn()
{
$session = $this->client->getContainer()->get('session');
$firewall = 'secured_area';
$token = new UsernamePasswordToken('admin', null, $firewall, array('ROLE_ADMIN'));
$session->set('_security_'.$firewall, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$this->client->getCookieJar()->set($cookie);
}
}
La technique dcrite dans Comment simuler une authentification HTTP dans un Test Fonctionnel est
plus propre et de ce fait prfre.
PDF brought to you by
generated on June 26, 2014
Chapter 87: Comment simuler une authentification avec un token dans un test fonctionnel | 306
Listing 88-1
Listing 88-2
Chapter 88
Comment tester les interactions de multiples
clients
Si vous avez besoin de simuler des interactions entre diffrents clients (pour un chat par exemple),
crez plusieurs clients:
1
2
3
4
5
6
7
8
$harry = static::createClient();
$sally = static::createClient();
$harry->request('POST', '/say/sally/Hello');
$sally->request('GET', '/messages');
$this->assertEquals(201, $harry->getResponse()->getStatus());
$this->assertRegExp('/Hello/', $sally->getResponse()->getContent());
Cependant cela ne fonctionnera que si vous ne maintenez pas dans votre application un tat global et si
aucune des bibliothques tierces n'utilise de techniques requrant des tats globaux. Dans ces cas vous
devrez isoler les clients:
1
2
3
4
5
6
7
8
9
10
11
$harry = static::createClient();
$sally = static::createClient();
$harry->insulate();
$sally->insulate();
$harry->request('POST', '/say/sally/Hello');
$sally->request('GET', '/messages');
$this->assertEquals(201, $harry->getResponse()->getStatus());
$this->assertRegExp('/Hello/', $sally->getResponse()->getContent());
Les clients isols excute de manire transparente leur requte dans un processus PHP ddi et sain ,
et vitent donc quelconque effet de bord.
PDF brought to you by
generated on June 26, 2014
Chapter 88: Comment tester les interactions de multiples clients | 307
Comme un client isol est plus lent, vous pouvez garder un client dans le processus principal et
n'isoler que les autres.
PDF brought to you by
generated on June 26, 2014
Chapter 88: Comment tester les interactions de multiples clients | 308
Listing 89-1
Chapter 89
Comment utiliser le Profiler dans un test
fonctionnel
Il est grandement recommand qu'un test fonctionnel ne teste que l'objet Response. Cependant si vous
crivez des tests qui surveillent vos serveurs de productions, vous dsirerez peut-tre crire des tests
sur les donnes provenant du profiler. En effet ce dernier fournit des mthodes efficaces, permettant de
contrler de nombreux comportements et d'appliquer certaines mtriques.
Le Profiler de Symfony2 collecte de nombreuses informations chaque requte. Utilisez celle-ci pour
vrifier le nombre d'appels la base de donnes, le temps pass dans l'excution du framework, ...
Avant de pouvoir crire des assertions, vous devez activer le profiler et vous assurez qu'il est disponible
(il est activ par dfaut dans l'environnement de test ):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class HelloControllerTest extends WebTestCase
{
public function testIndex()
{
$client = static::createClient();
// Active le profiler pour la prochaine requte
// (cela ne fait rien si le profiler n'est pas disponible)
$client->enableProfiler();
$crawler = $client->request('GET', '/hello/Fabien');
// Ecrire des assertions concernant la rponse
// Vrifier que le profiler est activ
if ($profile = $client->getProfile()) {
// Vrifier le nombre de requtes
$this->assertLessThan(10, $profile->getCollector('db')->getQueryCount());
// Vrifier le temps utilis par le framework
$this->assertLessThan((500, $profile->getCollector('time')->getDuration());
}
PDF brought to you by
generated on June 26, 2014
Chapter 89: Comment utiliser le Profiler dans un test fonctionnel | 309
Listing 89-2
23
24
}
}
Si un test choue cause des donnes du profilage (trop d'appels la base de donnes par exemple),
vous pouvez utiliser le Profiler Web afin danalyser la requte aprs que les tests soient termins. Cela est
ralisable simplement en intgrant le token suivant dans les messages d'erreurs:
1
2
3
4
5
$this->assertLessThan(
30,
$profile->get('db')->getQueryCount(),
sprintf('Checks that query count is less than 30 (token %s)', $profile->getToken())
);
Le profiler conserve des donnes diffrentes suivant l'environnement utilis (particulirement si
vous utilisez SQLite, ce qui est la configuration par dfaut).
Les informations du profiler sont disponibles mme si vous isolez un client ou utilisez une couche
HTTP particulire pour vos tests.
L'API intgre vous permet de connatre les mthodes et les interfaces disponibles : data collectors.
PDF brought to you by
generated on June 26, 2014
Chapter 89: Comment utiliser le Profiler dans un test fonctionnel | 310
Listing 90-1
Chapter 90
Comment tester du code interagissant avec
une base de donnes
Si votre code interagit avec une base de donnes, si par exemple il lit ou enregistre des donnes, vous
devez ajuster vos tests pour prendre en compte ceci. Il y a de nombreuses faons de grer ce cas. Dans
un test unitaire, vous pouvez crer un bouchon (mock en anglais) pour un Repository et l'utiliser pour
retourner les objets attendus. Dans un test fonctionnel, vous devriez avoir besoin de prparer une base
de donnes de test avec des valeurs prdfinies pour assurer que votre test fonctionne toujours avec les
mmes donnes.
Si vous voulez tester directement vos requtes, consultez Comment tester les dpts Doctrine.
Mocker le Repository dans un test unitaire
Si vous souhaitez tester du code dpendant d'un repository Doctrine tout en l'isolant, vous aurez besoin
de mocker ce Repository. Normalement, vous injectez l' EntityManager dans votre classe et l'utilisez
pour rcuprer le repository. Cela rend les choses un peu plus difficile puisque vous avez besoin de
mocker et l' EntityManager et votre classe de repository.
Il est possible (et c'est une bonne ide) d'injecter votre repository directement en l'enregistrant
comme un service factory. Cela demande un peu plus de travail pour le paramtrage, mais cela rend
les tests plus faciles puisque vous n'aurez besoin de mocker que le repository.
Supposons que la classe que vous voulez tester ressemble ceci:
1
2
3
namespace Acme\DemoBundle\Salary;
use Doctrine\Common\Persistence\ObjectManager;
PDF brought to you by
generated on June 26, 2014
Chapter 90: Comment tester du code interagissant avec une base de donnes | 311
Listing 90-2
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class SalaryCalculator
{
private $entityManager;
public function __construct(ObjectManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function calculateTotalSalary($id)
{
$employeeRepository =
$this->entityManager->getRepository('AcmeDemoBundle::Employee');
$employee = $employeeRepository->find($id);
return $employee->getSalary() + $employee->getBonus();
}
}
Puisque l' ObjectManager est inject dans la classe travers le constructeur, il est facile de passer un objet
mock dans un test
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
use Acme\DemoBundle\Salary\SalaryCalculator;
class SalaryCalculatorTest extends \PHPUnit_Framework_TestCase
{
public function testCalculateTotalSalary()
{
// Premirement, mockez l'objet qui va tre utilis dans le test
$employee = $this->getMock('\Acme\DemoBundle\Entity\Employee');
$employee->expects($this->once())
->method('getSalary')
->will($this->returnValue(1000));
$employee->expects($this->once())
->method('getBonus')
->will($this->returnValue(1100));
// Maintenant, mocker le repository pour qu'il retourne un mock de l'objet emloyee
$employeeRepository = $this->getMockBuilder('\Doctrine\ORM\EntityRepository')
->disableOriginalConstructor()
->getMock();
$employeeRepository->expects($this->once())
->method('find')
->will($this->returnValue($employee));
// Et enfin, mockez l'EntityManager pour qu'il retourne un mock du repository
$entityManager =
$this->getMockBuilder('\Doctrine\Common\Persistence\ObjectManager')
->disableOriginalConstructor()
->getMock();
$entityManager->expects($this->once())
->method('getRepository')
->will($this->returnValue($employeeRepository));
$salaryCalculator = new SalaryCalculator($entityManager);
$this->assertEquals(2100, $salaryCalculator->calculateTotalSalary(1));
PDF brought to you by
generated on June 26, 2014
Chapter 90: Comment tester du code interagissant avec une base de donnes | 312
Listing 90-3
}
}
Dans cet exemple, vous construisez les mocks de l'intrieur vers l'extrieur, en crant premirement
l'employee qui est retourn par le Repository, qui lui-mme est retourn par l' entityManager. De cette
faon, aucune vraie classe n'est implique dans le test.
Changer le paramtrage de la base de donnes pour les tests fonctionnels
Si vous avez des tests fonctionnels, vous souhaitez qu'ils interagissent avec une vraie base de donnes. La
plupart du temps, vous souhaitez une base de donnes ddie durant le dveloppement de l'application
ainsi qu'tre capable de nettoyer la base de donnes avant chaque test.
Pour faire cela, il vous est possible de spcifier la configuration de la base de donnes, qui remplacera la
configuration par dfaut:
1
2
3
4
5
6
7
8
# app/config/config_test.yml
doctrine:
# ...
dbal:
host: localhost
dbname: testdb
user: testdb
password: testdb
Assurez vous que votre base de donnes tourne sur localhost, qu'elle a une base de donnes dfinie et que
les droits utilisateurs sont configurs.
PDF brought to you by
generated on June 26, 2014
Chapter 90: Comment tester du code interagissant avec une base de donnes | 313
Listing 91-1
Chapter 91
Comment tester les dpts Doctrine
Les tests unitaires des dpts Doctrines lintrieur d'un projet Symfony ne sont pas recommands.
Lorsque vous travaillez avec un dpt, vous travaillez avec quelque chose qui est rellement sens tre
test avec une vritable connexion une base de donnes.
Heureusement, vous pouvez facilement tester vos requtes avec une vraie base de donnes, comme dcrit
ci-dessous
Tests fonctionnels
Si vous avez besoin de tester lexcution d'une requte, vous devez dmarrer le kernel afin d'obtenir une
connexion valide. Dans ce cas, votre classe doit hriter de WebTestCase, une classe qui simplifiera les
processus de test:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/Acme/StoreBundle/Tests/Entity/ProductRepositoryFunctionalTest.php
namespace Acme\StoreBundle\Tests\Entity;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class ProductRepositoryFunctionalTest extends WebTestCase
{
/**
* @var \Doctrine\ORM\EntityManager
*/
private $em;
public function setUp()
{
static::$kernel = static::createKernel();
static::$kernel->boot();
$this->em = static::$kernel->getContainer()->get('doctrine.orm.entity_manager');
}
public function testSearchByCategoryName()
{
PDF brought to you by
generated on June 26, 2014
Chapter 91: Comment tester les dpts Doctrine | 314
22
23
24
25
26
27
28
29
$products = $this->em
->getRepository('AcmeStoreBundle:Product')
->searchByCategoryName('foo')
;
$this->assertCount(1, $products);
}
}
PDF brought to you by
generated on June 26, 2014
Chapter 91: Comment tester les dpts Doctrine | 315
Listing 92-1
Listing 92-2
Listing 92-3
Chapter 92
Comment personnaliser le processus de
bootstrap avant les tests
Parfois, lorsque vous lancez des tests, vous avez besoin d'ajouter des choses au processus d'initialisation
(bootstrap) juste avant de les lancer. Par exemple, si vous lancez un test fonctionnel et que vous avez
introduit une nouvelle ressource de traduction, alors vous devrez vider votre cache avant de lancer ces
tests. Cet article vous explique comment faire
D'abord, ajouter le fichier suivant:
1
2
3
4
5
6
7
8
9
10
// app/tests.bootstrap.php
if (isset($_ENV['BOOTSTRAP_CLEAR_CACHE_ENV'])) {
passthru(sprintf(
'php "%s/console" cache:clear --env=%s --no-warmup',
__DIR__,
$_ENV['BOOTSTRAP_CLEAR_CACHE_ENV']
));
}
require __DIR__.'/bootstrap.php.cache';
Remplacer le fichier de test bootstrap bootstrap.php.cache dans app/phpunit.xml.dist par
tests.bootstrap.php :
1
2
<!-- app/phpunit.xml.dist -->
bootstrap = "tests.bootstrap.php"
Maintenant, vous pouvez dfinir pour quel environnement vous voulez vider le cache dans votre fichier
phpunit.xml.dist :
1
2
3
4
<!-- app/phpunit.xml.dist -->
<php>
<env name="BOOTSTRAP_CLEAR_CACHE_ENV" value="test"/>
</php>
PDF brought to you by
generated on June 26, 2014
Chapter 92: Comment personnaliser le processus de bootstrap avant les tests | 316
Cela devient maintenant une variable d'environnement (c-a-d $_ENV) qui est disponible dans votre fichier
bootstrap personnalis (tests.bootstrap.php).
PDF brought to you by
generated on June 26, 2014
Chapter 92: Comment personnaliser le processus de bootstrap avant les tests | 317
Listing 93-1
Chapter 93
Comment crer une Contrainte de Validation
Personnalise
Vous pouvez crer une contrainte personnalise en tendant la classe Constraint
1
. A titre d'exemple,
nous allons crer un simple validateur qui vrifie qu'une chane de caractres ne contient que des
caractres alphanumriques.
Crer une classe de contrainte
Tout d'abord, vous devez crer une classe de contrainte qui tend la classe Constraint
2
:
1
2
3
4
5
6
7
8
9
10
11
12
// src/Acme/DemoBundle/Validator/Constraints/ContainsAlphanumeric.php
namespace Acme\DemoBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
*/
class ContainsAlphanumeric extends Constraint
{
public $message = 'La chane "%string%" contient un caractre non autoris : elle ne
peut contenir que des lettres et des chiffres.';
}
L'annotation @Annotation est ncessaire pour cette nouvelle contrainte afin de la rendre disponible
via les annotations dans les autres classes. Les options de votre contrainte sont reprsentes par des
proprits publiques dans la classe de contrainte.
1. http://api.symfony.com/2.3/Symfony/Component/Validator/Constraint.html
2. http://api.symfony.com/2.3/Symfony/Component/Validator/Constraint.html
PDF brought to you by
generated on June 26, 2014
Chapter 93: Comment crer une Contrainte de Validation Personnalise | 318
Listing 93-2
Listing 93-3
Listing 93-4
Crer le validateur en lui-mme
Comme vous pouvez le voir, une classe contrainte est minimale. La validation est ralise par un
autre validateur de contrainte. La classe de validation est dfinie par l'implmentation de la mthode
validatedBy(), incluant une logique prdfinie:
1
2
3
4
5
// dans la classe de base Symfony\Component\Validator\Constraint
public function validatedBy()
{
return get_class($this).'Validator';
}
Si vous crez une contrainte personnalise (ex: MyConstraint), Symfony2 recherchera automatiquement
une autre classe, MyConstraintValidator lorsqu'il effectue la validation.
La classe validatrice ne requiert qu'une mthode : validate:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Acme/DemoBundle/Validator/Constraints/ContainsAlphanumericValidator.php
namespace Acme\DemoBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class ContainsAlphanumericValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
if (!preg_match('/^[a-zA-Za0-9]+$/', $value, $matches)) {
$this->context->addViolation($constraint->message, array('%string%' =>
$value));
}
}
}
La mthode validate ne retourne pas de valeur; la place, elle ajoute des violations de contraintes
la proprit context du validateur grce l'appel la mthode addViolation dans le cas
d'erreurs de validation. Par consquent, une valeur peut tre considre comme valide si aucune
violation de contrainte n'est ajoute au contexte. Le premier paramtre de l'appel addViolation
est le message d'erreur utilis pour la violation.
New in version 2.1: La mthode isValid est dprcie au profit de validate dans Symfony 2.1. La
mthode setMessage est galement dprcie, en faveur de l'appel la mthode addViolation du
contexte.
Utiliser le nouveau validateur
Utiliser un validateur personnalis est trs facile, tout comme ceux fournis par Symfony2 lui-mme :
1
2
3
# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\DemoBundle\Entity\AcmeEntity:
properties:
PDF brought to you by
generated on June 26, 2014
Chapter 93: Comment crer une Contrainte de Validation Personnalise | 319
Listing 93-5
Listing 93-6
Listing 93-7
Listing 93-8
4
5
6
name:
- NotBlank: ~
- Acme\DemoBundle\Validator\Constraints\ContainsAlphanumeric: ~
Si votre contrainte contient des options, alors elles devraient tre des proprits publiques de la classe
de contrainte personnalise que vous avez cre plus tt. Ces options peuvent tre configures comme
toutes les options des contraintes de Symfony.
Contraintes de validation avec dpendances
Si votre validateur possde des dpendances, comme une connexion une base de donnes, il faudra le
configurer comme un service dans le conteneur d'injection de dpendances. Ce service doit inclure le tag
validator.constraint_validator et un attribut alias :
1
2
3
4
5
services:
validator.unique.your_validator_name:
class: Fully\Qualified\Validator\Class\Name
tags:
- { name: validator.constraint_validator, alias: alias_name }
Votre classe contrainte devrait maintenant utiliser cet alias afin de rfrencer le validateur appropri:
1
2
3
4
public function validatedBy()
{
return 'alias_name';
}
Comme mentionn prcdemment, Symfony2 recherchera automatiquement une classe nomme d'aprs
le nom de la contrainte et suffixe par Validator. Si votre validateur de contrainte est dfini comme un
service, il est important de surcharger la mthode validatedBy() afin qu'elle renvoie l'alias utilis pour
dfinir le service ; autrement, Symfony2 n'utilisera pas le service de validation, et instanciera la classe,
sans injecter les dpendances requises.
Contrainte de validation de classe
Outre la validation d'une proprit de classe, une contrainte peut avoir une porte de classe en
renseignant une cible:
1
2
3
4
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
Avec ceci, la mthode validate() du validateur prend un objet comme premier argument:
1
2
3
4
5
6
7
8
class ProtocolClassValidator extends ConstraintValidator
{
public function validate($protocol, Constraint $constraint)
{
if ($protocol->getFoo() != $protocol->getBar()) {
$this->context->addViolationAt('foo', $constraint->message, array(), null);
}
PDF brought to you by
generated on June 26, 2014
Chapter 93: Comment crer une Contrainte de Validation Personnalise | 320
Listing 93-9
9
10
}
}
Notez bien qu'une contrainte de validation de classe est applique la classe elle-mme, et pas la
proprit :
1
2
3
4
# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\DemoBundle\Entity\AcmeEntity:
constraints:
- ContainsAlphanumeric
PDF brought to you by
generated on June 26, 2014
Chapter 93: Comment crer une Contrainte de Validation Personnalise | 321
Listing 94-1
Chapter 94
Comment crer des web services SOAP
l'intrieur d'un contrleur Symfony2
Configurer un contrleur afin qu'il agisse comme un serveur est ralis simplement avec quelques outils.
Vous devez, bien sr, avoir install l'extension PHP SOAP
1
. Comme l'extension PHP SOAP ne peut
actuellement pas gnrer un WSDL, vous devez soit en crer un, soit utiliser un gnrateur provenant
d'une bibliothque tierce.
Il existe de nombreuses implmentations de serveur SOAP disponibles compatibles avec PHP.
Zend SOAP
2
et NuSOAP
3
en sont deux exemples. Bien que nous utilisions l'extension PHP SOAP
dans nos exemples, l'ide gnrale devrait tre applicable aux autres implementations.
SOAP fonctionne en exposant les mthodes d'un objet PHP une entit externe (c'est--dire le
programme utilisant le service SOAP). Pour commencer, crez une classe - HelloService - qui reprsente
les fonctionnalits que vous mettrez disposition dans votre service SOAP. Dans ce cas, le service SOAP
permettra un client d'appeler la mthode nomme hello, qui engendrera l'envoi d'un courriel:
1
2
3
4
5
6
7
8
9
10
11
12
13
// src/Acme/SoapBundle/Services/HelloService.php
namespace Acme\SoapBundle\Services;
class HelloService
{
private $mailer;
public function __construct(\Swift_Mailer $mailer)
{
$this->mailer = $mailer;
}
public function hello($name)
1. http://php.net/manual/fr/book.soap.php
2. http://framework.zend.com/manual/fr/zend.soap.server.html
3. http://sourceforge.net/projects/nusoap
PDF brought to you by
generated on June 26, 2014
Chapter 94: Comment crer des web services SOAP l'intrieur d'un contrleur Symfony2 | 322
Listing 94-2
Listing 94-3
14
15
16
17
18
19
20
21
22
23
24
25
26
{
$message = \Swift_Message::newInstance()
->setTo('[email protected]')
->setSubject('Hello Service')
->setBody($name . ' dit bonjour !');
$this->mailer->send($message);
return 'Bonjour, '.$name;
}
}
Ensuite, vous devrez permettre Symfony de crer une instance de cette classe. Comme la classe envoie
un courriel, le service prendra comme argument une instance Swift_Mailer. En utilisant le conteneur de
service, nous pouvons configurer Symfony et lui permettre de construire l'objet HelloService adquat :
# app/config/config.yml
services:
hello_service:
class: Acme\SoapBundle\Services\HelloService
arguments: [@mailer]
Voici un exemple de contrleur capable de grer une requte SOAP. Si indexAction() est accessible via
la route /soap, alors le document WSDL peut tre atteint via /soap?wsdl.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
namespace Acme\SoapBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class HelloServiceController extends Controller
{
public function indexAction()
{
$server = new \SoapServer('/path/to/hello.wsdl');
$server->setObject($this->get('hello_service'));
$response = new Response();
$response->headers->set('Content-Type', 'text/xml; charset=ISO-8859-1');
ob_start();
$server->handle();
$response->setContent(ob_get_clean());
return $response;
}
}
Notez les appels ob_start() et ob_get_clean(). Ces mthodes contrlent le tampon de sortie
4
qui
vous permettent d'intercepter les flux de sortie de la mthode $server->handle(). Cela est ncessaire
car Symfony attend de votre contrleur un objet Response contenant ce flux. Rappelez vous aussi de
dfinir l'entte HTTP Content-Type comme text/xml puisque c'est ce quoi le client s'attendra.
Vous utilisez donc ob_start() pour commencer la mise en tampon de STDOUT et utilisez
4. http://php.net/manual/fr/book.outcontrol.php
PDF brought to you by
generated on June 26, 2014
Chapter 94: Comment crer des web services SOAP l'intrieur d'un contrleur Symfony2 | 323
Listing 94-4
Listing 94-5
ob_get_clean() pour mettre la sortie dans le contenu de la Rponse et vider le tampon de sortie.
Finalement, vous tes prt retourner l'objet Response.
Voici un exemple qui appelle un service en utilisant le client NuSOAP
5
. Cet exemple suppose que le
indexAction prsent dans le contrleur ci-dessus est accessible via la route /soap:
1
2
3
$client = new \Soapclient('http://example.com/app.php/soap?wsdl', true);
$result = $client->call('hello', array('name' => 'Scott'));
Un exemple d'un flux WSDL :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?xml version="1.0" encoding="ISO-8859-1"?>
<definitions xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tns="urn:arnleadservicewsdl"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns="http://schemas.xmlsoap.org/wsdl/"
targetNamespace="urn:helloservicewsdl">
<types>
<xsd:schema targetNamespace="urn:hellowsdl">
<xsd:import namespace="http://schemas.xmlsoap.org/soap/encoding/" />
<xsd:import namespace="http://schemas.xmlsoap.org/wsdl/" />
</xsd:schema>
</types>
<message name="helloRequest">
<part name="name" type="xsd:string" />
</message>
<message name="helloResponse">
<part name="return" type="xsd:string" />
</message>
<portType name="hellowsdlPortType">
<operation name="hello">
<documentation>Hello World</documentation>
<input message="tns:helloRequest"/>
<output message="tns:helloResponse"/>
</operation>
</portType>
<binding name="hellowsdlBinding" type="tns:hellowsdlPortType">
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="hello">
<soap:operation soapAction="urn:arnleadservicewsdl#hello" style="rpc"/>
<input>
<soap:body use="encoded" namespace="urn:hellowsdl"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
</input>
<output>
<soap:body use="encoded" namespace="urn:hellowsdl"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
</output>
</operation>
</binding>
<service name="hellowsdl">
<port name="hellowsdlPort" binding="tns:hellowsdlBinding">
5. http://sourceforge.net/projects/nusoap
PDF brought to you by
generated on June 26, 2014
Chapter 94: Comment crer des web services SOAP l'intrieur d'un contrleur Symfony2 | 324
46
47
48
49
<soap:address location="http://example.com/app.php/soap" />
</port>
</service>
</definitions>
PDF brought to you by
generated on June 26, 2014
Chapter 94: Comment crer des web services SOAP l'intrieur d'un contrleur Symfony2 | 325
Listing 95-1
Chapter 95
Comment crer et stocker un projet Symfony2
dans git
Bien que cet article soit spcifique git, les mmes principes gnriques s'appliqueront si vous
stockez votre projet dans Subversion.
Une fois que vous aurez lu La cration de pages avec Symfony2 et que vous deviendrez familier avec
l'usage de Symfony, vous serez sans aucun doute prt dmarrer votre propre projet. Dans cet article
du Cookbook, vous allez apprendre la meilleure faon qui soit de dmarrer un nouveau projet Symfony2
stock dans le systme de gestion de contrle de source git
1
.
Configuration Initiale du Projet
Pour dmarrer, vous aurez besoin de tlcharger Symfony et d'initialiser votre dpt local git :
1. Tlcharger Symfony2 Standard Edition
2
sans les vendors .
2. Dzippez/dtarez la distribution. Cela va crer un dossier nomm Symfony avec votre nouvelle
structure de projet, les fichiers de configuration, etc. Renommez-le en ce que vous voulez.
3. Crez un nouveau fichier nomm .gitignore la racine de votre nouveau projet (par exemple
: ct du fichier deps) et coller ce qui suit dedans. Les fichiers correspondants ces patterns
seront ignors par git :
1
2
3
4
/web/bundles/
/app/bootstrap*
/app/cache/*
/app/logs/*
1. http://git-scm.com/
2. http://symfony.com/download
PDF brought to you by
generated on June 26, 2014
Chapter 95: Comment crer et stocker un projet Symfony2 dans git | 326
Listing 95-2
Listing 95-3
Listing 95-4
5
6
/vendor/
/app/config/parameters.yml
Vous pouvez aussi avoir un fichier .gitignore qui peut tre utilis sur tout votre systme, dans
ce cas, vous pourrez trouver plus d'informations ici : Github .gitignore
3
. De cette manire, vous
pouvez exclure les fichiers/dossiers souvent utiliss par votre IDE pour l'ensemble de vos projets.
4. Copiez app/config/parameters.yml vers app/config/parameters.yml.dist. Le fichier
parameters.yml est ignor par git (voir ci-dessus) afin que les paramtres spcifiques la
machine comme les mots de passe de base de donnes ne soient pas committs. En crant le
fichier parameters.yml.dist, les nouveaux dveloppeurs peuvent rapidement cloner le projet,
copier ce fichier vers parameters.yml, l'adapter, et commencer dvelopper.
5. Initialisez votre dpt git :
1 $ git init
6. Ajoutez tous les fichiers initiaux dans git :
1 $ git add .
7. Crez un commit initial avec votre nouveau projet :
1 $ git commit -m "Initial commit"
8. Finalement, tlchargez toutes les bibliothques tierces en utilisant composer. vous pourrez trouver
plus d'informations sur Mettre jour les Vendors.
A ce point, vous disposez d'un projet Symfony2 totalement fonctionnel qui est correctement committ
sous git. Vous pouvez immdiatement commencer dvelopper, en committant les nouveaux
changements dans votre dpt git.
Vous pouvez continuer en lisant le chapitre La cration de pages avec Symfony2 pour en apprendre
davantage sur comment configurer et dvelopper votre application en interne.
L'Edition Standard Symfony2 est fournie avec des exemples d'utilisation. Pour supprimer le code
de dmonstration, suivez les instructions contenues dans le fichier Readme de la Standard Edition
4
.
Grer les bibliothques vendors avec bin/vendors et deps
Comment a marche ?
Chaque projet Symfony utilise des bibliothques tierces regroupes sous l'appellation vendors . D'une
faon ou d'une autre, le but est de vous permettre de tlcharger ces bibliothques dans le rpertoire
vendor/ et de pouvoir obtenir pour chacune la version dsire.
Par dfaut, ces bibliothques sont tlcharges en excutant le script de tlchargement php bin/
vendors install. Ce script utilise le fichier deps` situ la racine de votre projet. Ce fichier au format
3. https://help.github.com/articles/ignoring-files
4. https://github.com/symfony/symfony-standard/blob/master/README.md
PDF brought to you by
generated on June 26, 2014
Chapter 95: Comment crer et stocker un projet Symfony2 dans git | 327
Listing 95-5
INI contient la liste des bibliothques ncessaires votre projet, ainsi que le rpertoire cible o chacune
doit tre tlcharge, et (de manire optionnelle) la version tlcharger. Le script bin/vendors` utilise git
pour ce tlchargement, car la plupart des bibliothques sont gres et disponibles via git. Le script bin/
vendors se base galement sur le fichier deps.lock, lequel vous permet d'arrter la version une rvision
prcise.
Nota bene : ces bibliothques vendor ne sont pas gres dans votre dpt; ce sont de simples fichiers
installs (et ignors du dpt git) dans le rpertoire vendor/ par le script bin/vendors. Or, puisque toute
l'information ncessaire pour tlcharger ces fichiers est disponible dans deps et deps.lock (qui, eux,
sont grs par votre dpt), tout autre dveloppeur peut utiliser votre projet, lancer php bin/vendors
install, et obtenir les mmes bibliothques (et version). Vous contrlez donc exactement le contenu de
chaque bibliothque, sans avoir besoin de le versionner dans votre dpt.
Ainsi, lorsqu'un dveloppeur veut travailler sur votre projet, il lui suffit de lancer le script php bin/
vendors install pour s'assurer que toutes les bibliothques ncessaires soient tlcharges.
Mettre jour Symfony
Puisque Symfony est une bibliothque tierce de votre projet et qu'elle est donc gre entirement
via deps et``deps.lock``, mettre jour Symfony signifie simplement mettre jour ces deux fichiers
afin qu'ils correspondent la dernire version de l'dition standard de Symfony.
Bien sr, si vous avez ajout des entres aux fichiers deps ou deps.lock, veillez ne remplacer que
les parties originales afin de ne pas supprimer les entres supplmentaires.
Hacker les bibliothques vendor
Parfois, il est ncessaire de tlcharger une version prcise (branche, tag) d'une bibliothque. Vous
pouvez le faire directement dans le fichier deps :
1
2
3
4
[AcmeAwesomeBundle]
git=http://github.com/johndoe/Acme/AwesomeBundle.git
target=/bundles/Acme/AwesomeBundle
version=ma-version-trop-bien
L'option git dfinit l'URL de la bibliothque. Elle peut utiliser divers protocoles, comme
http:// ou git://.
L'option target dfinit le rpertoire cible, le rpertoire dans lequel la bibliothque va tre
tlcharge. Par convention, les bundles devraient aller dans le rpertoire vendor/bundles/
Acme, les autres bibliothques iront dans vendor/ma-biblio-trop-bien, le rpertoire vendor
est galement le rpertoire cible par dfaut.
L'option version vous permet de dfinir une rvision spcifique. Vous pouvez lui attribuer
un nom de tag (version=origin/0.42) ou de branche (refs/remotes/origin/ma-branche-
trop-bien). Version par dfaut: origin/HEAD.
Flux de mise jour
lors de l'xcution de php bin/vendors install, le script vrifie d'abord si le rpertoire cible existe pour
chaque bibliothque. Si ce n'est pas le cas, il effectue un git clone.
Puis, il effectue un git fetch origin, puis git reset --hard ma-version-trop-bien.
Le rpertoire sera donc clon une seule et unique fois. Pour faire des changements lis au dpt distant il
est ncessaire d'effacer le rpertoire cible lui-mme, et non seulement son contenu.
PDF brought to you by
generated on June 26, 2014
Chapter 95: Comment crer et stocker un projet Symfony2 dans git | 328
Vendors et Submodules
Au lieu d'utiliser le systme composer.json pour grer les bibliothques vendor, vous pourriez choisir
la place le systme natif git submodules
5
. Il n'y a rien d'incorrect dans cette approche, bien que le systme
composer.json soit la manire officielle de rsoudre ce problme et qu'il peut tre parfois difficile de
travailler avec les git submodules.
Stocker votre projet sur un serveur distant
Vous disposez maintenant d'un projet totalement fonctionnel stock dans git. Cependant, dans la plupart
des cas, vous voudrez aussi stocker votre projet sur un serveur distant que ce soit pour des raisons de
sauvegardes mais aussi afin que d'autres dveloppeurs puissent collaborer au projet.
La faon la plus facile de stocker votre projet sur un serveur distant est via GitHub
6
. Les dpts publics
sont gratuits, cependant vous devrez payer un abonnement mensuel pour hberger des dpts privs.
Une autre alternative est de stocker votre dpt git sur n'importe quel serveur en crant un dpt
barebones
7
et en pushant ce dernier. Une bibliothque qui aide grer ceci est Gitolite
8
.
5. http://git-scm.com/book/en/Git-Tools-Submodules
6. https://github.com/
7. http://git-scm.com/book/en/Git-Basics-Getting-a-Git-Repository
8. https://github.com/sitaramc/gitolite
PDF brought to you by
generated on June 26, 2014
Chapter 95: Comment crer et stocker un projet Symfony2 dans git | 329
Listing 96-1
Chapter 96
Comment crer et stocker un projet Symfony2
dans Subversion
Cette article est spcifique Subversion et bas sur des principes que vous trouverez dans l'article
Comment crer et stocker un projet Symfony2 dans git.
Une fois que vous avez lu La cration de pages avec Symfony2 et que vous tes devenu familier avec l'usage
de Symfony, vous serez sans aucun doute prt dmarrer votre propre projet. La mthode prfre pour
grer des projets Symfony2 est d'utiliser git
1
mais certains prfrent utiliser Subversion
2
, ce qui ne pose
aucun problme! Dans cet article du Cookbook, vous allez apprendre comment grer votre projet en
utilisant svn
3
de la mme manire que si vous l'aviez fait avec git
4
.
Ceci est une mthode parmi tant d'autres de stocker votre projet Symfony2 dans un dpt
Subversion. Il y a plusieurs manires de faire cela et celle-ci en est simplement une qui fonctionne.
Le Dpt Subversion
Pour cet article, nous supposerons que votre schma de dpt suit la structure standard et rpandue :
1
2
3
4
myproject/
branches/
tags/
trunk/
1. http://git-scm.com/
2. http://subversion.apache.org/
3. http://subversion.apache.org/
4. http://git-scm.com/
PDF brought to you by
generated on June 26, 2014
Chapter 96: Comment crer et stocker un projet Symfony2 dans Subversion | 330
Listing 96-2
Listing 96-3
Listing 96-4
Listing 96-5
La plupart des hbergements subversion devraient suivre cette pratique standard. C'est le schma
recommand par Contrle de version avec Subversion
5
et utilis par la plupart des hbergements
gratuits (voir Solutions d'hbergement Subversion).
Configuration Initiale du Projet
Pour dmarrer, vous aurez besoin de tlcharger Symfony2 et d'effectuer la configuration basique de
Subversion :
1. Tlchargez Symfony2 Standard Edition
6
avec ou sans les vendors .
2. Dzippez/dtarez la distribution. Cela va crer un dossier nomm Symfony avec votre nouvelle
structure de projet, les fichiers de configuration, etc. Renommez-le en ce que vous voulez.
3. Effectuez un checkout du dpt Subversion qui va hberger ce projet. Disons qu'il est
hberg sur Google code
7
et nomm myproject :
1 $ svn checkout http://myproject.googlecode.com/svn/trunk myproject
4. Copiez les fichiers du projet Symfony2 dans le dossier subversion :
1 $ mv Symfony/* myproject/
5. Ecrivons maintenant les patterns pour les fichiers ignorer. Tout ne doit pas tre stock dans
votre dpt subversion. Quelques fichiers (comme le cache) sont gnrs et d'autres (comme la
configuration de la base de donnes) sont destins tre adapts sur chaque machine. Ainsi,
nous utilisons la proprit svn:ignore afin de pouvoir ignorer ces fichiers spcifiques.
1
2
3
4
5
6
7
8
9
10
11
12
$ cd myproject/
$ svn add --depth=empty app app/cache app/logs app/config web
$ svn propset svn:ignore "vendor" .
$ svn propset svn:ignore "bootstrap*" app/
$ svn propset svn:ignore "parameters.yml" app/config/
$ svn propset svn:ignore "*" app/cache/
$ svn propset svn:ignore "*" app/logs/
$ svn propset svn:ignore "bundles" web
$ svn ci -m "commit basic symfony ignore list (vendor, app/bootstrap*, app/
config/parameters.yml, app/cache/*, app/logs/*, web/bundles)"
6. Le reste des fichiers peut maintenant tre ajout et committ dans le projet :
1
2
$ svn add --force .
$ svn ci -m "add basic Symfony Standard 2.X.Y"
5. http://svnbook.red-bean.com/
6. http://symfony.com/download
7. http://code.google.com/hosting/
PDF brought to you by
generated on June 26, 2014
Chapter 96: Comment crer et stocker un projet Symfony2 dans Subversion | 331
Listing 96-6
7. Copiez app/config/parameters.yml vers app/config/parameters.yml.dist. Le fichier
parameters.yml est ignor par svn (voir ci-dessus) afin que les paramtres spcifiques la
machine comme les mots de passe de base de donnes ne soient pas committs. En crant le
fichier parameters.yml.dist, les nouveaux dveloppeurs peuvent rapidement cloner le projet,
copier ce fichier vers parameters.yml, l'adapter, et commencer dvelopper.
8. Finalement, tlchargez toutes les bibliothques tierces :
1 $ php bin/vendors install
git
8
doit tre install pour pouvoir excuter la commande bin/vendors ; c'est le protocole utilis
pour aller rcuprer les bibliothques vendor. Cela signifie seulement que git est utilis comme
outil pour aider au tlchargement des bibliothques dans le rpertoire vendor/.
A ce point, vous avez un projet Symfony2 entirement fonctionnel stock dans votre dpt Subversion.
Le dveloppement peut dmarrer avec des commits dans ce dernier.
Vous pouvez continuer en lisant le chapitre La cration de pages avec Symfony2 pour en apprendre plus
sur comment configurer et dvelopper votre application en interne.
L'Edition Standard Symfony2 vient avec des exemples de fonctionnalits. Pour supprimer le code
de dmonstration, suivez les instructions du fichier Readme de la Standard Edition
9
.
Grer les bibliothques vendors avec bin/vendors et deps
Comment a marche ?
Chaque projet Symfony utilise des bibliothques tierces regroupes sous l'appellation vendors . D'une
faon ou d'une autre, le but est de vous permettre de tlcharger ces bibliothques dans le rpertoire
vendor/ et de pouvoir obtenir pour chacune la version dsire.
Par dfaut, ces bibliothques sont tlcharges en excutant le script de tlchargement php bin/
vendors install. Ce script utilise le fichier deps` situ la racine de votre projet. Ce fichier au format
INI contient la liste des bibliothques ncessaires votre projet, ainsi que le rpertoire cible o chacune
doit tre tlcharge, et (de manire optionnelle) la version tlcharger. Le script bin/vendors` utilise git
pour ce tlchargement, car la plupart des bibliothques sont gres et disponibles via git. Le script bin/
vendors se base galement sur le fichier deps.lock, lequel vous permet d'arrter la version une rvision
prcise.
Nota bene : ces bibliothques vendor ne sont pas gres dans votre dpt; ce sont de simples fichiers
installs (et ignors du dpt git) dans le rpertoire vendor/ par le script bin/vendors. Or, puisque toute
l'information ncessaire pour tlcharger ces fichiers est disponible dans deps et deps.lock (qui, eux,
sont grs par votre dpt), tout autre dveloppeur peut utiliser votre projet, lancer php bin/vendors
install, et obtenir les mmes bibliothques (et version). Vous contrlez donc exactement le contenu de
chaque bibliothque, sans avoir besoin de le versionner dans votre dpt.
Ainsi, lorsqu'un dveloppeur veut travailler sur votre projet, il lui suffit de lancer le script php bin/
vendors install pour s'assurer que toutes les bibliothques ncessaires soient tlcharges.
8. http://git-scm.com/
9. https://github.com/symfony/symfony-standard/blob/master/README.md
PDF brought to you by
generated on June 26, 2014
Chapter 96: Comment crer et stocker un projet Symfony2 dans Subversion | 332
Listing 96-7
Mettre jour Symfony
Puisque Symfony est une bibliothque tierce de votre projet et qu'elle est donc gre entirement
via deps et``deps.lock``, mettre jour Symfony signifie simplement mettre jour ces deux fichiers
afin qu'ils correspondent la dernire version de l'dition standard de Symfony.
Bien sr, si vous avez ajout des entres aux fichiers deps ou deps.lock, veillez ne remplacer que
les parties originales afin de ne pas supprimer les entres supplmentaires.
Hacker les bibliothques vendor
Parfois, il est ncessaire de tlcharger une version prcise (branche, tag) d'une bibliothque. Vous
pouvez le faire directement dans le fichier deps :
1
2
3
4
[AcmeAwesomeBundle]
git=http://github.com/johndoe/Acme/AwesomeBundle.git
target=/bundles/Acme/AwesomeBundle
version=ma-version-trop-bien
L'option git dfinit l'URL de la bibliothque. Elle peut utiliser divers protocoles, comme
http:// ou git://.
L'option target dfinit le rpertoire cible, le rpertoire dans lequel la bibliothque va tre
tlcharge. Par convention, les bundles devraient aller dans le rpertoire vendor/bundles/
Acme, les autres bibliothques iront dans vendor/ma-biblio-trop-bien, le rpertoire vendor
est galement le rpertoire cible par dfaut.
L'option version vous permet de dfinir une rvision spcifique. Vous pouvez lui attribuer
un nom de tag (version=origin/0.42) ou de branche (refs/remotes/origin/ma-branche-
trop-bien). Version par dfaut: origin/HEAD.
Flux de mise jour
lors de l'xcution de php bin/vendors install, le script vrifie d'abord si le rpertoire cible existe pour
chaque bibliothque. Si ce n'est pas le cas, il effectue un git clone.
Puis, il effectue un git fetch origin, puis git reset --hard ma-version-trop-bien.
Le rpertoire sera donc clon une seule et unique fois. Pour faire des changements lis au dpt distant il
est ncessaire d'effacer le rpertoire cible lui-mme, et non seulement son contenu.
Solutions d'hbergement Subversion
La plus grosse diffrence entre git
10
et svn
11
est que Subversion a besoin d'un dpt central pour
fonctionner. Vous avez donc plusieurs solutions :
Hbergement par vos soins : crez votre propre dpt et accdez-y soit grce au systme de
fichiers, soit via le rseau. Pour vous aider dans cette tche, vous pouvez lire Contrle de version
avec Subversion
12
.
10. http://git-scm.com/
11. http://subversion.apache.org/
12. http://svnbook.red-bean.com/
PDF brought to you by
generated on June 26, 2014
Chapter 96: Comment crer et stocker un projet Symfony2 dans Subversion | 333
Hbergement via une entit tierce : il y a beaucoup de solutions d'hbergement gratuites
et srieuses disponibles comme GitHub
13
, Google code
14
, SourceForge
15
ou Gna
16
. Certaines
d'entre elles offrent aussi des possibilits d'hbergement git.
13. https://github.com/
14. http://code.google.com/hosting/
15. http://sourceforge.net/
16. http://gna.org/
PDF brought to you by
generated on June 26, 2014
Chapter 96: Comment crer et stocker un projet Symfony2 dans Subversion | 334

You might also like