The Cookbook for Symfony 2.4

Dimension: px
Commencer à balayer dès la page:

Download "The Cookbook for Symfony 2.4"

Transcription

1 The Cookbook for Symfony. generated on November, 0

2 The Cookbook (.) This work is licensed under the Attribution-Share Alike.0 Unported license ( licenses/by-sa/.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 ( Based on tickets and users feedback, this book is continuously updated.

3 Contents at a Glance Comment utiliser Assetic pour gérer vos ressources... Comment minifier les JavaScripts et les feuilles de style avec YUI Compressor... Comment utiliser Assetic et les fonctions Twig pour optimiser les images... Comment appliquer un filtre Assetic à une extension de fichier spécifique... Comment utiliser les bonnes pratiques pour structurer vos Bundles... Comment utiliser l'héritage de bundle pour surcharger certaines parties d'un bundle... Comment surcharger n'importe quelle partie d'un bundle... Comment supprimer le AcmeDemoBundle... Comment exposer une configuration sémantique pour un Bundle... Comment utiliser Varnish pour accélérer mon site Web... Comment Maîtriser et Créer de nouveaux Environnements... Comment surcharger la structure de répertoires par défaut de Symfony... Comment configurer les paramètres externes dans le conteneur de services... Comment stocker les sessions dans la base de données grâce à PdoSessionStorage... Comment utiliser le routeur Apache... Comment créer une commande pour la Console...0 Comment utiliser la Console... Comment générer des URLs et envoyer des s depuis la Console... Comment activer les logs dans la commande console... Comment personnaliser les pages d'erreur... Comment définir des contrôleurs en tant que Services... Comment optimiser votre environnement pour le debuggage... Déployer une application Symfony... Comment gérer les uploads de fichier avec Doctrine... Comment utiliser les extensions Doctrine: Timestampable, Sluggable, Translatable, etc... Comment enregistrer des listeners («écouteurs» en français) et des souscripteurs d'évènement...0 Comment utiliser la couche DBAL de Doctrine... Comment générer des Entités à partir d'une base de données existante... Comment travailler avec plusieurs gestionnaires d'entités et connexions... Comment définir des fonctions DQL personnalisées... 0 Comment définir des Relations avec des Classes Abstraites et des Interfaces... 0 Comment implémenter un simple formulaire de création de compte... 0 Comment envoyer un ... Comment utiliser Gmail pour envoyer des s... Comment travailler avec les s pendant le Développement... Comment utiliser le «Spool» d' ... generated on November, 0 Contents at a Glance iii

4 Comment mettre en place des filtres avant et après un processus donné... Comment étendre une Classe sans utiliser l'héritage... Comment personnaliser le Comportement d'une Méthode sans utiliser l'héritage... Comment personnaliser le rendu de formulaire... Comment utiliser les Convertisseurs de Données... 0 Comment modifier dynamiquement les formulaires en utilisant les évènements... Comment imbriquer une Collection de Formulaires... Comment Créer un Type de Champ de Formulaire Personnalisé... Comment créer une extension de type de formulaire... Comment réduire la duplication de code avec "inherit_data"... 0 Comment tester unitairement vos formulaires... Comment configurer des données vierges pour une classe de Formulaire... Comment utiliser la fonction submit() pour gérer les soumissions de formulaires... 0 Comment utiliser l'option de Champ de Formulaire Virtual... Comment utiliser Monolog pour écrire des Logs... Comment configurer Monolog pour envoyer les erreurs par Comment loguer des messages dans différents fichiers... 0 Comment créer un Collecteur de Données personnalisé... 0 Comment déclarer un nouveau Format de Requête et un Type Mime... 0 Comment forcer les routes à toujours utiliser HTTPS ou HTTP... 0 Comment autoriser un caractère «/» dans un paramètre de route... Comment configurer une redirection vers une autre route sans contrôleur personnalisé... Comment utiliser des méthodes HTTP autres que GET et POST dans les routes... Comment utiliser des paramètres du conteneur de services dans vos routes... Comment charger les utilisateurs depuis la base de données (le fournisseur d'entité)... Comment ajouter la fonctionnalité de login «Se souvenir de moi»... Comment implémenter votre propre Voteur pour ajouter des adresses IP sur une liste noire... 0 Comment utiliser les Access Control Lists (ACLs) («liste de contrôle d'accès» en français)... Comment utiliser les concepts d'acl avancés... Comment forcer HTTPS ou HTTP pour des URLs Différentes... Comment personnaliser votre formulaire de login... Comment sécuriser n'importe quel service ou méthode de votre application... Comment créer un Fournisseur d'utilisateur personnalisé... 0 Comment créer un Fournisseur d'authentification Personnalisé... Comment changer le comportement par défaut du chemin cible... Comment utiliser le Serializer... Comment créer un «listener» («écouteur» en français) d'évènement... Comment travailler avec les champs d'applications («scopes» en anglais)... Comment travailler avec les Passes de Compilation dans les Bundles... Exemple de Session Proxy... Faire que la Locale soit "persistente" durant la session de l'utilisateur... Configurer le Dossier où les Fichiers pour les Sessions sont Enregistrés... 0 Combler une application legacy avec les sessions de Symfony... Limiter les Écritures de Metadonnées en Session... En quoi Symfony diffère de Symfony... Comment injecter des variables dans tous les modèles (i.e. Variables Globales)... Comment utiliser et enregistrer des chemins Twig namespacés... iv Contents at a Glance Contents at a Glance

5 Comment utiliser PHP plutôt que Twig dans les templates... Comment écrire une Extension Twig personnalisée... 0 Comment rendre un template sans passer par un contrôleur... 0 Comment simuler une authentification HTTP dans un Test Fonctionnel... 0 Comment simuler une authentification avec un token dans un test fonctionnel... 0 Comment tester les interactions de multiples clients... 0 Comment utiliser le Profiler dans un test fonctionnel... Comment tester du code interagissant avec une base de données... Comment tester les dépôts Doctrine... Comment personnaliser le processus de bootstrap avant les tests... Comment créer une Contrainte de Validation Personnalisée... 0 Comment créer des web services SOAP à l'intérieur d'un contrôleur Symfony... Comment créer et stocker un projet Symfony dans git... Comment créer et stocker un projet Symfony dans Subversion... generated on November, 0 Contents at a Glance v

6 Chapter Comment utiliser Assetic pour gérer 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 appliqués à ces fichiers avant qu'ils ne soient servis au navigateur. Cela permet de gérer séparément les fichiers ressources qui sont stockés par l'application des fichiers qui sont réellement présentés à l'utilisateur. Sans Assetic, vous servez directement les fichiers qui sont stockés dans votre application : Listing - <script src=" asset('js/script.js') " type="text/javascript" /> Mais avec Assetic, vous pouvez manipuler ces ressources de la manière dont vous le désirez (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 Exécuter 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 plutôt que servir les fichiers directement offre de nombreux avantages. Les fichiers n'ont pas besoin d'être stockés 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 différe légèrement. Inclure des fichiers Javascript Pour inclure des fichiers Javascript, utilisez le tag javascripts dans n'importe quel template. On va en général s'en servir dans le block javascripts, si vous utilisez les noms de block par défaut de la Distribution Standard de Symfony : generated on November, 0 Chapter : Comment utiliser Assetic pour gérer vos ressources

7 Listing - % 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 chargés et servis depuis un autre endroit. Le tag réellement affiché pourrait ressembler à: Listing - <script src="/app_dev.php/js/abcd.js"></script> C'est un point clé: Une fois que vous avez laissé Assetic gérer vos ressources, les fichiers sont servis depuis un autre endroit. Ceci pourra provoquer des problèmes 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 même méthodologie mais avec le tag stylesheets. Si vous utilisez les block par défaut de la Distribution Standard, ce tag prendra place dans un block stylesheets : Listing - % 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 cassés, sauf si vous utilisez le filtre cssrewrite. Remarquez que dans le premier exemple qui inclut les fichiers javascripts, vous faites référence aux fichiers avec un chemin comme suit mais dans celui-ci, vous faites référence 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 problème connu qui peut faire échouer le filtre cssrewrite avec la Corriger les chemins CSS avec le filtre cssrewrite Vu que Assetic génère de nouvelles URLs pour vos ressource, tous les chemins relatifs dans vos fichiers CSS vont être cassés. 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 précédente : Quand vous utilisez le filtre cssrewrite, ne faites pas à appel à vos CSS avec la Pour plus de détails, voir la note dans la section du dessus. generated on November, 0 Chapter : Comment utiliser Assetic pour gérer vos ressources

8 Combiner des ressources Vous pouvez aussi combiner plusieurs fichiers en un seul. Cela aide à réduire le nombre de requêtes HTTP, ce qui est très important pour les performances. Cela vous permet aussi de maintenir les fichiers plus facilement en les découpants en petites parties plus faciles à gérer. Cela peut être un plus pour la réusabilité de votre projet puisque vous pouvez facilement séparer les fichiers spécifiques au projet des fichiers qui peuvent être réutilisés dans d'autres applications, mais toujours les servir comme un fichier unique : Listing - % 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 débugguer plus facilement. Cependant, en environnement de prod (ou plus précisément, quand l'option debug est à false), ils seront affichés dans une unique balise script qui contiendra le contenu de tous vos fichiers JavaScript. Si vous découvrez Assetic et essayez d'utiliser votre application en environnement de prod (en utilisant le contrôleur app.php), vous verrez probablement que vos CSS et JS plantent. Pas de panique! C'est fait exprès. Pour plus de détails 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 : Listing - % javascripts '@AcmeFooBundle/Resources/public/js/thirdparty/jquery.js' '@AcmeFooBundle/Resources/public/js/*' % <script src=" asset_url "></script> % endjavascripts % Filtres Une fois qu'elles sont gérées par Assetic, vous pouvez appliquer des filtres à vos ressources avant qu'elles ne soient servies. Cela inclut les filtres qui compressent vos ressources pour réduire 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 possède une longue liste de filtres disponibles. Plusieurs de ces filtres ne font pas le travail directement, mais utilisent des bibliothèques tierces pour faire le gros du travail. Cela signifie que vous devrez souvent installer une bibliothèque tierce pour utiliser un filtre. Le grand avantage d'utiliser Assetic pour faire appel à ces bibliothèques (plutôt que de les utiliser directement) est qu'au lieu de les exécuter à la main après avoir modifié les fichiers, Assetic prendra tout en charge pour vous, et supprimera définitivement cette étape du processus de développement et de déploiement. Pour utiliser un filtre, vous aurez d'abord besoin de le spécifier dans la configuration d'assetic. Ajouter un filtre dans la configuration ne signifie pas qu'il est utilisé, mais juste qu'il est prêt à l'être (vous allez l'utiliser ci-dessous). Par exemple, pour utiliser le JavaScript YUI Compressor, la configuration suivante doit être ajoutée : generated on November, 0 Chapter : Comment utiliser Assetic pour gérer vos ressources

9 Listing - # 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 : Listing - % javascripts '@AcmeFooBundle/Resources/public/js/*' filter='yui_js' % <script src=" asset_url "></script> % endjavascripts % Vous pouvez trouver un guide plus détaillé 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. Contrôler l'url utilisée Si vous le souhaitez, vous pouvez contrôler les URLs générées par Assetic. Cela se fait dans le template, et le chemin est relatif par rapport à la racine publique : Listing - % javascripts '@AcmeFooBundle/Resources/public/js/*' output='js/compiled/main.js' % <script src=" asset_url "></script> % endjavascripts % Symfony contient également une méthode pour le cache busting (technique empêchant la mise en cache), où l'url générée par Assetic contient un paramètre qui peut être incrémenté, via la configuration, à chaque déploiement. Pour plus d'informations, lisez l'option de configuration assets_version. Exporter les ressources En environnement de dev, Assetic génère des chemins vers des fichiers CSS et JavaScript qui n'existent pas physiquement sur votre ordinateur. Mais ils sont néanmoins affichés car un contrôleur interne de Symfony ouvre les fichiers et sert leur contenu (après avoir exécuté tous les filtres). Cette manière dynamique de servir des ressources traitées est géniale car cela signifie que vous pouvez immédiatement voir les modifications que vous apportez à vos fichiers. Mais l'inconvénient est que cela peut parfois être un peu lent. Si vous utilisez beaucoup de filtres, cela peut être carrément frustrant. Heureusement, Assetic fournit un moyen pour exporter vos ressources vers des fichiers réels au lieu de les générer dynamiquement. Exporter les ressources en environnement de prod En environnement de prod, vos fichiers JS et CSS sont représentés chacun par une balise unique. En d'autres termes, plutôt que de voir chacun des fichiers JavaScript que vous incluez dans votre code source, vous verrez quelque chose comme ceci : Listing -0 generated on November, 0 Chapter : Comment utiliser Assetic pour gérer vos ressources

10 <script src="/app_dev.php/js/abcd.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 exprès : laisser Symfony générer ces fichiers dynamiquement en production serait tout simplement trop lent. Au lieu de cela, chaque fois que vous exécutez votre application dans l'environnement de prod (et par conséquent, chaque fois que vous déployez), vous devriez exécuter la commande suivante : Listing - $ php app/console assetic:dump --env=prod --no-debug Cela génèrera et écrira physiquement chaque fichier dont vous avez besoin (ex /js/abcd.js). Si vous mettez à jour vos ressources, vous aurez besoin de relancer cette commande pour regénérer vos fichiers. Exporter les ressources en environnement de dev Par défaut, chaque chemin de ressource généré en environnement de dev est pris en charge dynamiquement par Symfony. Cela n'a pas d'inconvénient (vous pouvez voir vos changements immédiatement), sauf que les ressources peuvent être lentes à charger. Si vous trouvez que vos ressources sont chargés trop lentement, suivez ce guide. Premièrement, dites à Symfony de ne plus essayer de traiter ces fichiers dynamiquement. Apportez les modifications suivantes dans le fichier config_dev.yml : Listing - # app/config/config_dev.yml assetic: use_controller: false Ensuite, puisque Symfony ne génère plus ces fichiers pour vous, vous aurez besoin de les exporter manuellement. Pour ce faire, lancez la commande suivante : Listing - $ php app/console assetic:dump Elle écrit physiquement tous les fichiers de ressource dont vous avez besoin pour l'environnement de dev. Le gros inconvénient est que vous devrez faire cela chaque fois que vous modifiez une ressource. Heureusement, en passant l'option --watch, la commande regénèrera automatiquement les ressources modifiées : Listing - $ php app/console assetic:dump --watch Lancer cette commande en environnement de dev peut générer un florilège de fichiers. Pour conserver votre projet bien organisé, il peut être intéressant de mettre les fichiers générés dans un répertoire séparé (ex /js/compiled) : Listing - % javascripts '@AcmeFooBundle/Resources/public/js/*' output='js/compiled/main.js' % <script src=" asset_url "></script> % endjavascripts % generated on November, 0 Chapter : Comment utiliser Assetic pour gérer vos ressources 0

11 Chapter 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. Grâce à Assetic, vous pourrez tirer profit de cet outil très facilement. Téléchargez le JAR YUI Compressor YUI Compressor est écrit en Java est distribué sous forme de JAR. Téléchargez le JAR 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 : Listing - # 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". generated on November, 0 Chapter : Comment minifier les JavaScripts et les feuilles de style avec YUI Compressor

12 Les utilisateurs de Windows ne doivent pas oublier de mettre à jour l'emplacement de Java. Dans Windows x bit, il s'agit de C:\Program Files (x)\java\jre\bin\java.exe par défaut Vous avez maintenant accès 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 : Listing - % 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 répertoire Resources/public/js dans votre bundle. Ce n'est, en fait, pas très 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 minifiés sont chargés beaucoup plus rapidement. Le même procédé peut être utilisé pour minifier vos feuilles de style. Listing - % stylesheets '@AcmeFooBundle/Resources/public/css/*' filter='yui_css' % <link rel="stylesheet" type="text/css" media="screen" href=" asset_url " /> % endstylesheets % Désactiver la minification en Mode Debug Les JavaScripts et feuilles de styles minifiés sont très difficiles à lire; et encore moins à débugguer. Pour palier cela, Assetic vous permet de désactiver un filtre lorsque votre application est en mode debug. Vous pouvez faire cela en préfixant 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. Listing - % javascripts '@AcmeFooBundle/Resources/public/js/*' filter='?yui_js' % <script src=" asset_url "></script> % endjavascripts % Plutôt que d'ajouter le filtre à vos balises assets, vous pouvez aussi l'activer de façon 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 détails sur comment appliquer des filtres en fonction des extensions de fichiers, lisez Filtrer en se basant sur les extensions. generated on November, 0 Chapter : Comment minifier les JavaScripts et les feuilles de style avec YUI Compressor

13 Chapter Comment utiliser Assetic et les fonctions Twig pour optimiser les images Parmi ses nombreux filtres, Assetic possède quatre filtres qui peuvent être utilisés pour optimiser les images à la volée. Cela vous permet de tirer profit de tailles de fichiers réduites sans utiliser d'éditeur d'image pour réduire chaque image. Les résultats sont mis en cache et peuvent être réutilisés en production pour qu'il n'y ait pas d'impact sur les performances pour vos utilisateurs finaux. Utiliser Jpegoptim Jpegoptim est un utilitaire pour optimiser les fichiers JPEG. Pour l'utiliser avec Assetic, ajoutez le bout de code suivant à votre configuration Assetic : Listing - # app/config/config.yml assetic: filters: jpegoptim: bin: path/to/jpegoptim Notez que pour utiliser jpegoptim, il faut qu'il soit déjà installé sur votre système. L'option bin pointe vers le fichier binaire compilé. Il peut maintenant être utilisé dans un template : Listing - % image '@AcmeFooBundle/Resources/public/images/example.jpg' filter='jpegoptim' output='/images/example.jpg' % <img src=" asset_url " alt="example"/> % endimage %. generated on November, 0 Chapter : Comment utiliser Assetic et les fonctions Twig pour optimiser les images

14 Supprimer toutes les données EXIF Par défaut, appliquer ce filtre ne supprime que certaines meta-informations du fichier. Les données EXIF et les commentaires ne sont pas supprimés, mais vous pouvez les supprimer en utilisant l'option strip_all : Listing - # app/config/config.yml assetic: filters: jpegoptim: bin: path/to/jpegoptim strip_all: true Réduire la qualité maximum Le niveau de qualité du JPEG n'est pas modifié par défaut. Vous pouvez réduire un peu la taille des images en définissant un niveau de qualité maximum plus bas que le niveau actuel. Cela se fera évidemment au détriment de la qualité de l'image : Listing - # app/config/config.yml assetic: filters: jpegoptim: bin: path/to/jpegoptim max: 0 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 spéciale Twig. Commencez par ajouter la configuration suivante : Listing - # app/config/config.yml assetic: filters: jpegoptim: bin: path/to/jpegoptim twig: functions: jpegoptim: ~ Le template Twig peut maintenant être modifié comme suit : Listing - <img src=" jpegoptim('@acmefoobundle/resources/public/images/example.jpg') " alt="example"/> Vous pouvez spécifier le répertoire cible dans la configuration de la manière suivante : Listing - # app/config/config.yml assetic: filters: jpegoptim: bin: path/to/jpegoptim twig: generated on November, 0 Chapter : Comment utiliser Assetic et les fonctions Twig pour optimiser les images

15 functions: jpegoptim: output: images/*.jpg generated on November, 0 Chapter : Comment utiliser Assetic et les fonctions Twig pour optimiser les images

16 Chapter Comment appliquer un filtre Assetic à une extension de fichier spécifique Les filtres Assetic peuvent être appliqués à des fichiers individuels, à des groupes de fichiers ou même, comme vous allez le voir ici, à des fichiers qui ont une extension spécifique. Pour vous montrer comment gérer 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 défaut respectives sont /usr/bin/coffee et /usr/bin/node: Listing - # 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 : Listing - % 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é. generated on November, 0 Chapter : Comment appliquer un filtre Assetic à une extension de fichier spécifique

17 Filtrer des fichiers multiples Vous pouvez aussi combiner plusieurs fichiers CoffeeScript en un unique fichier en sortie : Listing - % 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 réduire le nombre de ressources pour réduire le nombre de requêtes HTTP. Dans le but d'en tirer le plus grand avantage possible, il pourrait être intéressant de combiner tous vos fichiers CoffeeScript et JavaScript ensembles puisqu'ils seront finalement délivrés 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 problème peut être évité en ajoutant l'option apply_to à la configuration, ce qui vous permettra de spécifier qu'un filtre devra toujours être appliqué à une extension de fichier particulière. Dans ce cas, vous pouvez spécifier que le filtre Coffee s'applique à tous les fichiers.coffee : Listing - # 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 spécifier le filtre coffee dans le template. Vous pouvez aussi lister les fichiers JavaScript classique, chacun d'eux sera combiné et délivré en un unique fichier JavaScript (seuls les fichiers.coffee passeront à travers le filtre CoffeeScript) : Listing - % 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 % generated on November, 0 Chapter : Comment appliquer un filtre Assetic à une extension de fichier spécifique

18 Chapter Comment utiliser les bonnes pratiques pour structurer vos Bundles Un bundle est un répertoire qui a une structure bien définie et qui peut héberger à peu près tout : des classes aux contrôleurs en passant par les ressources web. Même si les bundles sont très 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 d'intéropérabilité technique pour les espaces de noms PHP. et les noms de classes : il commence par un segment «vendor», suivi par zéro ou plusieurs segments catégories, 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 aussitôt que vous lui ajoutez une classe bundle. Le nom de la classe bundle doit suivre ces règles simples : Utiliser uniquement des caractères alphanumériques et des tirets bas («underscore» en anglais) ; Utiliser un nom en notation dite «CamelCase» ; Utiliser un nom court et descriptif (pas plus de mots) ; Préfixer le nom avec la concaténation du «vendor» (et optionnellement l'espace de noms de la catégorie) ; Suffixer le nom avec Bundle. Vous trouverez ci-dessous des espaces de noms de bundle et des noms de classes valides : Espace de noms Acme\Bundle\BlogBundle Acme\Bundle\Social\BlogBundle Nom de la Classe Bundle AcmeBlogBundle AcmeSocialBlogBundle. generated on November, 0 Chapter : Comment utiliser les bonnes pratiques pour structurer vos Bundles

19 Espace de noms Acme\BlogBundle Nom de la Classe Bundle AcmeBlogBundle Par convention, la méthode 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 dépôt (AcmeBlogBundle et non pas BlogBundle par exemple). Les Bundles du coeur de Symfony ne préfixent pas la classe Bundle avec Symfony et ajoutent toujours un sous-espace de noms Bundle ; par exemple : FrameworkBundle. Chaque bundle possède 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'intérieur d'un bundle (voir ci-dessous pour des exemples d'utilisation). Structure de Répertoires La structure basique du répertoire d'un bundle HelloBundle doit être comme suit : Listing - 0 XXX/... HelloBundle/ HelloBundle.php Controller/ Resources/ meta/ LICENSE config/ doc/ index.rst translations/ views/ public/ Tests/ Le(s) répertoire(s) XXX reflète(nt) la structure de l'espace de noms du bundle. Les fichiers suivants sont obligatoires : HelloBundle.php ; Resources/meta/LICENSE: La licence complète pour le code ; Resources/doc/index.rst: Le fichier racine pour la documentation du bundle. Ces conventions assurent que les outils automatisés puissent compter sur cette structure par défaut pour travailler.. generated on November, 0 Chapter : Comment utiliser les bonnes pratiques pour structurer vos Bundles

20 La profondeur des sous-répertoires devrait être réduite au minimum pour les classes et fichiers les plus utilisés ( niveaux au maximum). Plus de niveaux peuvent être définis pour les fichiers non-stratégiques et moins utilisés. Le répertoire du bundle est en lecture seule. Si vous avez besoin d'écrire des fichiers temporaires, stockezles dans le dossier cache/ ou log/ de l'application hébergeant votre bundle. Des outils peuvent générer des fichiers dans la structure du répertoire du bundle, mais uniquement si les fichiers générés vont faire partie du dépôt. Les classes et fichiers suivants ont des emplacements spécifiques : Type Commandes Contrôleurs Extensions du Conteneur de Services Listeners d'évènements Configuration Ressources Web Fichiers de traduction Templates Tests Unitaires et Fonctionnels Répertoire Command/ Controller/ DependencyInjection/ EventListener/ Resources/config/ Resources/public/ Resources/translations/ Resources/views/ Tests/ Classes La structure du répertoire du bundle est utilisée en tant que hiérarchie d'espace de noms. Par exemple, un contrôleur 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 Symfony («coding standards» en anglais). Certaines classes devraient être vues comme des façades et donc être aussi courtes que possible, comme les «Commands», «Helpers», «Listeners» et «Controllers». Les classes se connectant au dispatcher («répartiteur» en français) d'évènements devraient être suffixées avec Listener. Les classes d'exceptions devraient être stockées dans un sous-espace de noms Exception. Vendors Un bundle ne doit pas embarquer de bibliothèques PHP tierces. Il devrait compter sur le chargement automatique («autoloading» en anglais) standard de Symfony à la place. Un bundle ne devrait pas embarquer de bibliothèques tierces écrites en JavaScript, CSS, ou quelconque autre langage. generated on November, 0 Chapter : Comment utiliser les bonnes pratiques pour structurer vos Bundles 0

21 Tests Un bundle devrait venir avec un ensemble de tests écrits avec PHPUnit et stockés dans le répertoire Tests/. Les tests devraient suivre les principes suivants : La suite de tests doit être exécutable avec une simple commande phpunit lancée depuis une application ; Les tests fonctionnels devraient être utilisés uniquement pour tester la sortie de la réponse et quelques informations de profilage si vous en avez ; Les tests devraient couvrir au moins % 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 complète. Une documentation complète devrait aussi être fournie dans le format restructuredtext, dans le répertoire Resources/doc/ ; le fichier Resources/doc/index.rst est l'unique fichier obligatoire et doit être le point d'entrée de la documentation. Contrôleurs En tant que bonne pratique, les contrôleurs dans un bundle prévu pour être distribué à d'autres ne doivent pas étendre la classe de base Controller. Ils peuvent implémenter ContainerAwareInterface ou étendre ContainerAware à la place. Si vous jetez un oeil aux méthodes de la classe Controller, 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 préfixées avec l'alias du bundle. Par exemple, pour un «AcmeBlogBundle», toutes les routes doivent être préfixées 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 entièrement fonctionnelle generated on November, 0 Chapter : Comment utiliser les bonnes pratiques pour structurer vos Bundles

22 Fichiers de Traduction Si un bundle fournit des traductions de messages, ces dernières doivent être définies au format XLIFF ; le domaine devrait être nommé après 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 paramètres configurables en utilisant les mécanismes intégrés de Symfony. Pour des paramètres de configuration simples, comptez sur les entrées par défaut de parameters de la configuration de Symfony. Les paramètres Symfony sont de simples paires clé/valeur ; une valeur étant n'importe quelle valeur PHP valide. Chaque nom de paramètre devrait commencer avec l'alias du bundle, bien que ceci ne soit qu'une suggestion de bonne pratique. Le reste du nom du paramètre va utiliser un point (.) pour séparer les différentes parties (par exemple : acme_hello. .from). L'utilisateur final peut fournir des valeurs dans différents types de fichier de configuration : Listing - # app/config/config.yml parameters: acme_hello. .from: [email protected] Récupérez les paramètres de configuration dans votre code depuis le conteneur: Listing - $container->getparameter('acme_hello. .from'); Même si ce mécanisme est assez simple, vous êtes grandement encouragé à utiliser la configuration sémantique décrite dans le cookbook. Si vous définissez des services, ils devraient aussi être préfixés avec l'alias du bundle. En savoir plus grâce au Cookbook Comment exposer une configuration sémantique pour un Bundle generated on November, 0 Chapter : Comment utiliser les bonnes pratiques pour structurer vos Bundles

23 Chapter Comment utiliser l'héritage 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 manière très pratique de surcharger des fichiers comme des contrôleurs, des templates et d'autres fichiers présents dans le dossier Resources/ d'un bundle. Par exemple, supposons que vous installiez le FOSUserBundle, mais que vous souhaitez surcharger son template de base layout.html.twig, ainsi que l'un de ses contrôleurs. Supposons aussi que vous ayez votre propre AcmeUserBundle où vous voulez avoir les fichiers de substitution. Commencez par déclarer le FOSUserBundle comme parent de votre bundle: Listing - 0 // 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 désormais surcharger plusieurs parties du FOSUserBundle en créant simplement un fichier ayant le même nom. Malgré le nom de la méthode, il n'y a pas de relation parent/enfant entre les bundles. Il s'agit juste d'une manière d'étendre et de surcharger un bundle existant.. generated on November, 0 Chapter : Comment utiliser l'héritage de bundle pour surcharger certaines parties d'un bundle

24 Surcharger des contrôleurs Supposons que vous vouliez ajouter de la fonctionnalité à registeraction du RegistrationController résidant dans le FOSUserBundle. Pour faire cela, créez simplement votre propre fichier RegistrationController.php, surcharger la méthode originale du bundle, et changez sa fonctionnalité: Listing - 0 // 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 complètement sa logique par la vôtre. Surcharger des contrôleurs de cette façon fonctionne uniquement si le bundle réfère au contrôleur 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 surchargées en créant simplement un fichier au même emplacement que dans votre bundle parent. Par exemple, il est très 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 réside à l'emplacement Resources/views/layout.html.twig dans le FOSUserBundle, vous pouvez créer votre propre fichier au même endroit dans le AcmeUserBundle. Symfony va complètement ignorer le fichier étant dans le FOSUserBundle, et utiliser le vôtre à la place. Il en va de même pour les fichiers de routage, de configuration de la validation et pour les autres ressources. Surcharger des ressources fonctionne uniquement lorsque vous référez à des ressources via la Si vous référez à des ressources sans utiliser le ces dernières ne peuvent alors pas être surchargées. generated on November, 0 Chapter : Comment utiliser l'héritage de bundle pour surcharger certaines parties d'un bundle

25 Les fichiers de traduction ne fonctionnent pas de la même manière que celle décrite ci-dessus. Tous les fichiers de traduction sont accumulés 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 initialisés) et ensuite ceux de votre répertoire app/resources. Si la même traduction est spécifiée dans deux ressources, c'est la traduction venant de la ressource chargée en dernier qui gagnera. generated on November, 0 Chapter : Comment utiliser l'héritage de bundle pour surcharger certaines parties d'un bundle

26 Chapter Comment surcharger n'importe quelle partie d'un bundle Ce document est une référence succincte sur comment surcharger différentes partie d'un bundle tiers. Templates Pour des informations sur la surcharge de templates, lisez * La Surcharge de templates de Bundle * Comment utiliser l'héritage de bundle pour surcharger certaines parties d'un bundle Routage Le routage n'est jamais importé automatiquement dans Symfony. Si vous voulez inclure les routes d'un bundle, alors elles doivent être importées manuellement à un endroit de votre application (ex app/ config/routing.yml). La manière la plus simple de «surcharger» les routes d'un bundle est de ne pas les importer du tout. Plutôt que d'importer les routes d'un bundle tiers, copiez simplement le fichier de routage dans votre application, modifiez le, et importez le. Contrôleurs En partant du principe que les bundles tiers n'utisent pas de contrôleurs qui ne soient pas des services (ce qui est presque toujours le cas), vous pouvez facilement surcharger les contrôleurs grâce à l'héritage de bundle. Pour plus d'informations, lisez Comment utiliser l'héritage de bundle pour surcharger certaines parties d'un bundle. generated on November, 0 Chapter : Comment surcharger n'importe quelle partie d'un bundle

27 Services & Configuration Pour surcharger/étendre un service, vous avez deux options. Premièrement, vous pouvez redéfinir la valeur du paramètre qui contient le nom de la classe du service en spécifiant votre propre classe dans app/ config/config.yml. Bien sûr, cela n'est possible que si le nom de la classe est défini comme paramètre dans la configuration du service du bundle. Par exemple, pour surcharger la classe utilisé par le service translator``de Symfony, vous pouvez surcharger le paramètre ``translator.class. Savoir quel paramètre surcharger peut nécessiter un peu de recherche. Pour le Translator, le paramètre est défini et utilisé dans le fichier Resources/config/translation.xml du FrameworkBundle : Listing - # app/config/config.yml parameters: translator.class: Acme\HelloBundle\Translation\Translator Deuxièmement, si la classe n'est pas spécifiée dans les paramètres, si vous voulez vous assurer que la classe est bien toujours surchargée lorsque votre bundle est utilisé, ou si vous avez besoin de faire un peu plus de modifications, vous devrez utiliser une passe de compilation: Listing - 0 // 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 définition 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 méthode, vous ne pouvez utiliser que la méthode de la passe de compilation. Entités 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 sûr, cela ne fonctionnera que si le type est appelé par son alias, et non pas s'il est instancié. Exemple: Listing - $builder->add('name', 'custom_type'); generated on November, 0 Chapter : Comment surcharger n'importe quelle partie d'un bundle

28 au lieu de: Listing - $builder->add('name', new CustomType()); Métadonnées de Validation En cours... Traductions En cours... generated on November, 0 Chapter : Comment surcharger n'importe quelle partie d'un bundle

29 Chapter Comment supprimer le AcmeDemoBundle La Standard Edition de Symfony est livrée avec une démo complète qui vit à l'intérieur d'un bundle appelé AcmeDemoBundle. C'est un bon repère pour tout démarrage d'un projet, mais vous aurez probablement envie de finalement le retirer. Cet article présente AcmeDemoBundle comme exemple, mais vous pouvez suivre les mêmes étapes pour supprimer n'importe quel bundle.. Désinscrire le bundle dans AppKernel Pour déconnecter le bundle du framework, vous devez le supprimer de la méthode AppKernel::registerBundles(). Le bundle se trouve normalement dans le tableau $bundles mais il n'est enregistré que pour l'environnement de développement. Vous pouvez le trouver à l'interieur de l'instruction if ci-dessous Listing - 0 // 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(); //... generated on November, 0 Chapter : Comment supprimer le AcmeDemoBundle

30 . Supprimer la configuration du bundle Maintenant que Symfony ne connaît plus le bundle, vous devez supprimer toute configuration ou configuration de routing qui se réfère au bundle dans le répertoire app/config.. 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.. 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 associée à ces fichiers. Vous pouvez rapidement repérer 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 sécurité dans le fichier app/config/security.yml. Vous pouvez l'utiliser pour votre configuration de sécurité mais vous pouvez aussi la supprimer.. Supprimer le bundle depuis le système de fichiers Maintenant que vous avez supprimé toutes les références du bundle dans votre application, vous pouvez supprimer le bundle depuis le système de fichiers. Le bundle se trouve dans le répertoire 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 méthode getpath() pour récupérer le chemin vers le bundle: Listing - echo $this->container->get('kernel')->getbundle('acmedemobundle')->getpath();. Supprimer l'integration dans d'autres bundles Ceci ne concerne pas le bundle AcmeDemoBundle - aucun autre bundle ne dépend 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 dépend de ce bundle.. generated on November, 0 Chapter : Comment supprimer le AcmeDemoBundle 0

31 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 caractère de l'alias du bundle pourrait vous aider à les repérer (i.e acme_demo pour les bundles dépendant 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. generated on November, 0 Chapter : Comment supprimer le AcmeDemoBundle

32 Chapter Comment exposer une configuration sémantique pour un Bundle Si vous ouvrez le fichier de configuration de votre application (en général app/config/config.yml), vous allez y trouver un certain nombre de différents «espaces de noms» de configuration, tel que framework, twig, et doctrine. Chacun d'entre eux configure un bundle spécifique, vous permettant de paramétrer 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'intégration de formulaire, qui implique de définir pas mal de services ainsi que l'intégration d'autres composants liés : Listing - framework: #... form: true Lorsque vous créez un bundle, vous avez deux choix concernant la gestion de la configuration :. Configuration normale des services (facile) : Vous pouvez spécifier 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 paramètres, 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 détails.. Exposer la configuration sémantique (avancé) : C'est la manière dont la configuration est faite pour les bundles coeurs (comme décrit cidessus). L'idée de base est que, à la place d'avoir l'utilisateur qui surcharge les paramètres individuels, vous laissez l'utilisateur configurer seulement certaines options generated on November, 0 Chapter : Comment exposer une configuration sémantique pour un Bundle

33 spécifiquement créées. En tant que développeur du bundle, vous analysez donc cette configuration et chargez les services dans une classe «Extension». Avec cette méthode, vous n'aurez pas besoin d'importer quelconque ressource de configuration depuis la configuration principale de votre application : la classe Extension peut gérer 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 méthode vous devriez utiliser, c'est probablement une bonne idée de commencer avec la méthode #, et de changer pour la # si besoin est. La seconde méthode possède plusieurs avantages spécifiques : Beaucoup plus puissante que de définir simplement des paramètres : une valeur d'option spécifique pourrait déclencher la création de plusieurs définitions de services ; Possibilité d'avoir une hiérarchie 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) ; Autocomplétion via l'ide lorsque vous créez un XSD et que les développeurs utilisent XML. Surcharger les paramètres d'un bundle Si un Bundle fournit une classe d'extension, alors vous ne devriez généralement pas surcharger l'un de ses paramètres de conteneur de service. L'idée est que si une classe d'extension est présente, chaque paramètre qui doit être configurable devrait être présent dans la configuration rendue accessible par cette classe. En d'autres termes, la classe d'extension définit tous les paramètres publics de configuration supportés pour lesquels une rétro-compatibilité sera maintenue. Créer une classe d'extension Si vous choisissez d'exposer une configuration sémantique pour votre bundle, vous aurez d'abord besoin de créer une nouvelle classe «Extension», qui va gérer le processus. Cette classe devrait se trouver dans le répertoire DependencyInjection de votre bundle et son nom devrait être construit en remplaçant le suffixe Bundle du nom de la classe du Bundle par Extension. Par exemple, la classe d'extension de AcmeHelloBundle serait nommée AcmeHelloExtension: Listing - 0 // 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 effectuée public function getxsdvalidationbasepath() return DIR.'/../Resources/config/'; generated on November, 0 Chapter : Comment exposer une configuration sémantique pour un Bundle

34 0 public function getnamespace() return ' Les méthodes getxsdvalidationbasepath et getnamespace sont requises uniquement si le bundle fournit un XSD optionnel pour la configuration. La présence de la classe précédente signifie que vous pouvez maintenant définir 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 immédiatement commencer à spécifier la configuration sous cet espace de noms : Listing - # app/config/config.yml acme_hello: ~ Si vous suivez les conventions de nommage décrites ci-dessus, alors la méthode load() du code de votre extension est toujours appelée tant que votre bundle est déclaré dans le Kernel. En d'autres termes, même si l'utilisateur ne fournit aucune configuration (c-a-d que l'entrée acme_hello n'apparaît même pas), la méthode load() sera appelée avec un tableau $configs vide comme argument. Vous pouvez toujours fournir des valeurs par défaut 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 ajoutée à un tableau de configurations et passée à la méthode load() de votre extension (Symfony convertit automatiquement XML et YAML en un tableau). Prenez la configuration suivante : Listing - # app/config/config.yml acme_hello: foo: foovalue bar: barvalue Le tableau passé à votre méthode load() ressemblera à cela: Listing - 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 apparaît dans un autre fichier de generated on November, 0 Chapter : Comment exposer une configuration sémantique pour un Bundle

35 configuration - disons config_dev.yml - avec des valeurs différentes, alors le tableau passé à votre méthode load() pourrait ressembler à ça: Listing - 0 array( array( 'foo' => 'foovalue', 'bar' => 'barvalue', ), array( 'foo' => 'foodevvalue', 'baz' => 'newconfigentry', ), ) L'ordre des deux tableaux dépend duquel est défini en premier. C'est alors votre travail de décider comment ces configurations devraient être fusionnées ensemble. Par exemple, vous pourriez avoir les dernières valeurs écrasant les précédentes ou en quelque sorte les fusionner ensemble. Enfin, dans la section Classe de Configuration, vous apprendrez une manière réellement robuste de gérer cela. Mais pour le moment, vous pourriez simplement les fusionner manuellement: Listing - 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 aveuglément. Utiliser la méthode load() Dans la méthode load(), la variable $container fait référence à un conteneur qui connaît uniquement cet espace de noms de configuration (c-a-d qu'il ne contient pas d'informations de service chargées depuis d'autres bundles). Le but de la méthode load() est de manipuler le conteneur, en ajoutant et en configurant n'importe quelles méthodes ou services nécessaires à 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 nécessaires à votre bundle. Par exemple, supposons que vous ayez un fichier services.xml contenant la plupart des configurations de service de votre bundle: Listing - use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\Config\FileLocator; public function load(array $configs, ContainerBuilder $container) generated on November, 0 Chapter : Comment exposer une configuration sémantique pour un Bundle

36 0 // préparer votre variable $config $loader = new XmlFileLoader($container, new FileLocator( DIR.'/../Resources/ config')); $loader->load('services.xml'); Vous pourriez même 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 passée et définie comme «true»: Listing - 0 public function load(array $configs, ContainerBuilder $container) // préparer 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 définir les paramètres Une fois que vous avez chargé des configurations de service, vous pourriez avoir besoin de modifier la configuration basée sur certaines valeurs saisies. Par exemple, supposons que vous ayez un service dont le premier argument est une chaîne de caractères «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 définissez ce service et utilisez un paramètre vide - acme_hello.my_service_type - en tant que premier argument : Listing -0 0 <!-- src/acme/hellobundle/resources/config/services.xml --> <container xmlns=" xmlns:xsi=" xsi:schemalocation=" dic/services/services-.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 définir un paramètre vide et puis le passer à votre service? La réponse est que vous allez définir ce paramètre dans votre classe d'extension, en vous basant sur les valeurs de configuration saisies. Par exemple, supposons que vous souhaitiez autoriser l'utilisateur à définir cette option type via une clé nommée my_type. Pour effectuer cela, ajoutez ce qui suit à la méthode load(): Listing - generated on November, 0 Chapter : Comment exposer une configuration sémantique pour un Bundle

37 0 public function load(array $configs, ContainerBuilder $container) // préparer 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 spécifiant la valeur de configuration my_type : Listing - # app/config/config.yml acme_hello: my_type: foo #... Paramètres globaux Lorsque vous configurez le conteneur, soyez conscient que vous avez à disposition les paramètres 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 paramètres et services commençant par un _ sont réservés pour le framework, et aucun autre ne doit être défini 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 présence de valeurs de configuration à la main en utilisant la fonction PHP isset(). Un système de Configuration optionnel est aussi disponible et peut vous aider avec la fusion, la validation, les valeurs par défaut, et la normalisation des formats. generated on November, 0 Chapter : Comment exposer une configuration sémantique pour un Bundle

38 La normalisation des formats se réfère au fait que certains formats - principalement XML - résultent en de légères différences concernant les tableaux de configuration et que ces tableaux ont besoin d'être «normalisés» afin de correspondre avec tout le reste. Pour profiter de ce système, vous allez créer une classe de Configuration et construire un arbre qui définit votre configuration dans cette classe: Listing // 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 très simple, mais vous pouvez maintenant utiliser cette classe dans votre méthode load() pour fusionner votre configuration et forcer la validation. Si n'importe quelle autre option que my_type est passée, l'utilisateur sera notifié avec une exception disant qu'une option non-supportée a été passée: Listing - public function load(array $configs, ContainerBuilder $container) $configuration = new Configuration(); $config = $this->processconfiguration($configuration, $configs); //... La méthode processconfiguration() utilise l'arbre de configuration que vous avez défini dans la classe de Configuration pour valider, normaliser et fusionner tous les tableaux de configuration ensemble. La classe de Configuration peut être bien plus compliquée que ce qui est montré ici, supportant des noeuds de tableaux, des noeuds «prototypes», une validation avancée, une normalisation spécifique à XML et une fusion avancée. 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 ou la Configuration du TwigBundle generated on November, 0 Chapter : Comment exposer une configuration sémantique pour un Bundle

39 Dump de la Configuration par Défaut New in version.: La commande config:dump-reference a été ajoutée dans Symfony. La commande config:dump-reference permet d'afficher sur la console la configuration yaml par défaut du bundle. Tant que votre configuration de bundle est située à l'emplacement standard (YourBundle\DependencyInjection\Configuration) et qu'elle n'a pas de constructor(), cela fonctionnera automatiquement. Si vous avez quelque chose de différent, votre classe Extension devra surcharger la méthode Extension::getConfiguration(). Cette dernière devant retourner une instance de votre Configuration. Des commentaires et exemples peuvent être ajoutés à vos noeuds de configuration en utilisant les méthodes ->info() et ->example(): Listing // 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 paramètre') ->end() ->end() ; return $treebuilder; Ce texte apparaît en tant que commentaires yaml sur la sortie de la commande config:dump-reference. Conventions concernant les Extensions Quand vous créez une extension, suivez ces conventions simples : L'extension doit être stockée dans le sous-espace de noms DependencyInjection ; L'extension doit être nommée d'après le nom du bundle et suffixée avec Extension (AcmeHelloExtension pour AcmeHelloBundle) ; L'extension devrait fournir un schéma XSD. Si vous suivez ces conventions simples, vos extensions seront déclarées automatiquement par Symfony. Sinon, réécrivez la méthode build() de Bundle dans votre bundle: generated on November, 0 Chapter : Comment exposer une configuration sémantique pour un Bundle

40 Listing - 0 //... use Acme\HelloBundle\DependencyInjection\UnconventionalExtensionClass; class AcmeHelloBundle extends Bundle public function build(containerbuilder $container) parent::build($container); // déclare manuellement les extensions qui ne suivent pas les conventions $container->registerextension(new UnconventionalExtensionClass()); Dans ce cas, la classe d'extension doit aussi implémenter une méthode getalias() et retourner un alias unique nommé après 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 méthode load() de votre extension sera appelée uniquement si l'utilisateur spécifie 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 définis plus haut, donc rien ne se fait automatiquement.. generated on November, 0 Chapter : Comment exposer une configuration sémantique pour un Bundle 0

41 Chapter 0 Comment utiliser Varnish pour accélérer mon site Web Du fait que le cache de Symfony utilise les en-têtes HTTP standards, le Symfony Reverse Proxy peut facilement être remplacé par n'importe quel autre «reverse proxy». Varnish est un accélérateur HTTP puissant et open-source capable de délivrer du contenu caché rapidement et incluant le support pour les Edge Side Includes. Configuration Comme vu précédemment, Symfony est suffisamment intelligent pour détecter si il parle à un «reverse proxy» qui comprend ESI ou non. Cela fonctionne automatiquement lorsque vous utilisez le «reverse proxy» de Symfony, mais vous avez besoin d'une configuration spéciale pour que cela fonctionne avec Varnish. Heureusement, Symfony repose déjà sur un autre standard écrit par Akamaï (Architecture Edge ). En conséquence, les conseils de configuration décrits dans ce chapitre peuvent être utiles même si vous n'utilisez pas Symfony. Varnish supporte seulement l'attribut src pour les balises ESI (les attributs onerror et alt sont ignorés). Tout d'abord, configurez Varnish afin qu'il annonce son support d'esi en ajoutant un en-tête Surrogate- Capability aux requêtes transférées à l'application backend : Listing 0- sub vcl_recv set req.http.surrogate-capability = "abc=esi/.0"; Puis, optimisez Varnish afin qu'il analyse uniquement le contenu de la réponse lorsqu'il y a au moins une balise ESI en vérifiant l'en-tête Surrogate-Control que Symfony ajoute automatiquement :. generated on November, 0 Chapter 0: Comment utiliser Varnish pour accélérer mon site Web

42 Listing 0-0 sub vcl_fetch if (beresp.http.surrogate-control ~ "ESI/.0") unset beresp.http.surrogate-control; // for Varnish >=.0 set beresp.do_esi = true; // for Varnish <.0 // esi; La compression avec ESI n'était pas supportée dans Varnish jusqu'à la version.0 (lire GZIP et Varnish ). Si vous n'utilisez pas Varnish.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 données cachées parce que l'invalidation est déjà prise en compte nativement dans les modèles de cache HTTP (voir Invalidation du cache). Néanmoins, Varnish peut être configuré pour accepter une méthode HTTP spécifique PURGE qui va invalider le cache pour une ressource donnée : Listing 0-0 sub vcl_hit if (req.request == "PURGE") set obj.ttl = 0s; error 00 "Purged"; sub vcl_miss if (req.request == "PURGE") error 0 "Not purged"; Vous devez protéger la méthode HTTP PURGE d'une façon ou d'une autre afin d'éviter que des personnes purgent vos données cachées.. generated on November, 0 Chapter 0: Comment utiliser Varnish pour accélérer mon site Web

43 Chapter Comment Maîtriser et Créer 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 définir la base de données étant utilisée, si oui ou non quelque chose doit être mis en cache, ou à quel point le «logging» doit être détaillé. Dans Symfony, l'idée d'«environnements» repose sur le fait que la même base de code peut être exécutée en utilisant des configurations diverses et variées. Par exemple, l'environnement dev devrait utiliser une configuration qui rend le développement facile et sympa, alors que l'environnement de prod devrait utiliser un ensemble de configurations optimisées pour la rapidité. Environnements Différents, Fichiers de Configuration Différents Une application Symfony typique démarre avec trois environnements : dev, prod, et test. Comme nous l'avons vu, chaque «environnement» représente simplement une façon d'exécuter la même base de code avec des configurations différentes. 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 utilisés : 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 grâce à un simple standard utilisé par défaut dans la classe AppKernel : Listing - // app/appkernel.php //... class AppKernel extends Kernel //... generated on November, 0 Chapter : Comment Maîtriser et Créer de nouveaux Environnements

44 0 public function registercontainerconfiguration(loaderinterface $loader) $loader->load( DIR.'/config/config_'.$this->getEnvironment().'.yml'); Comme vous pouvez le voir, lorsque Symfony est chargé, il utilise l'environnement donné pour déterminer quel fichier de configuration charger. Cela permet d'avoir des environnements multiples d'une manière élégante, puissante et transparente. Bien sûr, dans la réalité, chaque environnement diffère seulement quelque peu des autres. Généralement, tous les environnements vont avoir en commun une même configuration de base conséquente. Ouvrez le fichier de configuration «dev» et vous verrez facilement comment ceci est accompli : Listing - 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 dévier de la configuration par défaut en surchargeant des paramètres individuels. Par exemple, par défaut, la barre d'outils web_profiler est désactivée. Cependant, en environnement dev, la barre d'outils est activée en modifiant la valeur par défaut dans le fichier de configuration dev : Listing - # app/config/config_dev.yml imports: - resource: config.yml web_profiler: toolbar: true #... Exécuter une Application dans Différents Environnements Pour exécuter l'application dans chaque environnement, chargez l'application en utilisant soit le contrôleur frontal app.php (pour l'environnement prod), soit app_dev.php (pour l'environnement dev) : Listing - -> environnement *prod* -> environnement *dev* Les URLs données supposent que votre serveur web est configuré pour utiliser le répertoire web/ de l'application en tant que racine. Lisez-en plus sur Installer Symfony. Si vous ouvrez l'un de ces fichiers, vous allez rapidement voir que l'environnement utilisé pour chacun est explicitement défini : Listing - <?php require_once DIR.'/../app/bootstrap_cache.php'; require_once DIR.'/../app/AppCache.php'; generated on November, 0 Chapter : Comment Maîtriser et Créer de nouveaux Environnements

45 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 spécifie que cet environnement va être exécuté dans l'environnement prod. Une application Symfony peut être exécutée dans n'importe quel environnement en utilisant ce code et en changeant la chaîne de caractères 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 contrôleur frontal. En d'autres termes, comparé aux autres environnements, il n'y a pas de fichier de contrôleur frontal app_test.php. Mode de Débuggage Quelque chose d'important - sans rapport avec le thème des environnements - est la clé false à la ligne du contrôleur frontal ci-dessus. Cette ligne spécifie si oui ou non l'application doit être exécutée en «mode de débuggage». Peu importe l'environnement, une application Symfony peut être exécutée avec le mode débuggage activé ou désactivé (true ou false). Cela affecte beaucoup de choses dans l'application, comme par exemple si oui ou non les erreurs doivent être affichées ou si les fichiers de cache sont dynamiquement reconstruits à chaque requête. Bien que ce ne soit pas une condition requise, le mode de débuggage est généralement défini comme true pour les environnements dev et test, et comme false pour l'environnement prod. En interne, la valeur du mode de débuggage devient le paramètre kernel.debug utilisé dans le conteneur de service. Si vous regardez le fichier de configuration de l'application, vous verrez le paramètre utilisé pour, par exemple, activer ou désactiver le «logging» quand vous utilisez le DBAL de Doctrine : Listing - doctrine: dbal: logging: #... "%kernel.debug%" Créer un Nouvel Environnement Par défaut, une application Symfony possède trois environnements qui gèrent la plupart des cas. Bien sûr, comme un environnement n'est rien d'autre qu'une chaîne de caractères qui correspond à un ensemble de configurations, créer un nouvel environnement est assez facile. Par exemple, supposons qu'avant un déploiement, vous ayez besoin d'effectuer des essais sur votre application. Une manière de faire cela est d'utiliser presque les mêmes paramètres qu'en production, mais avec le web_profiler de Symfony activé. Cela permet à Symfony d'enregistrer des informations à propos de votre application lorsque vous effectuez vos essais. La meilleure manière d'accomplir ceci est grâce à un nouvel environnement nommé, par exemple, benchmark. Commencez par créer un nouveau fichier de configuration : Listing - # app/config/config_benchmark.yml imports: generated on November, 0 Chapter : Comment Maîtriser et Créer de nouveaux Environnements

46 - resource: config_prod.yml framework: profiler: only_exceptions: false Grâce à ce simple ajout, l'application supporte désormais 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 effectués explicitement ici. Comme vous allez vouloir accéder à cet environnement via un navigateur, vous devriez aussi créer un contrôleur 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 : Listing - <?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: Listing - Certains environnements, comme dev, n'ont jamais pour but d'être accédés par le public sur quelconque serveur déployé. La raison de ceci est que certains environnements, pour des raisons de débuggage, pourraient donner trop d'informations à propos de l'application ou de l'infrastructure sous-jacente. Afin d'être sûr que ces environnements ne soient pas accessibles, le contrôleur frontal est généralement protégé des adresses IP externes grâce au code suivant placé en haut du contrôleur : Listing -0 if (!in_array(@$_server['remote_addr'], array('.0.0.', '::'))) die('you are not allowed to access this file. Check '.basename( FILE ).' for more information.'); Les environnements et le répertoire de Cache Symfony profite du cache de différentes manières : la configuration de l'application, la configuration du routage, les templates Twig et encore plus sont cachés dans des objets PHP stockés dans des fichiers sur le système de fichiers. Par défaut, ces fichiers cachés sont largement stockés dans le répertoire app/cache. Cependant, chaque environnement cache son propre ensemble de fichiers : generated on November, 0 Chapter : Comment Maîtriser et Créer de nouveaux Environnements

47 Listing - app/cache/dev - répertoire de cache pour l'environnement *dev* app/cache/prod - répertoire de cache pour l'environnement *prod* Quelquefois, lorsque vous débuggez, 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 répertoire de l'environnement que vous êtes en train d'utiliser (la plupart du temps dev lorsque vous développez et débuggez). Bien que celui-ci puisse varier, le répertoire app/cache/dev inclut ce qui suit : appdevdebugprojectcontainer.php - le «conteneur de service» caché qui représente la configuration cachée de l'application ; appdevurlgenerator.php - la classe PHP générée sur la base de la configuration de routage et utilisée lors de la génération d'urls ; appdevurlmatcher.php - la classe PHP utilisée pour la correspondance des routes - regardez ici pour voir la logique des expressions régulières compilées et utilisées pour faire correspondre les URLs entrantes aux différentes routes ; twig/ - ce répertoire contient tous les templates Twig cachés. Vous pouvez facilement changer l'emplacement du répertoire et son nom. Pour plus d'informations, lisez l'article Comment surcharger la structure de répertoires par défaut de Symfony. Aller plus loin Lisez l'article sur Comment configurer les paramètres externes dans le conteneur de services. generated on November, 0 Chapter : Comment Maîtriser et Créer de nouveaux Environnements

48 Chapter Comment surcharger la structure de répertoires par défaut de Symfony Symfony est fourni automatiquement avec une structure de répertoires par défaut. Vous pouvez facilement surcharger cette structure de répertoires et créer la vôtre. La structure par défaut est la suivante : Listing - 0 app/ cache/ config/ logs/... src/... vendor/... web/ app.php... Surcharger le répertoire cache Vous pouvez surcharger le répertoire de cache en surchargeant la méthode getcachedir dans la classe AppKernel de votre application: Listing - // app/appkernel.php //... class AppKernel extends Kernel //... generated on November, 0 Chapter : Comment surcharger la structure de répertoires par défaut de Symfony

49 0 public function getcachedir() return $this->rootdir.'/'.$this->environment.'/cache/'; $this->rootdir est le chemin absolu vers le répertoire app et $this->environment est l'environnement actuel (c-a-d dev). Dans ce cas, vous avez changé l'emplacement du répertoire cache pour qu'il devienne app/environment/cache. Vous devriez avoir un répertoire cache différent pour chaque environnement, sinon certains effets de bord pourraient survenir. Chaque environnement génère ses propres fichiers de configuration en cache, et donc, chacun a besoin de son propre répertoire pour stocker ces fichiers. Surcharger le répertoire logs Pour surcharger le répertoire logs, procédez de la même manière que pour le répertoire cache. La seule différence est que vous devez surcharger la méthode getlogdir: Listing - 0 // app/appkernel.php //... class AppKernel extends Kernel //... public function getlogdir() return $this->rootdir.'/'.$this->environment.'/logs/'; Ici, vous avez changé l'emplacement du répertoire pour app/environment/logs. Surcharger le répertoire web Si vous avez besoin de renommer ou de déplacer le répertoire web, la seule chose dont vous devez vous assurer et que le chemin vers le répertoire app est toujours correct dans vos contrôleurs frontaux app.php et app_dev.php. Si vous renommez simplement le répertoire, alors tout est déjà bon. Mais si vous le déplacez, vous aurez besoin de modifier les chemins dans ces fichiers: Listing - require_once DIR.'/../Symfony/app/bootstrap.php.cache'; require_once DIR.'/../Symfony/app/AppKernel.php'; Certains serveurs mutualisés ont un répertoire racine public_html. Renommer votre répertoire de web pour public_html est une manière de faire fonctionner votre projet Symfony sur votre serveur mutualisé. Une autre manière serait déployer votre application en dehors de la racine web, supprimer le répertoire public_html puis le remplacer par une lien symbolique vers le répertoire web de votre projet. generated on November, 0 Chapter : Comment surcharger la structure de répertoires par défaut de Symfony

50 Si vous utilisez le bundle AsseticBundle, vous devrez le configurer pour qu'il utilise le bon répertoire web : Listing - # 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 : Listing - $ php app/console assetic:dump --env=prod --no-debug generated on November, 0 Chapter : Comment surcharger la structure de répertoires par défaut de Symfony 0

51 Chapter Comment configurer les paramètres externes dans le conteneur de services Dans le chapitre doc:/cookbook/configuration/environments, Vous avez vu comment gérer la configuration de votre application. Parfois on aura cependant besoin de stocker certaines données hors du code du projet, par exemple des mots de passe, ou des paramètres de configuration d'une base de données. La flexibilité du conteneur de services Symfony vous le permet. Variables d'environnement Symfony va repérer toute variable d'environnement préfixée de SYMFONY et la stocker en tant que paramètre dans le conteneur de services. les doubles tirets bas sont remplacés par un point, le point n'étant pas un caractère permis dans un nom de variable d'environnement. Par exemple, si vous utilisez Apache, les variables d'environnement peuvent être définies par la configuration VirtualHost suivante: Listing - 0 <VirtualHost *:0> ServerName DocumentRoot DirectoryIndex SetEnv SetEnv Symfony "/path/to/symfony app/web" index.php index.html SYMFONY DATABASE USER user SYMFONY DATABASE PASSWORD secret <Directory "/path/to/symfony app/web"> AllowOverride All Allow from All </Directory> </VirtualHost> generated on November, 0 Chapter : Comment configurer les paramètres externes dans le conteneur de services

52 L'exemple de configuration ci-dessus concerne Apache, à l'aide de la directive SetEnv. Cependant, ceci fonctionnera pour tout serveur permettant la définition de variables d'environnement. De même, afin de permettre l'usage de variables d'environnement par la console (sans serveur), il est nécessaire de définir lesdites variables comme des variables shell. Sur un système Unix: Listing - $ export SYMFONY DATABASE USER=user $ export SYMFONY DATABASE PASSWORD=secret Maintenant que les variables d'environnement ont été déclarées, elles seront présentes dans la variable globale $_SERVER de PHP. Symfony va donc automatiquement recopier les valeurs des variables $_SERVER préfixées de SYMFONY en tant que paramètres du conteneur de services. Vous pourrez ainsi faire référence à ces paramètres si nécessaire. Listing - doctrine: dbal: driver pdo_mysql dbname: symfony_project user: "%database.user%" password: "%database.password%" Constantes Le conteneur de services permet également la définition de constantes PHP comme paramètres. Il suffit de faire correspondre le nom de votre constante à une clé de paramètre et de préciser le type constant. Listing - 0 <?xml version=".0" encoding="utf-"?> <container xmlns=" xmlns:xsi=" <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>. generated on November, 0 Chapter : Comment configurer les paramètres externes dans le conteneur de services

53 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: Listing - # app/config/config.yml imports: - resource: parameters.xml Diverses considérations La directive imports peut être utilisée pour récupérer des paramètres stockés ailleurs. L'import d'un fichier PHP vous permet un maximum de flexibilité dans le conteneur. Le code suivant importe un fichier parameters.php. Listing - # 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 paramètres que vous désirez définir. Ceci est notamment utile lorsque d'importants éléments de configuration sont disponibles dans un format non standard. L'exemple ci-dessous importe des paramètres de configuration de base de données pour Drupal dans le conteneur de services. Listing - // app/config/parameters.php include_once('/path/to/drupal/sites/default/settings.php'); $container->setparameter('drupal.database.url', $db_url); generated on November, 0 Chapter : Comment configurer les paramètres externes dans le conteneur de services

54 Chapter Comment stocker les sessions dans la base de données grâce à PdoSessionStorage Par défaut, Symfony 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 données plutôt que dans des fichiers, car l'usage des bases de données permet plus facilement la gestion de la montée en charge dans un environnement multi-serveurs. Symfony incorpore une solution de stockage de sessions dans la base de données appelée PdoSessionHandler. Pour l'utiliser, il vous suffit de changer quelques paramètres dans config.yml (ou le format de configuration de votre choix): New in version.: Dans Symfony. la classe et l'espace de noms ont évolué. Vous trouvez dorénavant la classe de stockage de session dans l'espace de nom Session\Storage : Symfony\Component\HttpFoundation\Session\Storage. Notez également que dans Symfony., vous devrez configurer handler_id et non pas storage_id comme en Symfony.0. Ci-dessous, vous verrez que %session.storage.options% n'est plus utilisé. Listing - # 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:. generated on November, 0 Chapter : Comment stocker les sessions dans la base de données grâce à PdoSessionStorage

55 class: PDO arguments: dsn: "mysql:dbname=mydatabase" user: myuser password: mypassword session.handler.pdo: class: Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler arguments: %pdo.db_options%] db_table : Nom de la table des sessions dans votre base de données db_id_col : Nom de la colonne identifiant dans la table des sessions (de type VARCHAR() 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) Listing - Partager les informations de connection à la base de données Avec cette configuration, les paramètres de connexion à la base de données ne concernent que le stockage des sessions. Ceci peut fonctionner si vous dédiez une base de données aux sessions. Mais si vous désirez stocker les informations de session dans la même base de données que le reste des données du projet, vous pouvez réutiliser les paramètres de connexion définis dans dans parameter.ini en référençant lesdits paramètres : pdo: class: PDO arguments: - "mysql:dbname=%database_name%" - %database_user% - %database_password% Exemple d'instruction SQL MySQL L'instruction SQL pour la création d'une table de sessions sera probablement proche de : Listing - CREATE TABLE `session` ( `session_id` varchar() NOT NULL, `session_value` text NOT NULL, `session_time` int() NOT NULL, PRIMARY KEY (`session_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf; PostgreSQL Pour PostgreSQL, ce sera plutôt : Listing - CREATE TABLE session ( session_id character varying() NOT NULL, session_value text NOT NULL, generated on November, 0 Chapter : Comment stocker les sessions dans la base de données grâce à PdoSessionStorage

56 ); session_time integer NOT NULL, CONSTRAINT session_pkey PRIMARY KEY (session_id) generated on November, 0 Chapter : Comment stocker les sessions dans la base de données grâce à PdoSessionStorage

57 Chapter Comment utiliser le routeur Apache Symfony, bien que déjà très rapide, fournit également plusieurs astuces pour augmenter encore la vitesse avec quelques modifications. L'une de ces astuces est de laisser Apache gérer les routes directement plutôt que d'utiliser Symfony pour le faire. Changer les paramètres de la configuration du routeur Pour dumper les routes Apache, vous devez d'abord modifier les paramètres de configuration pour dire à Symfony d'utiliser le ApacheUrlMatcher plutôt que celui par défaut : Listing - # app/config/config_prod.yml parameters: router.options.matcher.cache_class: ~ # Désactive le cache du routeur router.options.matcher_class: Symfony\Component\Routing\Matcher\ApacheUrlMatcher Notez que cet ApacheUrlMatcher étend UrlMatcher donc même si vous ne regénérez pas les règles url_rewrite, tout fonctionnera (parce qu'à la fin de ApacheUrlMatcher::match() un appel à parent::match() est réalisé). Générer les règles mod_rewrite Pour tester que cela fonctionne, créons une route très basique pour le bundle de démo : Listing - # app/config/routing.yml hello: pattern: /hello/name defaults: _controller: AcmeDemoBundle:Demo:hello. generated on November, 0 Chapter : Comment utiliser le routeur Apache

58 Maintenant générez les règles url_rewrite : Listing - $ php app/console router:dump-apache -e=prod --no-debug Ce qui devrait afficher quelque chose du genre : Listing - # 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:%,e=_routing controller:acmedemobundle\:demo\:hello] Vous pouvez maintenant réécrire le web/.htaccess pour utiliser les nouvelles règles. Avec cet exemple, cela ressemblerait à ceci : Listing - 0 <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:%,e=_routing controller:acmedemobundle\:demo\:hello] </IfModule> La procédure ci-dessus devrait être effectuée à chaque fois que vous ajoutez/modifiez une route si vous voulez en tirer pleinement avantage C'est tout! Nous sommes maintenant prêts à utiliser les règles de routage Apache. Modification supplémentaires Pour gagner un peu de temps d'éxécution, changez les occurences de Request par ApacheRequest dans le fichier web/app.php: Listing - 0 // 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(); generated on November, 0 Chapter : Comment utiliser le routeur Apache

59 //$kernel = new AppCache($kernel); $kernel->handle(apacherequest::createfromglobals())->send(); generated on November, 0 Chapter : Comment utiliser le routeur Apache

60 Chapter Comment créer une commande pour la Console La page Console de la partie Components (Le Composant Console) décrit comment créer une commande. Cet article du Cookbook aborde les différences lorsque vous créez des commandes pour la console avec le framework Symfony. Enregistrement automatique des commandes Pour que les commandes soient automatiquement disponibles dans Symfony, créez un répertoire Command dans votre bundle et créez un fichier php se terminant par Command.php pour chaque commande que vous voulez créer. Par exemple, si vous voulez étendre le bundle AcmeDemoBundle (disponible dans la Standard Edition de Symfony) pour vous saluer en ligne de commande, créez le fichier GreetCommand.php et insérez y le contenu suivant: Listing - 0 // 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 définie, la tâche generated on November, 0 Chapter : Comment créer une commande pour la Console 0

61 0 0 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 prête à être exécutée : Listing - $ app/console demo:greet Fabien Tester les commandes Pour tester les commandes utilisées dans le cadre du framework, la classe Symfony\Bundle\FrameworkBundle\Console\Application devrait être utilisée au lieu de la classe Symfony\Component\Console\Application : Listing 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 créez 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()); // generated on November, 0 Chapter : Comment créer une commande pour la Console

62 Récupérer des services du Conteneur de services En utilisant ContainerAwareCommand comme classe parente de la commande (au lieu de la classe basique Command ), vous avez accès au conteneur de services. En d'autres termes, vous avez accès à tous les services configurés. Par exemple, vous pouvez facilement étendre la tâche pour gérer les traductions: Listing - 0 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!'));. generated on November, 0 Chapter : Comment créer une commande pour la Console

63 Chapter Comment utiliser la Console La page Utiliser les commandes de console, les raccourcis et les commandes préconstruites de la documentation des Composants traite des options de la console. Lorsque vous utilisez la console comme partie intégrante du framework full-stack, des options globales supplémentaires sont également disponibles. Par défaut, les commandes de la console sont exécutées dans l'environnement de dev, vous pourriez vouloir changer cela pour certaines commandes. Par exemple, vous pourriez vouloir exécuter certaines commandes dans l'environnement de prod pour des raisons de performance. Le résultat de certaines commandes sera différent selon l'environnement, par exemple, la commande cache:clear ne videra le cache que de l'environnement spécifié. Pour vider le cache de prod, vous devez exécuter : Listing - $ php app/console cache:clear --env=prod ou son équivalent : Listing - $ php app/console cache:clear -e prod En plus de changer l'environnement, vous pouvez également choisir de désactiver le mode debug. Cela peut être utile lorsque vous voulez exécuter des commandes dans l'environnement de dev mais éviter de dégrader les performances en collectant des données de debug : Listing - $ php app/console list --no-debug Il existe un shell interactive qui vous permet de taper des commandes sans devoir spécifier php app/ console à chaque fois, ce qui est très utile si vous devez saisir plusieurs commandes. Pour entrer dans le shell, exécutez : Listing - $ php app/console --shell $ php app/console -s Vous pouvez maintenant vous contenter de saisir simplement le nom de la commande : Listing - generated on November, 0 Chapter : Comment utiliser la Console

64 Symfony > list Lorsque vous utilisez le shell, vous pouvez choisir d'exécuter chaque commande dans un processus distinct : Listing - $ php app/console --shell --process-isolation $ php app/console -s --process-isolation Lorsque vous faites cela, la sortie ne sera pas colorisée et l'interactivité n'est pas supportée, vous devrez donc passer chaque paramètre explicitement. A moins que vous n'utilisiez des processus séparés, vider le cache dans le shell n'aura aucun effet sur les commandes que vous exécutez. Ceci est dû au fait que les fichiers de cache originaux sont toujours utilisés. generated on November, 0 Chapter : Comment utiliser la Console

65 Chapter Comment générer des URLs et envoyer des s 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 générez des URLs absolues à travers une Commande Console vous finirez sans doute par obtenir quelque chose comme ce qui n'est pas très utile. Pour corriger cela, vous devez configurer le "request context", qui est une façon élégante de dire que vous avez besoin de configurer votre environnement afin qu'il connaisse quelle URL il devrait utiliser à la génération d'urls. Il y a deux façons 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 générateur d'url - vous pouvez redéfinir les paramètres qu'il utilise comme valeur par défaut pour changer l'host par défaut (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 générées via les requêtes web normales, puisqu'elles remplaces celles par défauts. Listing - # app/config/parameters.yml parameters: router.request_context.host: example.org router.request_context.scheme: https router.request_context.base_url: my/path generated on November, 0 Chapter : Comment générer des URLs et envoyer des s depuis la Console

66 Configurer le Request Context via la Command Pour le changer uniquement en une commande vous pouvez simplement récupérer le Request Context depuis le service router et remplaçants ses réglages: Listing - 0 // 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 s depuis la commande console se fait de la même manière que dans le cookbook Comment envoyer un hormis le fait que le memory spooling est utilisé. En utilisant le memory spooling (consultez le cookbook Comment utiliser le «Spool» d' pour plus d'informations), vous devez savoir que c'est parce que Symfony gère la commande console de manière particulière, que les s ne sont pas envoyés automatiquement. Vous devez prendre soin de néttoyer file vous même. Utiliser le code suivant pour envoyer des s depuis la commande console: Listing - 0 $message = new \Swift_Message(); //... préparez 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 créer un environnement qui ne serait utilisé uniquement que par la commande console et utiliserait une autre méthode de spooling. S'occuper du spooling n'est uniquement nécessaie 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. generated on November, 0 Chapter : Comment générer des URLs et envoyer des s depuis la Console

67 Chapter 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 système 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 déploiements, il serait plus facile d'utiliser les fonctionnalités de log de Symfony, au lieu de configurer d'autres outils pour rassembler la sortie console et la traiter. Ceci peut être particulièrement pratique si vous avez déjà quelques paramètres existants pour aggréger 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 catchées. Logguer manuellement depuis la commande console Cette solution est vraiment simple. Lorsque vous créez une commande console dans le framework complet comme décrit dans "Comment créer une commande pour la Console", votre commande étend ContainerAwareCommand. Cela signifie que vous pouvez simplement accéder au service logger standard à travers le conteneur de services et utilisez le pour logguer: Listing - // 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;. generated on November, 0 Chapter : Comment activer les logs dans la commande console

68 0 0 0 class GreetCommand extends ContainerAwareCommand //... protected function execute(inputinterface $input, OutputInterface $output) $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 paramétrage de logging), vous devriez voir les entrées logguées dans le fichier app/logs/dev.log ou app/logs/prod.log`. Activer le logging automatique des Exceptions Pour faire que votre application console récupére les logs des exceptions manquées automatiquement pour toutes vos commande, vous pouvez utiliser console events. New in version.: Les évènements console ont été ajoutés en Symfony.. Dans un premier temps, configurez un écouteur (listener) pour les évènements exception de la console dans le conteneur de service : Listing - # 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 implémentez l'écouteur (listener): Listing - generated on November, 0 Chapter : Comment activer les logs dans la commande console

69 0 0 0 // 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 évènement. Vous pouvez simplement logguer en passant le service logger via la configuration du service. Votre méthode reçoit un objet ConsoleExceptionEvent, qui a une méthode pour récupérer les informations concernant l'évènement 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 façon, vous saurez si une commande comporte des erreurs, même si une aucune exception n'a été levée. Dans un premier temps, configurez un écouteur pour l'évènement console.termine dans le conteneur de services : Listing - # app/config/services.yml services: kernel.listener.command_dispatch: class: Acme\DemoBundle\EventListener\ConsoleTerminateListener arguments: logger: "@logger". generated on November, 0 Chapter : Comment activer les logs dans la commande console

70 tags: - name: kernel.event_listener, event: console.terminate Puis implémentez l'écouteur (listener): Listing // 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 > ) $statuscode = ; $event->setexitcode($statuscode); $this->logger->warning(sprintf( 'Command `%s` exited with status code %d', $command->getname(), $statuscode )); generated on November, 0 Chapter : Comment activer les logs dans la commande console 0

71 Chapter 0 Comment personnaliser les pages d'erreur Lorsqu'une exception quelconque est lancée dans Symfony, cette dernière est «capturée» par la classe Kernel et éventuellement transmise à un contrôleur spécial, TwigBundle:Exception:show pour qu'il la gère. Ce contrôleur, qui fait partie du coeur de TwigBundle, détermine quelle template d'erreur afficher et le code de statut qui devrait être défini pour l'exception donnée. Les pages d'erreur peuvent être personnalisées de deux manières différentes, dépendant du niveau de contrôle que vous souhaitez :. Personnalisez les templates d'erreur des différentes pages d'erreur (expliqué ci-dessous) ;. Remplacez le contrôleur d'exception par défaut TwigBundle::Exception:show par votre propre contrôleur et gérez le comme vous le désirez (voir exception_controller dans la référence de Twig) ; La personnalisation de la gestion d'exception est en fait bien plus puissante que ce qui est écrit dans ces lignes. Un évènement interne, kernel.exception, est lancé et permet d'avoir le contrôle complet de la gestion des exceptions. Pour plus d'informations, voir L'évènement kernel.exception. Tous les templates d'erreur se trouvent dans le TwigBundle. Pour surcharger ces templates, utilisez simplement la méthode 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 défaut qui est affiché à l'utilisateur final, créez un nouveau template situé à cet emplacement app/resources/twigbundle/views/exception/ error.html.twig : Listing 0- <!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content="text/html; charset=utf-" /> <title>une erreur est survenue : status_text </title> </head> <body> <h>oups! Une erreur est survenue</h> <h>le serveur a retourné une erreur " status_code status_text ".</h> generated on November, 0 Chapter 0: Comment personnaliser les pages d'erreur

72 0 </body> </html> Si vous n'êtes pas familier avec Twig, ne vous inquiétez pas. Twig est un moteur de templating simple, puissant et optionnel qui est intégré dans Symfony. Pour plus d'informations à propos de Twig, voir Créer et utiliser les templates. En plus de la page d'erreur HTML standard, Symfony fournit une page d'erreur par défaut pour quasiment tous les formats de réponse les plus communs, incluant JSON (error.json.twig), XML (error.xml.twig), et même Javascript (error.js.twig), pour n'en nommer que quelques uns. Pour surcharger n'importe lequel de ces templates, créez simplement un nouveau fichier avec le même nom dans le répertoire app/resources/twigbundle/views/exception. C'est la manière standard de surcharger n'importe quel template qui se trouve dans un bundle. Personnaliser la page 0 et les autres pages d'erreur Vous pouvez aussi personnaliser des templates d'erreur spécifiques en vous basant sur le code de statut HTTP. Par exemple, créez un template app/resources/twigbundle/views/exception/ error0.html.twig pour afficher une page spéciale pour les erreurs 0 (page non trouvée). Symfony utilise l'algorithme suivant pour déterminer quel template utiliser : Premièrement, il cherche un template pour le format et le code de statut donné (ex error0.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 complète des templates d'erreur par défaut, jetez un oeil au répertoire Resources/ views/exception du TwigBundle. Dans une installation standard de Symfony, le TwigBundle se trouve à cet emplacement : vendor/symfony/symfony/src/symfony/bundle/twigbundle. Souvent, la façon 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 débuggage qui sont montrées au développeur peuvent aussi être personnalisées de la même manière en créant des templates comme exception.html.twig pour la page d'exception HTML standard ou exception.json.twig pour la page d'exception JSON. generated on November, 0 Chapter 0: Comment personnaliser les pages d'erreur

73 Chapter Comment définir des contrôleurs en tant que Services Dans le Book, vous avez appris comment un contrôleur peut facilement être utilisé lorsqu'il étend la classe de base Controller. Bien que ceci fonctionne très bien, les contrôleurs peuvent aussi être définis en tant que services. Pour faire référence à un contrôleur qui est défini en tant que service, utilisez la notation avec deux-points (:). Par exemple, supposons que vous ayez défini un service nommé my_controller et que vous voulez le transmettre à une méthode appelée indexaction() à l'intérieur du service: Listing - $this->forward('my_controller:indexaction', array('foo' => $bar)); Vous devez utiliser la même notation quand vous définissez la valeur de la route _controller : Listing - my_controller: pattern: / defaults: _controller: my_controller:indexaction Pour utiliser un contrôleur de cette manière, il doit être défini dans la configuration du conteneur de services. Pour plus d'informations, voir le chapitre Service Container. Lorsque vous utilisez un contrôleur défini 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 méthodes «raccourcis», vous allez intéragir directement avec les services dont vous avez besoin. Heureusement, cela est généralement très facile et la classe de base Controller elle-même est une formidable source d'inspiration quant à comment effectuer de nombreuses tâches usuelles.. generated on November, 0 Chapter : Comment définir des contrôleurs en tant que Services

74 Spécifier un contrôleur en tant que service demande un petit plus de travail. L'avantage premier est que le contrôleur en entier ou tout service passé au contrôleur peut être modifié via la configuration du conteneur de services. Cela est spécialement utile lorsque vous développez un bundle opensource ou tout autre bundle qui va être utilisé dans beaucoup de projets différents. Donc, même si vous ne spécifiez pas vos contrôleurs en tant que services, vous allez probablement voir ceci effectué dans quelques bundles Symfony open-source. Utiliser les annotations de routage Lorsque vous utilisez les annotations pour définir le routage dans un contrôleur défini comme service, vous devrez spécifier votre service comme suit: Listing - /** service="my_bundle.annot_controller") */ class AnnotController extends Controller Dans cet exemple, my_bundle.annot_controller devrait être l'id de l'instance du AnnotController défini dans le conteneur de services. Cette partie est documentée dans le generated on November, 0 Chapter : Comment définir des contrôleurs en tant que Services

75 Chapter 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 contrôleur frontal app_dev.php). Cet environnement est optimisé pour : Donner au développeur des informations rapides et claires si quelque chose ne se déroule pas comme prévu (à l'aide de la web debug toolbar, d'exceptions documentées et présentées clairement, du profiler,...) ; Être aussi proche que possible à l'environnement de production afin de préparer le déploiement du projet. Désactiver le bootstrap et le cache des classes Pour rendre l'environnement de production aussi rapide que possible, Symfony crée de longs fichiers PHP dans le dossier cache, qui correspondent à l'aggrégation des classes PHP dont votre projet a besoin à chaque requête. Cependant, ce comportement peut désorienter votre IDE ou votre debugger. Nous allons vous montrer ici comment modifier le mécanisme de cache afin qu'il permette un débuggage des classes intégrées à Symfony. Le contrôleur frontal app_dev.php se compose par défaut du code suivant: Listing - 0 //... 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(); generated on November, 0 Chapter : Comment optimiser votre environnement pour le debuggage

76 Pour faciliter le travail du debugger, désactivez le cache des classes PHP en supprimant l'appel loadclasscache() et en replaçant les fichiers requis comme ceci: Listing - 0 //... // 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 désactivez le cache des classes, n'oubliez pas de revenir aux réglages initiaux après votre session de débuggage. Certains IDEs n'apprécient pas que certaines classes soient enregistrées à différents emplacements. Pour prévenir ces problèmes, vous pouvez désactiver la lecture du dossier cache dans votre IDE, ou changer l'extension utilisée par Symfony pour ces fichiers: Listing - $kernel->loadclasscache('classes', '.php.cache'); generated on November, 0 Chapter : Comment optimiser votre environnement pour le debuggage

77 Chapter Déployer une application Symfony Le déploiement peut être une tâche complexe et variable en fonction de votre configuration et de vos besoins. Cet article n'essaie pas de répondre à tout, mais plutôt d'aborder les besoins les plus récurrents et d'apporter quelques idées lors du déploiement. Bases du déploiement Symfony Les étapes typiques à suivre lors du déploiement d'une application Symfony sont :. Uploader votre code à jour sur le serveur de production;. Mettre à jour vos dépendances Vendor (en général, c'est fait via Composer et cela peut être fait avant l'upload);. Lancer les migrations de base de données ou toute tâche similaire qui apporterait des changements de structure à votre base;. Vider (et peut être plus important encore, faire un "warm up") le cache. Un déploiement peut aussi inclure d'autres étapes comme : Tagger une version particulière de votre code dans votre système de gestion de code; Créer 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 répertoire web pour conserver votre environnement de production propre; Vider les systèmes de cache externes (comme Memcached ou Redis ). Comment déployer une application Symfony Il y a plusieurs manières de déployer une application Symfony. Commençons par les bases pour entrer dans les détails ensuite generated on November, 0 Chapter : Déployer une application Symfony

78 Transfert de fichier de base La manière la plus basique de déployer une application est de copier les fichiers manuellement via ftp/ scp (ou une méthode similaire). Cela a quelques inconvénients puisque vous ne contrôlez pas tout, notamment le processus de mise à jour. Cette méthode implique également de réaliser d'autres tâches manuellement après le transfert de fichiers (voir Tâches communes après le déploiement). Utiliser un système de gestion de version Si vous utilisez un système 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 dépôt. Ainsi, lorsque vous êtes prêt à mettre à jour votre code, il suffit juste de récupérer les dernières modifications de votre dépôt. Cela rend les mises à jour de vos fichiers plus facile, mais vous devrez tout de même vous occuper manuellement d'autres étapes (voir Tâches communes après le déploiement). Utiliser des scripts et d'autres outils Il existe des outils de qualité pour faciliter le déploiement. Il y a même certains outils qui ont spécialement été taillés pour les besoins de Symfony, et d'autres qui s'assurent que tout se passe bien avant, pendant et après le déploiement. Lisez Les outils pour une liste des outils qui peuvent vous aider à déployer. Tâches communes après le déploiement Après avoir déployé 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 système. La méthode que vous utilisez pour déployer votre code source de doit pas déployer ce fichier. Vous devriez plutôt le définir manuellement (ou via un processus) sur votre serveur. B) Mettre à jour les vendors Vos vendors peuvent être mis à jour avant de transférer votre code source (mettez à jour votre répertoire vendor/ puis transférez le avec le reste de votre code source) ou après sur le serveur. Peu importe ce que vous choisissez, mettez à jour vos vendors comme vous le faites d'habitude : Listing - $ 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 : Listing - $ php app/console cache:clear --env=prod --no-debug generated on November, 0 Chapter : Déployer une application Symfony

79 D) Dumpez vos ressources Assetic Si vous utilisez Assetic, vous devrez aussi dumpez vos ressources : Listing - $ 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 données Vider votre cache APC Lancer assets:install (déjà dans composer.phar install) Ajouter/éditer des tâches CRON Mettre vos ressources sur un CDN... Cycle de vie de l'application : intégration continue, qualité,... Alors que cet article couvre les aspects techniques du déploiement, le cycle de vie complet du code depuis le développement jusqu'au serveur de production peut contenir bien d'autres étapes (déploiement en préproduction, qualité, lancement des tests,...). L'utilisation de la préproduction, des tests, de l 'assurance qualité, de l'intégration continue, des migrations de base de données et la capacité de retour arrière en cas d'échec sont fortement recommandés. Il existes des outils simples ou plus complexes qui vous permettent de simplifier le déploiement. N'oubliez pas que déployer votre application implique également de mettre à jour vos dépendances (généralement avec Composer), mettre à jour votre base de données, vider votre cache et de réaliser potentiellement d'autres chose comme mettre vos ressources sur un CDN (voir Tâches communes après le déploiement). Les outils Capifony : Cet outil fournit un ensemble d'outils spécialisés basés sur Capistrano et taillés spécifiquement pour les projets symfony et Symfony. sfdebpkg : Cet outil aide à construire un paquet natif Debian pour vos projets Symfony. Magallanes : generated on November, 0 Chapter : Déployer une application Symfony

80 Cet outil de déploiement semblable à Capistrano est construit en PHP et est peut être plus facile à étendre pour les développeurs PHP qui ont des besoins spécifiques. Bundles: Il existe plusieurs bundles qui proposent des fonctionnalités liés au déploiement directement dans votre console Symfony. Scripts de base: Vous pouvez bien sur utiliser le shell, Ant, ou n'importe quel autre outil de script pour déployer vos projets. Platform as a Service Providers: Paas est une manière relativement nouvelle de déployer votre application. Typiquement, une Paas utilisera un unique fichier de configuration à la racine de votre projet pour déterminer comment construire un environnement à la volée qui supporte votre logiciel. PagodaBox possède un excellent support de Symfony. Vous voulez en savoir plus? Discutez avec la communauté sur le canal IRC Symfony #symfony (sur freenode) pour plus d'informations generated on November, 0 Chapter : Déployer une application Symfony 0

81 Chapter Comment gérer les uploads de fichier avec Doctrine Gérer les uploads de fichier avec les entités Doctrine n'est en rien différent de gérer n'importe quel autre upload. En d'autres termes, vous êtes libre de déplacer le fichier via votre contrôleur après avoir géré la soumission du formulaire. Pour voir des exemples, référez-vous à la page de référence du type fichier. Si vous le choisissez, vous pouvez aussi intégrer l'upload de fichier dans le cycle de vie de votre entité (c-a-d création, mise à jour et suppression). Dans ce cas, lorsque votre entité est créée, mise à jour et supprimée de Doctrine, les processus d'upload et de suppression du fichier se feront de manière automatique (sans avoir besoin de faire quoi que ce soit dans votre contrôleur). Pour que cela fonctionne, vous allez avoir besoin de prendre en compte un certain nombre de détails qui vont être couverts dans cet article du Cookbook. Installation basique Tout d'abord, créez une classe Entité Doctrine simple avec laquelle nous allons travailler: Listing - 0 // src/acme/demobundle/entity/document.php namespace Acme\DemoBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; /** */ class Document /** generated on November, 0 Chapter : Comment gérer les uploads de fichier avec Doctrine

82 */ public $id; /** length=) */ public $name; /** length=, 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 répertoire où les documents uploadés doivent être sauvegardés return DIR.'/../../../../web/'.$this->getUploadDir(); protected function getuploaddir() // on se débarrasse de «DIR» afin de ne pas avoir de problème lorsqu'on affiche // le document/image dans la vue. return 'uploads/documents'; L'entité Document a un nom et est associée à un fichier. La propriété path stocke le chemin relatif du fichier et est persistée dans la base de données. La méthode getabsolutepath() est un moyen pratique de retourner le chemin absolu du fichier alors que la méthode 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 déjà 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 spécifier vos régles de validation (comme montré dans cet exemple), assurez-vous d'avoir activé la validation via les annotations (voir configuration de la validation). Pour gérer l'upload de fichier dans le formulaire, utilisez un champ «virtuel» file. Par exemple, si vous construisez votre formulaire directement dans un contrôleur, cela ressemblerait à quelque chose comme ça: generated on November, 0 Chapter : Comment gérer les uploads de fichier avec Doctrine

83 Listing - 0 public function uploadaction() //... $form = $this->createformbuilder($document) ->add('name') ->add('file') ->getform(); //... Ensuite, créez cette propriété dans votre classe Document et ajoutez quelques règles de validation: Listing - 0 // src/acme/demobundle/entity/document.php //... class Document /** */ public $file; //... Comme vous utilisez la contrainte File, Symfony va automatiquement deviner que le champ du formulaire est un champ d'upload de fichier. C'est pourquoi vous n'avez pas eu à le spécifier explicitement lors de la création du formulaire ci-dessus (->add('file')). Le contrôleur suivant vous montre comment gérer le processus en entier: Listing use Acme\DemoBundle\Entity\Document; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; //... /** */ public function uploadaction() $document = new Document(); $form = $this->createformbuilder($document) ->add('name') ->add('file') ->getform() ; if ($this->getrequest()->ismethod('post')) $form->handlerequest($this->getrequest()); if ($form->isvalid()) $em = $this->getdoctrine()->getmanager(); $em->persist($document); generated on November, 0 Chapter : Comment gérer les uploads de fichier avec Doctrine

84 0 $em->flush(); $this->redirect($this->generateurl(...)); return array('form' => $form->createview()); Lorsque vous écrivez le template, n'oubliez pas de spécifier l'attribut enctype : Listing - <h>upload File</h> <form action="#" method="post" form_enctype(form) > form_widget(form) <input type="submit" value="upload Document" /> </form> Le contrôleur précédent va automatiquement persister l'entité Document avec le nom soumis, mais elle ne va rien faire à propos du fichier et la propriété path sera vide. Une manière facile de gérer l'upload de fichier est de le déplacer juste avant que l'entité soit persistée et ainsi spécifier la propriété path en conséquence. Commencez par appeler une nouvelle méthode upload() sur la classe Document que vous allez créer dans un moment pour gérer l'upload de fichier: Listing - 0 if ($form->isvalid()) $em = $this->getdoctrine()->getmanager(); $document->upload(); $em->persist($document); $em->flush(); $this->redirect(...); La méthode upload() va tirer parti de l'objet UploadedFile, qui correspond à ce qui est retourné après qu'un champ file ait été soumis: Listing - 0 public function upload() // la propriété «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 problèmes de sécurité // la méthode «move» prend comme arguments le répertoire cible et // le nom de fichier cible où le fichier doit être déplacé. generated on November, 0 Chapter : Comment gérer les uploads de fichier avec Doctrine

85 0 $this->file->move($this->getuploadrootdir(), $this->file->getclientoriginalname()); // définit la propriété «path» comme étant le nom de fichier où vous // avez stocké le fichier $this->path = $this->file->getclientoriginalname(); // «nettoie» la propriété «file» comme vous n'en aurez plus besoin $this->file = null; Utiliser les callbacks du «cycle de vie» (lifecycle) Même si cette implémentation fonctionne, elle souffre d'un défaut majeur : que se passe-t-il s'il y a un problème lorsque l'entité est persistée? Le fichier serait déjà déplacé vers son emplacement final même si la propriété path de l'entité n'a pas été persistée correctement. Pour éviter ces problèmes, vous devriez changer l'implémentation afin que les opérations sur la base de données et le déplacement du fichier deviennent atomiques : s'il y a un problème en persistant l'entité ou si le fichier ne peut pas être déplacé, alors rien ne devrait se passer. Pour faire cela, vous devez déplacer le fichier aussitôt que Doctrine persiste l'entité dans la base de donnés. Ceci peut être accompli en s'interférant dans le cycle de vie de l'entité via un callback: Listing - /** */ class Document Ensuite, réfactorisez la classe Document pour tirer parti de ces callbacks: Listing use Symfony\Component\HttpFoundation\File\UploadedFile; /** */ class Document /** */ public function preupload() if (null!== $this->file) // faites ce que vous voulez pour générer un nom unique $this->path = sha(uniqid(mt_rand(), true)).'.'.$this->file->guessextension(); /** generated on November, 0 Chapter : Comment gérer les uploads de fichier avec Doctrine

86 0 0 */ public function upload() if (null === $this->file) return; // s'il y a une erreur lors du déplacement du fichier, une exception // va automatiquement être lancée par la méthode move(). Cela va empêcher // proprement l'entité d'être persistée dans la base de données si // erreur il y a $this->file->move($this->getuploadrootdir(), $this->path); unset($this->file); /** */ public function removeupload() if ($file = $this->getabsolutepath()) unlink($file); La classe effectue maintenant tout ce dont vous avez besoin : elle génère un nom de fichier unique avant de le persister, déplace le fichier après avoir persisté l'entité, et efface le fichier si l'entité est supprimée. Maintenant que le déplacement du fichier est automatiquement pris en charge par l'entité, l'appel de la méthode $document->upload() devrait être supprimé du contrôleur: Listing -0 if ($form->isvalid()) $em = $this->getdoctrine()->getmanager(); $em->persist($document); $em->flush(); $this->redirect(...); Les évènements de sont déclenchés avant et après que l'entité soit persistée dans la base de données. D'autre part, les évènements de sont appelés lorsque l'entité est mise à jour. Les callbacks PreUpdate et PostUpdate sont déclenchés seulement s'il y a un changement dans l'un des champs de l'entité étant persistée. Cela signifie que, par défaut, si vous modifiez uniquement la propriété $file, ces évènements ne seront pas déclenchés, comme la propriété elle-même n'est pas directement persistée 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é. generated on November, 0 Chapter : Comment gérer les uploads de fichier avec Doctrine

87 Utiliser l'id en tant que nom de fichier Si vous voulez utiliser l'id comme nom de fichier, l'implémentation est légèrement différente car vous devez sauvegarder l'extension dans la propriété path, à la place du nom de fichier actuel: Listing use Symfony\Component\HttpFoundation\File\UploadedFile; /** */ class Document // propriété utilisé temporairement pour la suppression private $filenameforremove; /** */ public function preupload() if (null!== $this->file) $this->path = $this->file->guessextension(); /** */ public function upload() if (null === $this->file) return; // vous devez lancer une exception ici si le fichier ne peut pas // être déplacé afin que l'entité ne soit pas persistée dans la // base de données comme le fait la méthode move() de UploadedFile $this->file->move($this->getuploadrootdir(), $this->id.'.'.$this->file->guessextension()); unset($this->file); /** */ public function storefilenameforremove() $this->filenameforremove = $this->getabsolutepath(); /** */ public function removeupload() generated on November, 0 Chapter : Comment gérer les uploads de fichier avec Doctrine

88 0 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 dépend de l'id). Ensuite, une fois que l'objet est bien complètement supprimé de la base de données, vous pouvez supprimer le fichier en toute sécurité (dans PostRemove). generated on November, 0 Chapter : Comment gérer les uploads de fichier avec Doctrine

89 Chapter Comment utiliser les extensions Doctrine: Timestampable, Sluggable, Translatable, etc. Doctrine est très flexible, et la communauté a déjà créé une série d'extensions Doctrine très pratiques afin de vous aider avec les tâches usuelles liées aux entités. Une bibliothèque en particulier - la bibliothèque DoctrineExtensions - fournit l'intégration de fonctionnalités pour les comportements (Behaviors) Sluggable, Translatable, Timestampable, Loggable, Tree et Sortable L'utilisation de chacune de ces extensions est expliquée dans son dépôt. Toutefois, pour installer/activer chaque extension, vous devez enregistrer et activer un Ecouteur d'évènement (Event Listener). Pour faire cela, vous avez deux possibilités :. Utiliser le bundle StofDoctrineExtensionsBundle, qui intègre la bibliothèque ci-dessus.. Implémenter ces services directement en suivant la documentation pour l'intégration dans Symfony : Installer les extensions Gedmo Doctrine dans Symfony generated on November, 0 Chapter : Comment utiliser les extensions Doctrine: Timestampable, Sluggable, Translatable, etc.

90 Chapter Comment enregistrer des listeners («écouteurs» en français) et des souscripteurs d'évènement Doctrine est fourni avec un riche système d'évènement qui lance des évènements à chaque fois - ou presque - que quelque chose se passe dans le système. Pour vous, cela signifie que vous pouvez créer 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 créer un index de recherche indépendant à chaque fois qu'un objet est sauvegardé dans votre base de données. Doctrine définit deux types d'objets qui peuvent «écouter» des évènements Doctrine : les listeners et les souscripteurs d'évènement. Les deux sont très similaires, mais les listeners sont un peu plus simples. Pour plus d'informations, voir Le système d'évènements sur le site de Doctrine. Configurer le listener/souscripteur d'évènement Pour spécifier à un service d'agir comme un listener d'évènements 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 spécifique et tous les gestionnaires d'entité qui utilisent cette connexion. Listing - doctrine: dbal: default_connection: default connections: default: driver: pdo_sqlite memory: true. generated on November, 0 Chapter : Comment enregistrer des listeners («écouteurs» en français) et des souscripteurs d'évènement 0

91 0 0 services: my.listener: class: Acme\SearchBundle\Listener\SearchIndexer tags: - name: doctrine.event_listener, event: postpersist my.listener: class: Acme\SearchBundle\Listener\SearchIndexer tags: - name: doctrine.event_listener, event: postpersist, connection: default my.subscriber: class: Acme\SearchBundle\Listener\SearchIndexerSubscriber tags: - name: doctrine.event_subscriber, connection: default Créer la Classe du Listener Dans l'exemple précédent, un service my.listener a été configuré en tant que listener Doctrine sur l'évènement postpersist. Cette classe derrière ce service doit avoir une méthode postpersist, qui va être appelée lorsque l'évènement est lancé: Listing - 0 // 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 évènement, vous avez accès à un objet LifecycleEventArgs, qui vous donne accès à l'objet entité de l'évènement ainsi qu'au gestionnaire d'entités lui-même. Une chose importante à noter est qu'un listener va écouter toutes les entités de votre application. Donc, si vous ne voulez gérer qu'un type spécifique d'entité (par exemple : une entité Product mais pas une entité BlogPost), vous devez vérifier le nom de la classe de votre entité dans votre méthode (comme montré ci-dessus). generated on November, 0 Chapter : Comment enregistrer des listeners («écouteurs» en français) et des souscripteurs d'évènement

92 Chapter 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 arrière-plan pour effectivement communiquer avec la base de données. Pour en lire plus à propos de l'orm Doctrine, voir «Doctrine et les bases de données». La couche d'abstraction de la base de données (DBAL pour DataBase Abstraction Layer) de Doctrine se situe au plus haut niveau de PDO et offre une API intuitive et flexible pour communiquer avec les bases de données relationnelles les plus populaires. En d'autres termes, la bibliothèque DBAL rend facile l'exécution de requêtes et autres actions sur la base de données. Lisez la Documentation DBAL officielle de Doctrine pour apprendre tous les détails et capacités de la bibliothèque DBAL de Doctrine. Pour démarrer, configurez les paramètres de connexion de la base de données : Listing - # app/config/config.yml doctrine: dbal: driver: pdo_mysql dbname: Symfony user: root password: null charset: UTF Pour une liste complète des options de configuration de DBAL, voir Configuration du DBAL Doctrine. Vous pouvez alors accéder à la connexion DBAL de Doctrine en utilisant le service database_connection : generated on November, 0 Chapter : Comment utiliser la couche DBAL de Doctrine

93 Listing - 0 class UserController extends Controller public function indexaction() $conn = $this->get('database_connection'); $users = $conn->fetchall('select * FROM users'); //... Déclarer des Types de Correspondance Personnalisés Vous pouvez déclarer des types de correspondance personnalisés via la configuration de Symfony. Ils seront ajoutés à toutes les connexions configurées. Pour plus d'informations sur les types de correspondances personnalisés, lisez la section types de correspondances personnalisés de la documentation de Doctrine. Listing - # app/config/config.yml doctrine: dbal: types: custom_first: Acme\HelloBundle\Type\CustomFirst custom_second: Acme\HelloBundle\Type\CustomSecond Déclarer des Types de Correspondance Personnalisés via le SchemaTool Le SchemaTool est utilisé pour inspecter la base de données afin d'en comparer le schéma. Pour effectuer cette tâche, il a besoin de connaître quel type de correspondance utiliser pour chaque type de base de données. En déclarer de nouveaux peut être effectué grâce à la configuration. Faisons correspondre le type ENUM (non-supporté par DBAL par défaut) à un type string : Listing - # app/config/config.yml doctrine: dbal: connections: default: // Autres paramètres de connexion mapping_types: enum: string. generated on November, 0 Chapter : Comment utiliser la couche DBAL de Doctrine

94 Chapter Comment générer des Entités à partir d'une base de données existante Lorsque vous commencez à travailler sur un tout nouveau projet qui utilise une base de données, deux situations différentes peuvent arriver. Dans la plupart des cas, le modèle de base de données est conçu et construit de zéro. Dans d'autres cas, cependant, vous commencerez avec un modèle de base de données existant et probablement inchangeable. Heureusement, Doctrine est fourni avec une série d'outils vous aidant à générer les classes de modèle à partir de votre base de données existante. Comme la documentation des outils Doctrine le dit, faire du «reverse engineering» est un processus qu'on n'effectue qu'une seule fois lorsqu'on démarre un projet. Doctrine est capable de convertir environ 0-0% des informations de correspondance nécessaires en se basant sur les champs, les index et les contraintes de clés étrangères. En revanche, Doctrine ne peut pas identifier les associations inverses, les types d'inhéritance, les entités avec clés étrangères en tant que clés primaires ou les opérations sémantiques sur des associations telles que la «cascade» ou les évènements de cycle de vie. Ainsi, du travail additionnel sur les entités générées sera nécessaire par la suite pour finir la conception de chacune afin de satisfaire les spécificités du modèle 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 entrée «comment» est liée à une entrée «post» grâce à une contrainte de clé étrangère. Listing - CREATE TABLE `blog_post` ( `id` bigint(0) NOT NULL AUTO_INCREMENT, `title` varchar(00) COLLATE utf_unicode_ci NOT NULL, `content` longtext COLLATE utf_unicode_ci NOT NULL, `created_at` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT= DEFAULT CHARSET=utf COLLATE=utf_unicode_ci;. generated on November, 0 Chapter : Comment générer des Entités à partir d'une base de données existante

95 0 CREATE TABLE `blog_comment` ( `id` bigint(0) NOT NULL AUTO_INCREMENT, `post_id` bigint(0) NOT NULL, `author` varchar(0) COLLATE utf_unicode_ci NOT NULL, `content` longtext COLLATE utf_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= DEFAULT CHARSET=utf COLLATE=utf_unicode_ci; Avant d'aller plus loin, assurez-vous que vos paramètres de connexion à la base de données sont correctement définis dans le fichier app/config/parameters.yml (ou à quelconque endroit que ce soit où votre configuration de base de données est conservée) 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 première étape permettant de construire les classes entité depuis une base de données existante est de demander à Doctrine d'introspecter cette dernière et de générer les fichiers de méta-données correspondants. Les fichiers de méta-données décrivent la classe entité à générer en se basant sur les champs des tables. Listing - $ 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 données et de générer les fichiers XML de méta-données dans le dossier src/acme/blogbundle/resources/config/doctrine/ metadata/orm de votre bundle. Il est aussi possible de générer les méta-données au format YAML en changeant le premier argument pour yml. Le fichier de méta-données BlogPost.dcm.xml généré ressemble à ce qui suit : Listing - 0 <?xml version=".0" encoding="utf-"?> <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="00"/> <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=""/> <lifecycle-callbacks/> </entity> </doctrine-mapping> generated on November, 0 Chapter : Comment générer des Entités à partir d'une base de données existante

96 Si vous avez des relations onetomany entre vos entités, vous devrez éditer les fichiers xml ou yml générés pour ajouter une section sur les entités spécifiques afin de définir les attributs inversedby et mappedby de la relation onetomany. Une fois que les fichiers de méta-données sont générés, vous pouvez demander à Doctrine d'importer le schéma et de construire les classes entité qui lui sont liées en exécutant les deux commandes suivantes. Listing - $ php app/console doctrine:mapping:import AcmeBlogBundle annotation $ php app/console doctrine:generate:entities AcmeBlogBundle La première commande génère les classes entité avec des annotations de correspondance, mais vous pouvez bien sûr changer l'argument annotation pour être xml ou yml. La classe entité nouvellement créée ressemble à ce qui suit : Listing <?php // src/acme/blogbundle/entity/blogcomment.php namespace Acme\BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * Acme\BlogBundle\Entity\BlogComment * */ class BlogComment /** bigint $id * type="bigint", nullable=false) */ private $id; /** string $author * type="string", length=00, nullable=false) */ private $author; /** text $content * type="text", nullable=false) */ private $content; /** datetime $createdat * type="datetime", nullable=false) */ generated on November, 0 Chapter : Comment générer des Entités à partir d'une base de données existante

97 0 private $createdat; /** BlogPost * referencedcolumnname="id") */ private $post; Comme vous pouvez le voir, Doctrine convertit tous les champs de la table en propriétés privées et annotées 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é étrangère. De ce fait, vous pouvez trouver une propriété privée $post correspondant à une entité BlogPost dans la classe entité BlogComment. La dernière commande a généré tous les «getters» et «setters» pour les propriétés de vos deux classes entité BlogPost et BlogComment. Les entités générées sont maintenant prêtes à être utilisées. Amusezvous! generated on November, 0 Chapter : Comment générer des Entités à partir d'une base de données existante

98 Chapter Comment travailler avec plusieurs gestionnaires d'entités et connexions Vous pouvez utiliser plusieurs gestionnaires d'entités ou plusieurs connexions dans une application Symfony. Cela est nécessaire si vous utilisez différentes bases de données ou même des «vendors» avec des ensembles d'entités entièrement différents. En d'autres termes, un gestionnaire d'entités qui se connecte à une base de données va gérer quelques entités alors qu'un autre gestionnaire d'entités qui se connecte à une autre base de données pourrait gérer le reste. Listing - Utiliser plusieurs gestionnaires d'entités est assez facile, mais plus avancé et généralement non requis. Soyez sûr que vous nécessitiez plusieurs gestionnaires d'entités avant d'ajouter cette couche de complexité. Le code de configuration suivant montre comment vous pouvez configurer deux gestionnaires d'entités : 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: UTF customer: driver: %database_driver% host: %database_host% port: %database_port% dbname: %database_name% user: %database_user% password: %database_password% charset: UTF generated on November, 0 Chapter : Comment travailler avec plusieurs gestionnaires d'entités et connexions

99 orm: default_entity_manager: default entity_managers: default: connection: default mappings: AcmeDemoBundle: ~ AcmeStoreBundle: ~ customer: connection: customer mappings: AcmeCustomerBundle: ~ Dans ce cas, vous avez défini deux gestionnaires d'entités et les avez appelé default et customer. Le gestionnaire d'entités default gère les entités des bundles AcmeDemoBundle et AcmeStoreBundle, alors que le gestionnaire d'entités customer gère les entités du bundle AcmeCustomerBundle.Vous avez également défini deux connexions, une pour chaque gestionnaire d'entité. Lorsque vous travaillez avec plusieurs connexions ou plusieurs gestionnaires d'entités, 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 défaut (c-a-d default) sera utilisé. Créer votre base de données quand vous utilisez plusieurs connexion : Listing - # 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 schéma quand vous utilisez plusieurs gestionnaires d'entité : Listing - # 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'entités par défaut (c'est-à-dire default) est retourné: Listing - 0 class UserController extends Controller public function indexaction() // les deux retournent le gestionnaire d'entités «default» $em = $this->get('doctrine')->getmanager(); $em = $this->get('doctrine')->getmanager('default'); $customerem = $this->get('doctrine')->getmanager('customer'); generated on November, 0 Chapter : Comment travailler avec plusieurs gestionnaires d'entités et connexions

100 Vous pouvez maintenant utiliser Doctrine comme vous le faisiez avant - en utilisant le gestionnaire d'entités default pour persister et aller chercher les entités qu'il gère et le gestionnaire d'entités customer pour persister et aller chercher ses entités. La même chose s'applique aux appels de repository: Listing class UserController extends Controller public function indexaction() // Retourne un repository géré par le gestionnaire «default» $products = $this->get('doctrine') ->getrepository('acmestorebundle:product') ->findall(); // Manière explicite de traiter avec le gestionnaire «default» $products = $this->get('doctrine') ->getrepository('acmestorebundle:product', 'default') ->findall(); // Retourne un repository géré par le gestionnaire «customer» $customers = $this->get('doctrine') ->getrepository('acmecustomerbundle:customer', 'customer') ->findall(); generated on November, 0 Chapter : Comment travailler avec plusieurs gestionnaires d'entités et connexions 00

101 Chapter 0 Comment définir des fonctions DQL personnalisées Doctrine vous permet de spécifier des fonctions DQL personnalisées. Pour plus d'informations à ce sujet, lisez l'article du cookbook de Doctrine «Fonctions DQL définies par l'utilisateur». Dans Symfony, vous pouvez définir vos fonctions DQL personnalisées comme suit : Listing 0-0 # 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. generated on November, 0 Chapter 0: Comment définir des fonctions DQL personnalisées 0

102 Chapter Comment définir des Relations avec des Classes Abstraites et des Interfaces New in version.: Le ResolveTargetEntityListener est une nouveauté de Doctrine., qui a été «packagé» pour la première fois avec Symfony.. L'un des buts des bundles est de créer des ensembles distincts de fonctionnalités qui n'ont pas beaucoup (ou pas du tout) de dépendances, vous permettant d'utiliser cette fonctionnalité dans d'autres applications sans inclure d'éléments superflus. Doctrine. inclut un nouvel utilitaire appelé le ResolveTargetEntityListener, qui fonctionne en interceptant certains appels dans Doctrine et en ré-écrivant des paramètres targetentity dans vos métadonnées de correspondance durant l'exécution. 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é concrète au moment de l'exécution. Cette fonctionnalité vous permet de définir des relations entre différentes entités sans en faire des dépendances «écrites en dur». Contexte/décor 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 entités séparées, car elles peuvent être utilisées dans d'autres systèmes 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 récupérer le ResolveTargetEntityListener pour remplacer toute mention de l'interface par un objet réel qui implémente cette interface. generated on November, 0 Chapter : Comment définir des Relations avec des Classes Abstraites et des Interfaces 0

103 Mise en place Utilisons les entités basiques suivantes (qui sont incomplètes pour plus de brièveté) pour expliquer comment mettre en place et utiliser le RTEL. Une entité «Customer»: Listing - 0 // 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; /** */ class Customer extends BaseCustomer implements InvoiceSubjectInterface // Dans notre exemple, toutes les méthodes définies dans // l'«invoicesubjectinterface» sont déjà implémentées dans le «BaseCustomer» Une entité «Invoice»: Listing // src/acme/invoicebundle/entity/invoice.php namespace Acme\InvoiceBundle\Entity; use Doctrine\ORM\Mapping AS ORM; use Acme\InvoiceBundle\Model\InvoiceSubjectInterface; /** * Représente une «Invoice». * */ class Invoice /** InvoiceSubjectInterface */ protected $subject; Une «InvoiceSubjectInterface»: Listing - // src/acme/invoicebundle/model/invoicesubjectinterface.php namespace Acme\InvoiceBundle\Model; /** * Une interface que le sujet de la facture devrait implémenter. * Dans la plupart des circonstances, il ne devrait y avoir qu'un unique objet generated on November, 0 Chapter : Comment définir des Relations avec des Classes Abstraites et des Interfaces 0

104 0 0 * qui implémente cette interface puisque le ResolveTargetEntityListener peut * changer seulement la cible d'un objet unique. */ interface InvoiceSubjectInterface // Liste toutes les méthodes additionnelles dont votre // InvoiceBundle aura besoin pour accéder au sujet afin // que vous soyez sûr que vous avez accès à ces méthodes. /** string */ public function getname(); Ensuite, vous devez configurer le «listener», qui informe le DoctrineBundle de votre remplacement : Listing - # app/config/config.yml doctrine: #... orm: #... resolve_target_entities: Acme\InvoiceBundle\Model\InvoiceSubjectInterface: Acme\AppBundle\Entity\Customer Réflexions finales Avec le ResolveTargetEntityListener, vous êtes capable de découpler vos bundles, en les gardant utilisables par eux-mêmes, mais en étant toujours capable de définir des relations entre différents objets. En utilisant cette méthode, vos bundles vont finir par être plus faciles à maintenir indépendamment. generated on November, 0 Chapter : Comment définir des Relations avec des Classes Abstraites et des Interfaces 0

105 Chapter Comment implémenter un simple formulaire de création de compte Certains formulaires ont des champs en plus, dont les valeurs n'ont pas besoin d'être stockées en base de données. Par exemple, vous pourriez vouloir créer un formulaire de création 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 modèle simple : User Vous avez une entité User simple associée à la base de données: Listing // 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; /** message=" already taken") */ class User /** */ protected $id; /** length=) generated on November, 0 Chapter : Comment implémenter un simple formulaire de création de compte 0

106 */ protected $ ; /** length=) */ protected $plainpassword; public function getid() return $this->id; public function get () return $this-> ; public function set ($ ) $this-> = $ ; public function getplainpassword() return $this->plainpassword; public function setplainpassword($password) $this->plainpassword = $password; Cette entité User contient trois champs et deux d'entre eux ( et plainpassword) doivent être affichés dans le formulaire. La propriété doit être unique dans la base de données, ce qui est réalisé par l'ajout d'une validation au début de la classe. Si vous voulez intégrer cet utilisateur dans le système de sécurité, vous devez implémenter la UserInterface du composant Security. Créer un formulaire pour le modèle Ensuite, créez le formulaire pour le modèle User: Listing - // src/acme/accountbundle/form/type/usertype.php namespace Acme\AccountBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; generated on November, 0 Chapter : Comment implémenter un simple formulaire de création de compte 0

107 0 0 0 use Symfony\Component\OptionsResolver\OptionsResolverInterface; class UserType extends AbstractType public function buildform(formbuilderinterface $builder, array $options) $builder->add(' ', ' '); $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 : et plainpassword (dupliqué pour confirmer le mot de passe saisi). L'option data_class spécifie au formulaire le nom de la classe associée (c-a-d l'entité User). Pour en savoir plus sur le composant Formulaire, lisez Formulaires. Imbriquer le formulaire User dans le formulaire de création de compte Le formulaire que vous utiliserez pour la page de création de compte n'est pas le même que le formulaire qui est utilisé pour modifier simplement l'objet User (c-a-d UserType). Le formulaire de création de compte contiendra quelques champs supplémentaires, comme «Accepter les conditions», dont les valeurs ne seront pas stockées en base de données. Commencez par créer une simple classe qui représente la «création de compte» («registration» en anglais): Listing - 0 // 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 /** generated on November, 0 Chapter : Comment implémenter un simple formulaire de création de compte 0

108 0 0 0 */ protected $user; /** */ 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, créez le formulaire pour ce modèle Registration: Listing - 0 // 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 méthode spéciale 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 propriété Registration.user contienne une instance de la classe User. generated on November, 0 Chapter : Comment implémenter un simple formulaire de création de compte 0

109 Gérer la soumission du formulaire Ensuite, vous aurez besoin d'un contrôleur pour prendre en charge le formulaire. Commencez par créer un simple contrôleur pour afficher le formulaire de création de compte: Listing - 0 // 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 : Listing - # 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, créez le contrôleur qui prendra en charge la soumission du formulaire. Il se chargera de la validation, et enregistrera les données dans la base de données: Listing - 0 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())); generated on November, 0 Chapter : Comment implémenter un simple formulaire de création de compte 0

110 C'est tout! Maintenant, votre formulaire valide et vous permet d'enregistrer un objet User dans la base de données. La checkbox supplémentaire terms du modèle Registration est utilisée durant la validation, mais n'est plus utilisée ensuite lors de l'enregistrement de l'utilisateur en base de données. generated on November, 0 Chapter : Comment implémenter un simple formulaire de création de compte 0

111 Chapter Comment envoyer un Envoyer des s est une tâche classique pour n'importe quelle application web et une qui possède des complications et pièges potentiels. Plutôt que de réinventer la roue, une solution pour envoyer des s est d'utiliser le SwiftmailerBundle, qui tire partie de la puissance de la bibliothèque Swiftmailer. N'oubliez pas d'activer le bundle dans votre Kernel avant de l'utiliser: Listing - public function registerbundles() $bundles = array(..., new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(), ); //... Configuration Avant d'utiliser Swiftmailer, soyez sûr d'inclure sa configuration. Le seul paramètre obligatoire de la configuration est transport : Listing - # app/config/config.yml swiftmailer: transport: smtp encryption: ssl auth_mode: login host: smtp.gmail.com username: your_username password: your_password. generated on November, 0 Chapter : Comment envoyer un

112 La plus grande partie de la configuration de Swiftmailer traite de comment les messages eux-mêmes devraient être envoyés. 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-md) 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 où envoyer TOUS les s) disable_delivery (définir à true pour désactiver entièrement l'envoi des s) Envoyer des s La bibliothèque Swiftmailer fonctionne grâce à la création, la configuration et enfin à l'envoi d'objets Swift_Message. Le «mailer» est responsable de l'envoi réél du message et est accessible via le service mailer. Globalement, envoyer un est assez simple: Listing - 0 public function indexaction($name) $message = \Swift_Message::newInstance() ->setsubject('hello ') ->setfrom('[email protected]') ->setto('[email protected]') ->setbody($this->renderview('hellobundle:hello: .txt.twig', array('name' => $name))) ; $this->get('mailer')->send($message); return $this->render(...); Pour garder les choses découplées, le corps de l' a été stocké dans un template et est rendu grâce à la méthode renderview(). L'objet $message supporte beaucoup plus d'options, comme inclure des pièces jointes, ajouter du contenu HTML, et bien plus encore. Heureusement, Swiftmailer couvre le thème de la Création de Messages de manière très détaillée dans sa documentation. Plusieurs articles du cookbook liés à l'envoi d' s dans Symfony sont disponibles : Comment utiliser Gmail pour envoyer des s Comment travailler avec les s pendant le Développement Comment utiliser le «Spool» d' . generated on November, 0 Chapter : Comment envoyer un

113 Chapter Comment utiliser Gmail pour envoyer des s Durant le développement, au lieu d'utiliser un serveur SMTP ordinaire pour envoyer des s, 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 sûr recommandé que vous créiez un compte spécial. Dans le fichier de configuration de développement, changez le paramètre transport pour qu'il contienne la valeur gmail et définissez les paramètres username et password avec les informations d'accès de Google : Listing - # 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 définit encryption, auth_mode et host afin que cela fonctionne avec Gmail. generated on November, 0 Chapter : Comment utiliser Gmail pour envoyer des s

114 Chapter Comment travailler avec les s pendant le Développement Lorsque vous développez une application qui envoie des s, vous voudrez souvent que cette dernière n'en envoie pas au destinataire spécifié pendant le développement. Si vous utilisez le SwiftmailerBundle avec Symfony, vous pouvez facilement réaliser cela à travers les paramètres de configuration sans avoir à faire de quelconques changements dans votre code. Il y a deux choix principaux possibles lorsqu'on parle de gestion d' s durant le développement : (a) désactiver complètement l'envoi d' s ou (b) envoyer tous les s à une adresse spécifique. Désactiver l'envoi Vous pouvez désactiver l'envoi d' s en définissant l'option disable_delivery à true. C'est ce qui est fait par défaut dans l'environnement test dans la distribution Standard. Si vous faites cela dans la configuration spécifique test, alors les s ne seront pas envoyés lorsque vous exécuterez les tests, mais continueront d'être envoyés dans les environnements prod et dev : Listing - # app/config/config_test.yml swiftmailer: disable_delivery: true Si vous voulez aussi désactiver l'envoi dans l'environnement dev, ajoutez tout simplement la même configuration dans le fichier config_dev.yml. Envoyer à une Adresse Spécifique Vous pouvez aussi choisir d'avoir tous les s envoyés à une adresse spécifique à la place de l'adresse réellement spécifiée lors de l'envoi du message. Ceci peut être accompli via l'option delivery_address : Listing - generated on November, 0 Chapter : Comment travailler avec les s pendant le Développement

115 # app/config/config_dev.yml swiftmailer: delivery_address: Maintenant, supposons que vous envoyiez un à Listing - 0 public function indexaction($name) $message = \Swift_Message::newInstance() ->setsubject('hello ') ->setfrom('[email protected]') ->setto('[email protected]') ->setbody($this->renderview('hellobundle:hello: .txt.twig', array('name' => $name))) ; $this->get('mailer')->send($message); return $this->render(...); Dans l'environnement dev, l' sera envoyé à [email protected]. Swiftmailer ajoutera un en-tête supplémentaire à l' , X-Swift-To, contenant l'adresse remplacée, afin que vous puissiez toujours voir à qui il aurait été envoyé. En plus des adresses to, cela va aussi stopper les s envoyés à n'importe quelle adresse des champs CC et BCC. Swiftmailer ajoutera des en-têtes additionnels à l' contenant les adresses surchargées. Ces en-têtes 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 Débuggage Web Vous pouvez voir tout envoyé durant une unique réponse lorsque vous êtes dans l'environnement dev via la Barre d'outils de Débuggage. L'icône d' dans la barre d'outils montrera combien d' s ont été envoyés. Si vous cliquez dessus, un rapport s'ouvrira montrant les détails des s envoyés. Si vous envoyez un et puis redirigez immédiatement vers une autre page, la barre d'outils de débuggage n'affichera pas d'icône d' ni de rapport sur la page d'après. A la place, vous pouvez définir l'option intercept_redirects comme étant true dans le fichier config_dev.yml, ce qui va forcer les redirections à s'arrêter et à vous permettre d'ouvrir le rapport avec les détails des s envoyés. Sinon, vous pouvez ouvrir le profiler après la redirection et rechercher par l'url soumise et utilisée lors de la requête précédente (par exemple : /contact/handle). La fonctionnalité de recherche du profiler vous permet de charger les informations du profiler pour toutes les requêtes passées. Listing - # app/config/config_dev.yml web_profiler: intercept_redirects: true generated on November, 0 Chapter : Comment travailler avec les s pendant le Développement

116 Chapter Comment utiliser le «Spool» d' Quand vous utilisez le SwiftmailerBundle pour envoyer un depuis une application Symfony, il va par défaut l'envoyer immédiatement. Vous pourriez, cependant, vouloir éviter d'avoir un hit de performance entre le Swiftmailer et le transport de l' , qui pourrait avoir pour conséquence que l'utilisateur ait à attendre que la page suivante se charge pendant que l' est envoyé. Cela peut être évité en choisissant d'envoyer les s en mode «spool» au lieu de les envoyer directement. Cela signifie que Swiftmailer n'essaie pas d'envoyer l' 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 s 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 : Listing - # app/config/config.yml swiftmailer: #... spool: type: file path: /path/to/spool Si vous voulez stocker le «spool» quelque part dans votre répertoire de projet, rappelez-vous que vous pouvez utiliser le paramètre %kernel.root_dir% pour référencer la racine du projet : Listing - path: "%kernel.root_dir%/spool" Maintenant, quand votre application envoie un , il ne sera en fait pas envoyé mais ajouté au «spool» à la place. L'envoi des messages depuis le «spool» est effectué séparément. Il y a une commande de la console pour envoyer les messages qui sont dans le «spool» : Listing - $ php app/console swiftmailer:spool:send --env=prod Cette commande possède une option pour limiter le nombre de messages devant être envoyés : generated on November, 0 Chapter : Comment utiliser le «Spool» d'

117 Listing - $ php app/console swiftmailer:spool:send --message-limit=0 --env=prod Vous pouvez aussi définir la limite de temps en secondes : Listing - $ php app/console swiftmailer:spool:send --time-limit=0 --env=prod Bien sûr, vous n'avez pas besoin de lancer cette tâche manuellement. A la place, la commande de la console devrait être lancée par une tâche cron ou une tâche planifiée et exécutée à intervalle régulier. generated on November, 0 Chapter : Comment utiliser le «Spool» d'

118 Chapter Comment mettre en place des filtres avant et après un processus donné Il est très commun, dans le développement d'application web, d'avoir besoin qu'un bout de logique soit exécuté juste avant ou juste après vos actions de contrôleur agissant comme des filtres ou des «hooks». Dans Symfony, cela était effectué avec les méthodes «preexecute» et «postexecute» ; la plupart des principaux «frameworks» ont des méthodes similaires mais il n'y a rien de semblable dans Symfony. La bonne nouvelle est qu'il y a une bien meilleure manière d'interférer le processus Requête -> Réponse en utilisant le composant EventDispatcher. Exemple de validation de jeton Imaginez que vous devez développer une API dans laquelle certains contrôleurs sont publics mais d'autres ont un accès restreint qui est réservé à un ou plusieurs clients. Pour ces fonctionnalités privées, vous pourriez fournir un jeton à vos clients afin qu'ils s'identifient eux-mêmes. Donc, avant d'exécuter votre action de contrôleur, vous devez vérifier 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 définis dans la configuration et aucune mise en place de base de données ni de fournisseur d'authentification via le composant de Sécurité ne vont être utilisés. Créer un filtre avant un évènement kernel.controller Premièrement, stockez un jeton basique en configuration en utilisant le fichier config.yml et la clé «parameters» : Listing - generated on November, 0 Chapter : Comment mettre en place des filtres avant et après un processus donné

119 # app/config/config.yml parameters: tokens: client: pass client: pass Les contrôleurs de Tag devant être vérifiés Un «listener» de kernel.controller est notifié à chaque requête juste avant que le contrôleur ne soit exécuté. D'abord, vous avez besoin de savoir si le contrôleur qui correspond à la requête a besoin d'une validation de token. Une façon propre et facile est de créer une interface vide et de faire en sorte que les contrôleurs l'implémentent: Listing - namespace Acme\DemoBundle\Controller; interface TokenAuthenticatedController //... Un contrôleur qui implémente cette interface ressemble simplement à cela: Listing - 0 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() //... Créer un «Listener» d'évènement Ensuite, vous allez avoir besoin de créer un «listener» d'évènement, qui va contenir la logique que vous souhaitez exécuter avant vos contrôleurs. Si vous n'êtes pas familier avec les «listeners» d'évènement, vous pouvez en apprendre plus sur eux en lisant Comment créer un «listener» («écouteur» en français) d'évènement: Listing - // 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 generated on November, 0 Chapter : Comment mettre en place des filtres avant et après un processus donné

120 0 0 0 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 Symfony 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 nécessite un jeton valide!'); Listing - Déclarez le «Listener» Finalement, déclarez votre «listener» comme un service et «taggez-le» en tant que «listener» d'évènement. En écoutant le kernel.controller, vous dites à Symfony que vous voulez que votre «listener» soit appelé juste avant qu'un contrôleur quelconque soit exécuté : # 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 méthode onkernelcontroller de TokenListener sera exécutée à chaque requête. Si le contrôleur qui doit être exécuté implémente TokenAuthenticatedController, l'authentification par jeton est appliquée. Cela vous permet d'avoir le filtre «avant» que vous vouliez sur tous les contrôleurs. Créer un filtre après un évènement kernel.response En plus d'avoir un «hook» qui est exécuté avant notre contrôleur, vous pouvez également ajouter un hook qui sera exécuté après votre contrôleur. Pour cet exemple, imaginez que vous voulez ajouter un hash generated on November, 0 Chapter : Comment mettre en place des filtres avant et après un processus donné 0

121 sha (avec un salage - ou salt - qui utilise le jeton) à chaque réponse qui a passé notre authentification par jeton. Un autre évènement du noyau de Symfony, appelé kernel.response, est notifié à chaque requête, mais après que le contrôleur a retourné un objet Response. Créer un écouteur «après» est aussi simple que de créer une classe écouteur et de l'enregistrer en tant que service sur cet évènement. Par exemple, prenez le TokenListener de l'exemple précédent et enregistrez d'abord le jeton d'authentification dans les attributs de la requête. Cela indiquera que cette requête a subi une demande d'authentification par jeton: Listing - 0 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 nécessite un jeton valide!'); // marque la requête après authentification $event->getrequest()->attributes->set('auth_token', $token); Maintenant, ajoutons une autre méthode à cette classe, onkernelresponse, qui vérifiera que l'objet requête est marqué et, si c'est le cas, qui définira un en-tête personnalisé pour la réponse: Listing - 0 // ajoutez la nouvelle instruction use en haut de votre fichier use Symfony\Component\HttpKernel\Event\FilterResponseEvent; public function onkernelresponse(filterresponseevent $event) // Vérifie que onkernelcontroller est une requête authentifiée if (!$token = $event->getrequest()->attributes->get('auth_token')) return; $response = $event->getresponse(); // crée un hash et le définit comme en-tête de la réponse $hash = sha($response->getcontent().$token); $response->headers->set('x-content-hash', $hash); Listing - Enfin, un second «tag» est nécessaire dans la définition de service pour notifier Symfony que l'évènement onkernelresponse doit être notifié pour l'évènement 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 generated on November, 0 Chapter : Comment mettre en place des filtres avant et après un processus donné

122 C'est tout! Le TokenListener est maintenant notifié avant chaque contrôleur qui est exécuté (onkernelcontroller) et après chaque réponse retournée par un contrôleur (onkernelresponse). En faisant des contrôleurs spécifiques qui implémentent l'interface TokenAuthenticatedController, nos écouteurs savent quels contrôleurs traiter. Et en stockant une valeur dans les attributs de la requête, la méthode onkernelresponse sait quand ajouter notre nouvel en-tête. Amusez-vous! generated on November, 0 Chapter : Comment mettre en place des filtres avant et après un processus donné

123 Chapter Comment étendre une Classe sans utiliser l'héritage Pour permettre à plusieurs classes d'ajouter des méthodes à une autre, vous pouvez définir la méthode magique call() dans la classe que vous voulez étendre comme cela : Listing class Foo //... public function call($method, $arguments) // crée un évènement 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'évènement? La méthode // n'existe pas if (!$event->isprocessed()) throw new \Exception(sprintf('Call to undefined method %s::%s.', get_class($this), $method)); // retourne la valeur retournée par le listener return $event->getreturnvalue(); Cela utilise un évènement HandleUndefinedMethodEvent spécial qui devrait aussi être créé. C'est en fait une classe générique qui pourrait être réutilisée chaque fois que vous avez besoin de ce pattern d'extension de classe : Listing - use Symfony\Component\EventDispatcher\Event; class HandleUndefinedMethodEvent extends Event generated on November, 0 Chapter : Comment étendre une Classe sans utiliser l'héritage

124 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; /** * Définit la valeur à retourner et stoppe les autres listeners allant * être notifiés */ 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, créez une classe qui va écouter l'évènement foo.method_is_not_found et ajoutez la méthode bar() : Listing - class Bar public function onfoomethodisnotfound(handleundefinedmethodevent $event) generated on November, 0 Chapter : Comment étendre une Classe sans utiliser l'héritage

125 0 0 // nous voulons répondre seulement aux appels de la méthode 'bar' if ('bar'!= $event->getmethod()) // autorise un autre listener à prendre en charge cette méthode // inconnue return; // le sujet de l'objet (l'instance foo) $foo = $event->getsubject(); // les arguments de la méthode bar $arguments = $event->getarguments(); // faites quelque chose //... // définit la valeur retournée $event->setreturnvalue($somevalue); Finalement, ajoutez la nouvelle méthode bar à la classe Foo en déclarant une instance de Bar avec l'évènement foo.method_is_not_found : Listing - $bar = new Bar(); $dispatcher->addlistener('foo.method_is_not_found', array($bar, 'onfoomethodisnotfound')); generated on November, 0 Chapter : Comment étendre une Classe sans utiliser l'héritage

126 Chapter Comment personnaliser le Comportement d'une Méthode sans utiliser l'héritage Faire quelque chose avant ou après l'appel d'une Méthode Si vous souhaitez faire quelque chose juste avant, ou juste après qu'une méthode a été appelée, vous pouvez «dispatcher» un évènement respectivement au début ou à la fin d'une méthode: Listing class Foo //... public function send($foo, $bar) // faites quelque chose avant le début de la méthode $event = new FilterBeforeSendEvent($foo, $bar); $this->dispatcher->dispatch('foo.pre_send', $event); // récupérez $foo et $bar depuis l'évènement, ils pourraient // avoir été modifiés $foo = $event->getfoo(); $bar = $event->getbar(); // l'implémentation réelle est ici $ret =...; // faites quelque chose après la fin de la méthode $event = new FilterSendReturnValue($ret); $this->dispatcher->dispatch('foo.post_send', $event); return $event->getreturnvalue(); generated on November, 0 Chapter : Comment personnaliser le Comportement d'une Méthode sans utiliser l'héritage

127 Dans cet exemple, deux évènements sont lancés : foo.pre_send, avant que la méthode soit exécutée, et foo.post_send après que la méthode est exécutée. Chacun utilise une classe Event personnalisée pour communiquer des informations aux listeners des deux évènements. Ces classes d'évènements devraient être créées par vous-même et devraient permettre, dans cet exemple, aux variables $foo, $bar et $ret d'être récupérées et définies par les listeners. Par exemple, supposons que FilterSendReturnValue possède une méthode setreturnvalue, un listener pourrait alors ressembler à ceci : Listing - public function onfoopostsend(filtersendreturnvalue $event) $ret = $event->getreturnvalue(); // modifie la valeur originale de ``$ret`` $event->setreturnvalue($ret); generated on November, 0 Chapter : Comment personnaliser le Comportement d'une Méthode sans utiliser l'héritage

128 Chapter 0 Comment personnaliser le rendu de formulaire Symfony vous propose un large choix de méthodes pour personnaliser la manière 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 affichés en utilisant la fonction Twig form_row ou la méthode d'aide («helper» en anglais) PHP row : Listing 0- form_row(form.age) Vous pouvez également afficher chacune de ces trois parties d'un champ individuellement : Listing 0- <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 affichés en utilisant un ensemble de balises livré d'office avec Symfony. Par exemple, les deux templates ci-dessus afficheront : Listing 0- <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 : generated on November, 0 Chapter 0: Comment personnaliser le rendu de formulaire

129 Listing 0- form_widget(form) La suite de ce document explique comment le rendu de chaque partie du formulaire peut être modifié à différents niveaux. Pour plus d'informations sur l'affichage de formulaires en général, lisez Afficher un Formulaire dans un Template. Que sont les thèmes 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 : libellés de champs, erreurs, champs texte input, balises select, etc. Ces fragments sont définis comme blocs dans Twig et comme fichiers de templates avec PHP. Un thème 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 thème qui contient une personnalisation des fragments de formulaire concernés. Symfony est fourni avec un thème par défaut (form_div_layout.html.twig pour Twig et FrameworkBundle:Form pour PHP) qui définit chaque fragment nécessaire à l'affichage des différentes parties d'un formulaire. Dans la section suivante, vous apprendrez comment personnaliser un thème 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 généré. Listing 0- form_widget(form.age) affiche : Listing 0- <input type="number" id="form_age" name="form[age]" required="required" value="" /> 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 défaut, avec Twig, le bloc integer_widget du template form_div_layout.html.twig serait choisi. En PHP, cela serait plutôt le fichier integer_widget.html.php situé dans le dossier FrameworkBundle/ Resources/views/Form. L'implémentation par défaut du fragment integer_widget ressemble à ceci : Listing 0- # 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) : Listing generated on November, 0 Chapter 0: Comment personnaliser le rendu de formulaire

130 # 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'idée est qu'un fragment détermine le code HTML généré 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é «thème» de formulaire. Lorsque vous affichez un formulaire, pouvez choisir quel(s) thème(s) de formulaire appliquer. Dans Twig, un thème est un unique fichier de template et les fragments sont des blocs définis dans ce fichier. En PHP, un thème est un répertoire et les fragments sont des fichiers de template individuels dans ce répertoire. 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 affichée (ex widget, label, errors, row). En conséquence, pour personnaliser la manière dont les erreurs sont affichées 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 méthode tire avantage de l'héritage de type de champs. Plus précisément, puisque le type text étend le type field, le composant formulaire cherchera d'abord le fragment spécifique au type (par exemple : text_errors) avant de se rabattre sur le nom du fragment parent si le spécifique n'existe pas (par exemple : form_errors). Pour plus d'informations sur ce sujet, lisez Nommage de Fragment de Formulaire. Thèmes de formulaire Pour apprécier la puissance des thèmes 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. Thèmes 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é : Méthode Avantages Inconvénients Dans le même template que le formulaire Rapide et facile Ne peut pas être réutilisé generated on November, 0 Chapter 0: Comment personnaliser le rendu de formulaire 0

131 Méthode Avantages Inconvénients Dans un template séparé Peut être réutilisé par d'autres templates Nécessite la création d'un template Ces deux méthodes ont les mêmes effets mais ne sont pas aussi avantageuses l'une que l'autre suivant les situations. Méthode : Dans le même template que le formulaire La manière la plus facile de personnaliser le bloc integer_widget est de le modifier directement dans le template qui affiche le formulaire. Listing 0-0 % 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 spéciale % form_theme form _self %, Twig cherchera tout bloc surchargé dans le même 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'inconvénient de cette méthode est que les blocs de formulaire personnalisés ne peuvent pas être réutilisés pour afficher d'autres formulaires dans d'autres templates. En d'autres termes, cette méthode est spécialement utile pour faire des changements applicables à un formulaire spécifique de votre application. Si vous voulez réutiliser vos personnalisations pour certains (ou tous les) autres formulaires, lisez la section suivante. Méthode : Dans un template séparé Vous pouvez également choisir de mettre le bloc de formulaire personnalisé integer_widget dans un template séparé. Le code et le résultat final seront les mêmes, mais vous pourrez alors réutiliser cette personnalisation de formulaire dans d'autres templates : Listing 0-0 # 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 : generated on November, 0 Chapter 0: Comment personnaliser le rendu de formulaire

132 Listing 0- % 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 encadrée par l'élément div comme vous l'avez spécifié dans le bloc personnalisé. Thème de formulaire en PHP Si vous utilisez PHP comme moteur de template, la seule méthode pour personnaliser un fragment est de créer un nouveau fichier de template, ce qui est équivalent à la seconde méthode utilisée par Twig. Le fichier de template doit être nommé en fonction du fragment. Vous devez créer un fichier integer_widget.html.php pour personnaliser le fragment integer_widget. Listing 0- <!-- 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 thème via la méthode settheme : Listing 0- <?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 encadrée par l'élément div. Faire référence aux blocs par défaut d'un formulaire (spécifique à Twig) Jusqu'à présent, pour surcharger un bloc de formulaire particulier, la meilleure méthode consistait à copier le bloc par défaut depuis form_div_layout.html.twig, le copier dans un nouveau template, et le personnaliser. Dans la plupart des cas, vous pouvez éviter cela en référençant 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 même template que le formulaire ou pas. Faire référence aux blocs depuis le même template que le formulaire Importer les blocs en ajoutant la balise use dans le template où vous affichez le formulaire : Listing 0- % use 'form_div_layout.html.twig' with integer_widget as base_integer_widget %. generated on November, 0 Chapter 0: Comment personnaliser le rendu de formulaire

133 A partir de maintenant, lorsque les blocs sont importés depuis form_div_layout.html.twig, le bloc integer_widget sera renommé en base_integer_widget. Cela signifie que lorsque vous redéfinissez le bloc integer_widget, vous pouvez faire référence à l'implémentation par défaut via base_integer_widget : Listing 0- % block integer_widget % <div class="integer_widget"> block('base_integer_widget') </div> % endblock % Faire référence aux blocs par défaut depuis un template externe Si vos personnalisations de formulaire se trouvent dans un template externe, vous pouvez faire référence au bloc par défaut en utilisant la fonction Twig parent() : Listing 0- # 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 référence au bloc par défaut si vous utilisez PHP comme moteur de template. Vous devrez alors copier manuellement le code du bloc par défaut 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 réalisant 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 utilisée quand un formulaire sera affiché. Listing 0- # app/config/config.yml twig: form: resources: - 'AcmeDemoBundle:Form:fields.html.twig' # generated on November, 0 Chapter 0: Comment personnaliser le rendu de formulaire

134 Par défaut, Twig utilise un layout à base de div pour afficher les formulaires. Cependant, certaines personnes préféreront utiliser un layout à base de tableau. Utilisez la ressource form_table_layout.html.twig pour utiliser un tel layout : Listing 0- # 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 plutôt que d'ajouter le template comme ressource : Listing 0- % 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 passée à votre template. PHP En utilisant la configuration suivante, tout fragment de formulaire personnalisé se situant dans le répertoire src/acme/demobundle/resources/views/form sera utilisé lorsque le formulaire sera affiché. Listing 0-0 # app/config/config.yml framework: templating: form: resources: - 'AcmeDemoBundle:Form' #... Par défaut, le moteur de template PHP utilise un layout à base de div pour afficher les formulaires. Néanmoins, certains préféreront afficher leurs formulaires dans un layout à base de tableau. Utilisez la ressource FrameworkBundle:FormTable pour utiliser un tel layout : Listing 0- # 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 plutôt que d'ajouter le template comme ressource : Listing 0- <?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 passée à votre template. Comment personnaliser un champ individuel Jusqu'ici, vous avez vu les différentes manières de personnaliser le widget généré pour tous les types de champ texte. Vous pouvez également personnaliser un champ individuel. Par exemple, supposons que generated on November, 0 Chapter 0: Comment personnaliser le rendu de formulaire

135 vous ayez deux champs texte (nom et prénom) 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 : Listing 0- % 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 définit 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 défini manuellement ou automatiquement généré en se basant sur le nom du type de formulaire (par exemple : ProductType donnera product). Si vous n'êtes pas sûr du nom de votre formulaire, regardez le code source généré de votre formulaire. Vous pouvez aussi surcharger le code d'un champ entier en utilisant la même méthode : Listing 0-0 # _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'à présent, ce document vous a expliqué différentes manières de personnaliser une unique partie de l'affichage d'un formulaire. L'idée est de personnaliser un fragment spécifique qui correspond à une partie du formulaire que vous voulez contrôler (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 méthodes décrites dans la section Thèmes de formulaire. Personnaliser l'affichage des erreurs Le composant formulaire ne prend en charge que la manière dont les erreurs de validation sont affichées, et non les messages d'erreurs eux-mêmes. Les messages d'erreurs sont déterminés par les contraintes de validation que vous appliquez à vos objets. Pour plus d'informations, lisez le chapitre sur la validation. generated on November, 0 Chapter 0: Comment personnaliser le rendu de formulaire

136 Il y a plusieurs manières différentes de personnaliser l'affichage des erreurs lorsqu'un formulaire soumis n'est pas valide. Les messages d'erreur d'un champ sont affichés lorsque vous utilisez le «helper» form_errors : Listing 0- form_errors(form.age) Par défaut, les erreurs sont affichées dans une liste non-ordonnée : Listing 0- <ul> <li>ce champ est obligatoire</li> </ul> Listing 0- 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 Thèmes de formulaire pour savoir comment appliquer ces personnalisations. Vous pouvez aussi personnaliser l'affichage des erreurs pour un seul type de champ spécifique. Par exemple, certaines erreurs qui sont plus globales (c'est-à-dire qui ne sont pas spécifiques à un seul champ) sont affichées séparément, souvent en haut de votre formulaire : Listing 0- form_errors(form) Pour personnaliser uniquement le rendu de ces erreurs, suivez les mêmes 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 affichées, votre fragment personnalisé sera utilisé au lieu du fragment par défaut form_errors. Personnaliser le «Form Row» Quand vous pouvez le faire, la manière 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 généré utilisé pour afficher tous les champs de formulaire, surchargez le fragment form_row. Par exemple, supposons que vous vouliez ajouter une classe à l'élément div qui entoure chaque bloc : generated on November, 0 Chapter 0: Comment personnaliser le rendu de formulaire

137 Listing 0- # 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 Thèmes de formulaire pour savoir comment appliquer cette personnalisation. Ajouter une astérisque «obligatoire» sur les libellés de champs Si vous voulez marquer tous les champs obligatoires par une astérisque (*), vous pouvez le faire en personnalisant le fragment form_label. Avec Twig, si vous faites cette personnalisation dans le même template que votre formulaire, modifiez le tag use et ajoutez ce qui suit : Listing 0-0 % 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 séparé, ajoutez ce qui suit : Listing 0- % 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 : Listing 0- <!-- 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), generated on November, 0 Chapter 0: Comment personnaliser le rendu de formulaire

138 0 $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 Thèmes 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 même template que votre formulaire, modifiez le tag use et ajoutez ce qui suit : Listing 0- % 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 séparé, procédez comme suit : Listing 0- % 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 : Listing 0- <!-- 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')?> /> generated on November, 0 Chapter 0: Comment personnaliser le rendu de formulaire

139 0 <!-- 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 : Listing 0- form_widget(form.title, 'help': 'foobar') Lisez Thèmes de formulaire pour savoir comment appliquer ces personnalisations. Utiliser les Variables La plupart des fonctions disponibles pour afficher les différents parties d'un formulaire (ex widget, label, erreurs, etc) vous permettent également de réaliser certaines personnalisations directement. Jetez un oeil à l'exemple suivant : Listing 0- # 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 détails sur ce concept de Twig, lisez Un peu plus sur les «Variables» de formulaire. generated on November, 0 Chapter 0: Comment personnaliser le rendu de formulaire

140 Chapter Comment utiliser les Convertisseurs de Données Vous allez souvent éprouver le besoin de transformer les données entrées par l'utilisateur dans un formulaire en quelque chose d'autre qui sera utilisé dans votre programme. Vous pourriez effectuer ceci manuellement dans votre contrôleur, mais que se passe-t-il si vous voulez réutiliser ce formulaire spécifique à différents endroits? Supposons que vous ayez une relation «one-to-one» de «Task» («Tâche» en français) vers «Issue» («Problème» en français), par exemple : une tâche possède de manière optionnelle un problème qui lui est lié. Ajouter un élément «listbox» contenant tous les problèmes possibles peut éventuellement engendrer une très longue liste dans laquelle il est impossible de trouver quoi que ce soit. Vous pourriez vouloir utiliser plutôt un champ texte dans lequel l'utilisateur pourra simplement taper le numéro du problème recherché. Vous pouvez essayer de faire cela dans votre contrôleur mais ce n'est pas la meilleure solution. Ce serait beaucoup mieux si votre problème pouvait automatiquement être converti en un objet Issue. C'est maintenant que le Convertisseur de Données entre en jeu. Créer le convertisseur (Transformer) Créez d'abord la classe IssueToNumberTransformer, elle sera chargée de convertir un numéro de problème en un objet Issue et inversement: Listing - 0 // 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 generated on November, 0 Chapter : Comment utiliser les Convertisseurs de Données 0

141 /** ObjectManager */ private $om; /** ObjectManager $om */ public function construct(objectmanager $om) $this->om = $om; /** * Transforms an object (issue) to a string (number). * Issue null $issue string */ public function transform($issue) if (null === $issue) return ""; return $issue->getnumber(); /** * Transforms a string (number) to an object (issue). * string $number Issue null 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 problème avec le numéro "%s" ne peut pas être trouvé!', $number )); return $issue; generated on November, 0 Chapter : Comment utiliser les Convertisseurs de Données

142 Si vous voulez qu'un nouveau problème soit créé quand un numéro inconnu est saisi, vous pouvez l'instancier plutôt 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 créer de nouveau type de champ de formulaire personnalisé en appelant addmodeltransformer (ou addviewtransformer, lisez Convertisseurs de modèle et de vue) sur n'importe quel constructeur de champ: Listing 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', )); //... generated on November, 0 Chapter : Comment utiliser les Convertisseurs de Données

143 Cet exemple requiert que vous ayez passé le gestionnaire d'entité en option lors de la création du formulaire. Plus tard, vous apprendrez comment vous pourriez créer un type de champ issue personnalisé pour éviter de devoir faire cela dans votre contrôleur: Listing - $taskform = $this->createform(new TaskType(), $task, array( 'em' => $this->getdoctrine()->getmanager(), )); Cool, vous avez fini! Votre utilisateur sera maintenant capable de saisir un numéro de problème dans le champ texte, et il sera converti en un object Issue qui représente ce problème. Cela signifie que, après avoir réussi à associer (bind) les données, le framework Formulaire passera un véritable objet Issue à la méthode Task::setIssue() plutôt que son numéro. Si le problème ne peut pas être retrouvé à partir de son numéro, une erreur sera créée pour ce champ et le message de cette erreur peut être contrôlé grâce à 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: Listing - // C'EST INCORRECT, LE CONVERTISSEUR SERA APPLIQUE A TOUT LE FORMULAIRE // regardez l'exemple ci-dessus pour connaître la bonne syntaxe $builder->add('issue', 'text') ->addmodeltransformer($transformer); Convertisseurs de modèle et de vue New in version.: Les noms et méthodes des convertisseurs ont été changés dans Symfony.. prependnormtransformer devient addmodeltransformer et appendclienttransformer devient addviewtransformer. Dans l'exemple ci-dessus, le convertisseur a été utilisé comme convertisseur de «modèle». En fait, il y a deux types différents de convertisseurs, et trois types différents de données. Dans tout formulaire, les trois types de données sont : ) Données modèle - C'est une donnée dans le format utilisé dans votre application (ex, un objet Issue). Si vous appelez Form::getData ou Form::setData, vous traitez avec la donnée «modèle». ) Données normalisée - C'est la version normalisée de votre donnée, et c'est bien souvent la même que votre donnée «modèle» (mais pas dans cet exemple). Elle n'est en général pas utilisée directement. ) Donnée vue - C'est le format qui est utilisé pour remplir les champs eux-mêmes. C'est aussi le format dans lequel l'utilisateur soumettra ses données. Quand vous appelez les méthodes Form::bind($data), la variable $data est dans le format de données «vue». Les deux différents types de convertisseurs vous aident à faire des conversions entre ces types de données : Convertisseurs modèle: transform: «donnée modèle» => «donnée normalisée» reversetransform: «donnée normalisée» => «donnée modèle» Convertisseurs vue: transform: «donnée normalisée» => «donnée vue» reversetransform: «donnée vue» => «donnée normalisée» generated on November, 0 Chapter : Comment utiliser les Convertisseurs de Données

144 Le convertisseur que vous utiliserez dépendra de votre situation. Pour utiliser le convertisseur vue, appelez addviewtransformer. Alors pourquoi utiliser le convertisseur modèle? 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 «modèle» (qui convertit un format normalisé, le numéro du problème, en un format modèle, l'objet Issue, et inversement). La différence entre les convertisseurs est subtile et vous devriez toujours penser à ce que la donnée «normalisée» d'un champ devrait être. Par exemple, la donnée «normalisée» d'un champ texte est une chaîne de caractères, 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 inconvénients : ) Vous devez toujours vous souvenir d'appliquer le convertisseur lorsque vous ajoutez des champs pour saisir des numéros de problème ) Vous devez vous soucier de passer l'option em quand vous créez le formulaire qui utilise le convertisseur. Pour ces raisons, vous pourriez choisir de créer un type de champ personnalisé. Premièrement, créez la classe du type de champ personnalisé: Listing // 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 /** ObjectManager */ private $om; /** 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); generated on November, 0 Chapter : Comment utiliser les Convertisseurs de Données

145 0 0 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é : Listing - 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 spécial issue_selector, ce sera très facile: Listing // 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'; generated on November, 0 Chapter : Comment utiliser les Convertisseurs de Données

146 Chapter Comment modifier dynamiquement les formulaires en utilisant les évènements Souvent, un formulaire ne pourra pas être créé de façon statique. Dans cet article, vous apprendrez à personnaliser vos formulaires selon trois cas courants:. Personnaliser un formulaire en se basant sur les données Exemple: vous avez un formulaire "Product" et vous devez modifier/ajouter/supprimer un champ selon les données de l 'objet avec lequel vous travaillez.. Comment gérer des formulaires en se basant sur les données utilisateur Exemple: vous créez un formulaire "Message" et vous devez construire une liste déroulante qui ne contient que les utilisateurs qui sont amis avec l'utilisateur actuellement authentifié.. Génération 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 données Avant de se lancer directement dans la génération dynamique de formulaire, commençons par rappeler ce à quoi ressemble une classe de formulaire basique: Listing - 0 // 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'); generated on November, 0 Chapter : Comment modifier dynamiquement les formulaires en utilisant les évènements

147 0 $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 familière, 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 possède uniquement deux propriétés («name» et «price»). Le formulaire généré à partir de cette classe sera identique que ce soit pour la création d'un Produit ou pour l'édition d'un Produit existant (par exemple : un Produit récupéré depuis la base de données). 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 Répartiteur d'évènements de Symfony pour analyser les données de l'objet et modifier le formulaire en se basant sur les données de l'objet Product. Dans cet article, vous allez apprendre comment ajouter ce niveau de flexibilité à vos formulaires. Ajouter un écouteur d'évènement à une classe de formulaire Donc, au lieu d'ajouter directement ce widget name`, la responsabilité de créer ce champ en particulier est laissée à un écouteur d'évènement: Listing // 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 ); generated on November, 0 Chapter : Comment modifier dynamiquement les formulaires en utilisant les évènements

148 Le but est de créer un champ name uniquement si l'objet Product sous-jacent est nouveau (par exemple : n'a pas été persisté dans la base de données). Partant de ce principe, l'écouteur d'évènement pourrait ressembler à quelque chose comme ça: Listing - 0 //... public function buildform(formbuilderinterface $builder, array $options) //... $builder->addeventlistener(formevents::pre_set_data, function(formevent $event) $product = $event->getdata(); $form = $event->getform(); // vérifie si l'objet Product est "nouveau" // Si aucune donnée n'est passée au formulaire, la donnée est "null". // Ce doit être considéré comme un nouveau "Product" if (!$product null === $product->getid()) $form->add('name', 'text'); ); New in version.: La possibilité de passer une chaine de caractères à FormInterface::add est une nouveauté de Symfony.. Vous pouvez bien sûr utiliser n'importe quel type de callback au lieu d'une closure, par exemple une méthode de l'objet ProductType` pour une meilleur lisibilité: Listing - 0 //... 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 a un but organisationnel. C'est un endroit centralisé où vous trouverez la liste des différents évènements de formulaire disponibles. Vous pouvez voir la liste complète des évènement de formulaire dans la classe FormEvents generated on November, 0 Chapter : Comment modifier dynamiquement les formulaires en utilisant les évènements

149 Ajouter un souscripteur d'évènement sur un formulaire Pour une meilleur réutilisabilité ou s'il y a trop de logique dans votre écouteur d'évènement, vous pouvez également déplacer la logique qui crée le champ name dans un souscripteur d'évènements: Listing - 0 // 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 crée le champ name est située dans sa propre classe de souscripteur: Listing // 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'évènement // form.pre_set_data et que la méthode presetdata doit être appelée 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 gérer des formulaires en se basant sur les données utilisateur Parfois, vous voulez qu'un formulaire soit généré dynamiquement en se basant sur les données du formulaire mais aussi sur quelque chose d'autre - par exemple des données de l'utilisateur connecté. Supposons que vous travaillez sur un site social où un utilisateur ne peut envoyer des messages qu'à des generated on November, 0 Chapter : Comment modifier dynamiquement les formulaires en utilisant les évènements

150 personnes marquées 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. Créer le type de formulaire En utilisant un écouteur d'évènements, votre formulaire pourrait ressembler à ceci: Listing // 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 problème est maintenant de récupérer l'utilisateur actuel et de créer 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: Listing - private $securitycontext; public function construct(securitycontext $securitycontext) $this->securitycontext = $securitycontext; generated on November, 0 Chapter : Comment modifier dynamiquement les formulaires en utilisant les évènements 0

151 Vous devez vous demander, maintenant que vous avez accès à l'utilisateur (au travers du security context), pourquoi ne pas utiliser directement buildform et éviter de passer par un écouteur d'évènement? C'est parce que le faire dans la méthode buildform signifierait que tout le type de formulaire sera modifié et non pas juste l'instance de formulaire que vous voulez. Ce n'est généralement pas très grave, mais techniquement, un seul type de formulaire pourrait être utilisé dans une seule requête pour créer 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: Listing // 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') ; // récupère le user et vérifie 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é!' ); 'DESC'); $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 requête personnalisée // retourne $er->createquerybuilder('u')->addorderby('fullname', // ou appelle une méthode d'un repository qui retourne un query generated on November, 0 Chapter : Comment modifier dynamiquement les formulaires en utilisant les évènements

152 0 builder ); );, // l'instance $er est une instance de UserRepository // retourne $er->createorderbyfullnamequerybuilder(); // crée le champ, cela équivaut à $builder->add() // nom du champ, type de champ, donnée, options $form->add('friend', 'entity', $formoptions); //... Les options multiple et expanded valent false par défaut car le type de champ est entity. Utiliser le formulaire Votre formulaire est maintenant prêt à être utilisé et il y a deux manières possibles de l'utiliser dans un contrôleur :. le créer manuellement et y passer le security context; ou. le définir comme service. a) Créer le formulaire manuellement C'est très 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: Listing -0 0 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 définir votre formulaire comme service, créez simplement un service classique que vous taggerez avec form.type. Listing - # app/config/config.yml services: generated on November, 0 Chapter : Comment modifier dynamiquement les formulaires en utilisant les évènements

153 acme.form.friend_message: class: Acme\DemoBundle\Form\Type\FriendMessageFormType arguments: tags: - name: form.type, alias: acme_friend_message Si vous désirez le créer dans un contrôleur ou dans n'importe quel autre service qui a accès la form factory, alors faites: Listing - 0 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: Listing - $form = $this->createform('acme_friend_message'); Vous pouvez également l'imbriquer facilement dans un autre formulaire: Listing - // dans une classe "form type" public function buildform(formbuilderinterface $builder, array $options) $builder->add('message', 'acme_friend_message'); Génération dynamique pour formulaire soumis Un autre cas qui peut survenir est que vous voulez personnaliser votre formulaire selon des données soumises par l'utilisateur. Par exemple, imaginez que vous ayez un formulaire d'inscription pour des rassemblements sportifs. Certains évènements vous permettront de spécifier votre position préférée sur le terrain. Ce serait par exemple une liste de choix. Cependant, les choix possibles dépendront de chaque sport. Le football aura des attaquants, des défenseurs, 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 accéder à chaque sport comme ceci: Listing - // 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; //... generated on November, 0 Chapter : Comment modifier dynamiquement les formulaires en utilisant les évènements

154 0 0 0 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 première fois, alors cet exemple fonctionne parfaitement. Cependant, les choses se compliqueront lorsque vous gèrerez la soumission. Ceci est dû au fait que l'évènement PRE_SET_DATA nous donne la donnée avec laquelle vous commencez (un objet SportMeetup vide), et non pas la données soumise. Dans un formulaire, vous pouvez écouter les évènements suivants: PRE_SET_DATA POST_SET_DATA PRE_SUBMIT SUBMIT POST_SUBMIT New in version.: Les évènements PRE_SUBMIT, SUBMIT et POST_SUBMIT ont été ajoutés dans Symfony.. Avant, ils étaient nommés PRE_BIND, BIND et POST_BIND. New in version..: Le comportement de l'évènement POST_SUBMIT a changé dans la version... Ci-dessous, un exemple d'utilisation. La solution est d'ajouter un écouteur POST_SUBMIT sur le champ dont votre nouveau champ dépend. Si vous ajoutez un écouteur POST_SUBMIT sur un champ enfant (ex sport), et ajoutez un nouvel enfant au formulaire parent, le composant Form détectera automatiquement le nouveau champ et lui associera les données soumises par le client. Le type de formulaire ressemble maintenant à ceci: Listing - generated on November, 0 Chapter : Comment modifier dynamiquement les formulaires en utilisant les évènements

155 // 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 récupérer ici $event->getform()->getdata(), // car $event->getdata() vous renverra la données 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 évènements et avoir différentes fonctions de callback juste parce que dans deux scénarios différents, les données que vous pouvez utiliser sont disponibles dans différents évènements. A part cela, les écouteurs réalisent exactement la même chose dans un formulaire donné. Mais il manque encore la mise à jour du formulaire côté client après que la sélection du sport a été faite. Cela devrait être fait grâce à 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 réponse de l'appel AJAX pourra alors être utilisée pour mettre à jour la vue. generated on November, 0 Chapter : Comment modifier dynamiquement les formulaires en utilisant les évènements

156 Supprimer la validation de formulaire Pour supprimer la validation, vous pouvez utiliser l'évènement POST_SUBMIT et empêcher le ValidationListener d'être appelé. Vous pouvez être amené à faire cela si vous définissez group_validation à false car, même dans ce cas, certaines vérifications sont tout de même faites. Par exemple, un fichier uploadé sera quand même vérifié pour voir s'il est trop volumineux, et un formulaire vérifiera également si des champs supplémentaires ont été soumis. Pour désactiver tout cela, utilisez un écouteur: Listing - 0 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();, 00); // Définissez toujours une priorité plus grande que le ValidationListener //... En faisant cela, vous risquez de désactiver plus que la validation du formulaire, puisque POST_SUBMIT peut avoir d'autres écouteurs.. generated on November, 0 Chapter : Comment modifier dynamiquement les formulaires en utilisant les évènements

157 Chapter Comment imbriquer une Collection de Formulaires Dans cet article, vous allez apprendre à créer 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/créer/supprimer différents objets Tag liés à cette «Task», tout cela dans le même formulaire. Dans cet article, nous supposons que vous utilisez Doctrine en tant qu'outil de stockage pour votre base de données. Mais si vous n'utilisez pas Doctrine (par exemple : Propel ou une simple connexion à la base de données), tout reste très similaire. Seules quelques sections de ce tutoriel s'occupent de la «persistence». Si vous utilisez Doctrine, vous aurez besoin d'ajouter les méta-données de Doctrine, incluant la relation ManyToMany sur la propriété tags de Task. Pour commencer, supposons que chaque Task appartienne à plusieurs objets Tags. Créons tout d'abord une simple classe Task: Listing - 0 // 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(); generated on November, 0 Chapter : Comment imbriquer une Collection de Formulaires

158 0 0 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 spécifique à Doctrine et revient à utiliser un array (mais cela doit être une ArrayCollection si vous utilisez Doctrine). Maintenant, créez une classe Tag. Comme vous l'avez vu ci-dessus, une Task peut avoir plusieurs objets Tag: Listing - // src/acme/taskbundle/entity/tag.php namespace Acme\TaskBundle\Entity; class Tag public $name; La propriété name est «public» ici, mais elle pourrait tout aussi bien être «protected» ou «private» (ce qui impliquerait d'avoir des méthodes getname et setname). Venons-en maintenant aux formulaires. Créez une classe formulaire afin qu'un objet Tag puisse être modifié par l'utilisateur: Listing - 0 // 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) generated on November, 0 Chapter : Comment imbriquer une Collection de Formulaires

159 0 $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-même. Mais comme le but final est de permettre la modification des tags d'une Task directement depuis le formulaire de la «task» lui-même, créez un formulaire pour la classe Task. Notez que vous imbriquez une collection de formulaires TagType en utilisant le type de champ collection: Listing // 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 contrôleur, vous allez maintenant initialiser une nouvelle instance de TaskType: Listing - // src/acme/taskbundle/controller/taskcontroller.php namespace Acme\TaskBundle\Controller; use Acme\TaskBundle\Entity\Task; generated on November, 0 Chapter : Comment imbriquer une Collection de Formulaires

160 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 intéressant $tag = new Tag(); $tag->name = 'tag'; $task->gettags()->add($tag); $tag = new Tag(); $tag->name = 'tag'; $task->gettags()->add($tag); // fin du code de test $form = $this->createform(new TaskType(), $task); // analyse le formulaire quand on reçoit une requête POST if ($request->ismethod('post')) $form->handlerequest($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 tâche ainsi que les formulaires TagType pour n'importe quels tags qui sont liés à cet objet Task. Dans le contrôleur ci-dessus, j'ai ajouté du code de test afin que vous puissiez voir cela en action (puisqu'un objet Task possède zéro tag lorsqu'il est créé pour la première fois). Listing - 0 # src/acme/taskbundle/resources/views/task/new.html.twig # #... # <form action="..." method="post" form_enctype(form) > # affiche l'unique champ de la tâche : description # form_row(form.description) <h>tags</h> <ul class="tags"> # itère sur chaque tag existant et affiche son unique champ : name # % for tag in form.tags % <li> form_row(tag.name) </li> % endfor % </ul> generated on November, 0 Chapter : Comment imbriquer une Collection de Formulaires 0

161 form_rest(form) #... # </form> Lorsque l'utilisateur soumet le formulaire, les données soumises pour les champs Tags sont utilisées pour construire une collection «ArrayCollection» d'objets Tag, qui est ensuite affectée au champ tag de l'instance Task. La collection Tags est naturellement accessible via $task->gettags() et peut être persistée dans la base de données ou utilisée de la manière 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 manière 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 intégrer des collections imbriquées avec autant de sous-niveaux que vous souhaitez. Mais si vous utilisez Xdebug dans votre environnement de développement, vous pourriez recevoir une erreur telle Maximum function nesting level of '00' reached, aborting!. Cela est dû au paramètre PHP xdebug.max_nesting_level, qui est défini avec une valeur de 00 par défaut. Cette directive limite la récursion à 00 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 définir cette directive avec une valeur plus haute (soit dans le fichier ini PHP ou à l'aide de ini_set, 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 tôt, vous avez ajouté deux tags à votre formulaire dans le contrôleur. Maintenant, vous devez permettre à l'utilisateur d'ajouter autant de tags qu'il souhaite directement depuis le navigateur. Quelques lignes de Javascript sont nécessaires pour effectuer cela. La première chose que vous devez faire est de spécifier à 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 levée : This form should not contain extra fields ce qui signifie que le formulaire ne peut contenir de champs supplémentaires. Pour rendre cela flexible, vous ajoutez l'option allow_add à votre champ collection: Listing - 0 // 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(),. generated on November, 0 Chapter : Comment imbriquer une Collection de Formulaires

162 )); '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 définissant by_reference à false, settags sera appelée. 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 nécessaire pour afficher un nouveau formulaire «tag». Pour l'utiliser, faites le changement suivant dans votre formulaire : Listing - <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 extérieur avec l'attribut data-prototype, similaire à ce que vous voyez ci-dessus. form.tags.vars.prototype est un élément de formulaire qui ressemble à l'élément individuel form_widget(tag) à l'intérieur de votre boucle for. Cela signifie que vous pouvez appeler form_widget, form_row, ou form_label sur ce prototype. Vous pourriez même choisir de n'afficher qu'un seul de ses champs (par exemple : le champ name) : Listing - form_widget(form.tags.vars.prototype.name) e Sur la page affichée, le résultat ressemblera à quelque chose comme ceci : Listing -0 <ul class="tags" data-prototype="<div><label class=" required"> name </label><div id="task_tags name "><div><label for="task_tags name name" class=" required">name</ label><input type="text" id="task_tags name name" name="task[tags][ name ][name]" required="required" maxlength="" /></div></div></div>"> 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 déjà 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'évènement «click» de ce lien afin que vous puissiez ajouter un formulaire tag (addtagform sera expliqué plus tard) : Listing - // Récupère le div qui contient la collection de tags var collectionholder = $('ul.tags'); generated on November, 0 Chapter : Comment imbriquer une Collection de Formulaires

163 0 // 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) // empêche le lien de créer 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 chaîne de caractères name est une variable de substitution («placeholder» en anglais) que vous remplacerez avec un nombre unique et incrémental (par exemple : task[tags][][name]). New in version.: La variable de substitution a été changée dans Symfony.. Au lieu de $$name$$, elle se nomme dorénavant name. Le code nécessaire pour faire fonctionner tout cela peut varier, mais en voici un exemple : Listing - 0 function addtagform(collectionholder, $newlinkli) // Récupère l'élément ayant l'attribut data-prototype comme expliqué plus tôt 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 sousformulaire apparaîtra sur la page. Lors de la soumission, tout nouveau formulaire de tag sera converti en un nouvel objet Tag et ajouté à la propriété tags de l'objet Task. generated on November, 0 Chapter : Comment imbriquer une Collection de Formulaires

164 Doctrine: Relations de Cascade et sauvegarde du côté «Inverse» Afin que les nouveaux tags soient sauvegardés dans Doctrine, vous devez prendre en compte certains éléments. Tout d'abord, à moins que vous n'itériez 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 réparer cela, vous pourriez choisir d'effectuer automatiquement l'opération de persistence en mode «cascade» de l'objet Task pour tout les tags liés. Pour faire ceci, ajoutez l'option cascade à votre méta-donnée ManyToMany : Listing - // src/acme/taskbundle/entity/task.php //... /** cascade="persist") */ protected $tags; Un second problème potentiel peut toucher les relations de Doctrine en ce qui concerne Le côté Propriétaire et le côté Inverse. Dans cet exemple, si le côté «propriétaire» dans la relation est «Task», alors la persistence va fonctionner sans problème comme les tags sont ajoutés correctement à la «Task». Cependant, si le côté «propriétaire» est «Tag», alors vous aurez besoin de coder un peu plus afin de vous assurer que le bon côté de la relation est correctement modifié. L'astuce est de s'assurer qu'une unique «Task» est définie pour chaque «Tag». Une manière facile de faire cela est d'ajouter un bout de logique supplémentaire à la méthode settags(), qui est appelée par le framework formulaire puisque la valeur de by_reference est définie comme false: Listing - 0 // 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 méthode addtask: Listing - // src/acme/taskbundle/entity/tag.php //... public function addtask(task $task) if (!$this->tasks->contains($task)) $this->tasks->add($task); generated on November, 0 Chapter : Comment imbriquer une Collection de Formulaires

165 0 Si vous avez une relation OneToMany, alors l'astuce reste similaire, excepté que vous pouvez simplement appeler la méthode settask depuis la méthode settags. Autoriser des tags à être supprimés La prochaine étape est d'autoriser la suppression d'un élément 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: Listing - 0 // 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 conséquence : si un élément d'une collection n'est pas envoyé lors de la soumission, les données liées à cet élément sont supprimées de la collection sur le serveur. La solution est donc de supprimer l'élément formulaire du DOM. Premièrement, ajoutez un lien «Supprimer ce tag» dans chaque formulaire de tag : Listing - 0 jquery(document).ready(function() // ajoute un lien de suppression à tous les éléments li de // formulaires de tag existants collectionholder.find('li').each(function() addtagformdeletelink($(this)); ); //... le reste du bloc vu plus haut ); function addtagform() // generated on November, 0 Chapter : Comment imbriquer une Collection de Formulaires

166 // ajoute un lien de suppression au nouveau formulaire addtagformdeletelink($newformli); La fonction addtagformdeletelink va ressembler à quelque chose comme ceci : Listing - 0 function addtagformdeletelink($tagformli) var $removeforma = $('<a href="#">supprimer ce tag</a>'); $tagformli.append($removeforma); $removeforma.on('click', function(e) // empêche le lien de créer un «#» dans l'url e.preventdefault(); ); // supprime l'élément 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 passée à 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. generated on November, 0 Chapter : Comment imbriquer une Collection de Formulaires

167 Doctrine: Assurer la persistence dans la base de données Quand vous supprimez des objets de cette manière, vous pourriez avoir à travailler un peu plus afin de vous assurer que la relation entre la «Task» et le «Tag» supprimé soit correctement enlevée. Dans Doctrine, vous avez deux «côtés» dans une relation : le côté propriétaire et le côté inverse. Normalement, dans ce cas, vous aurez une relation ManyToMany et les tags supprimés disparaîtront et seront persistés correctement (ajouter des tags fonctionne aussi sans efforts supplémentaires). Mais si vous avez une relation OneToMany ou une ManyToMany avec un mappedby sur l'entité «Task» (signifiant qu'une «Task» est le côté «inverse»), vous devrez effectuer plus de choses afin que les tags supprimés soient persistés correctement. Dans ce cas, vous pouvez modifier le contrôleur afin qu'il efface la relation pour les tags supprimés. Ceci suppose que vous ayez une editaction qui gére la «mise à jour» de votre «Task»: Listing // 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 tâche trouvée pour cet id : '.$id); $originaltags = new ArrayCollection(); // Crée un tableau contenant les objets Tag courants de la // base de données foreach ($task->gettags() as $tag) $originaltags->add($tag); $editform = $this->createform(new TaskType(), $task); if ($request->ismethod('post')) $editform->handlerequest($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 generated on November, 0 Chapter : Comment imbriquer une Collection de Formulaires

168 0 $id))); // 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' => // affiche un template de formulaire quelconque Comme vous pouvez le voir, ajouter et supprimer les éléments correctement peut ne pas être trivial. A moins que vous n'ayez une relation ManyToMany où «Task» est le côté «propriétaire», vous devrez ajouter du code supplémentaire 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. generated on November, 0 Chapter : Comment imbriquer une Collection de Formulaires

169 Chapter Comment Créer 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 créer un type de champ de formulaire personnalisé pour un besoin spécifique. Cet article part du principe que vous avez besoin d'une définition de champ qui s'occupe du sexe/genre d'une personne, basée sur le champ existant «choice». Cette section explique comment le champ est défini, comment vous pouvez personnaliser son affichage et, finalement, comment vous pouvez le déclarer afin de pouvoir l'utiliser dans votre application. Définir le Type de Champ Afin de créer le type de champ personnalisé, vous devez créer tout d'abord la classe représentant le champ. Dans cette situation, la classe s'occupant du type de champ sera nommée GenderType et le fichier sera stocké dans le répertoire par défaut pour les champs de formulaire, c'est-à-dire <BundleName>\Form\Type. Assurez-vous que le champ étend AbstractType : Listing - 0 // 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',. generated on November, 0 Chapter : Comment Créer un Type de Champ de Formulaire Personnalisé

170 0 )); ) public function getparent() return 'choice'; public function getname() return 'gender'; L'endroit où est stocké ce fichier n'est pas important - le répertoire Form\Type est seulement une convention. Ici, la valeur retournée par la fonction getparent indique que vous étendez le type de champ choice. Cela signifie que, par défaut, vous héritez de toute la logique et du rendu de l'affichage de ce type de champ. Pour avoir un aperçu de cette logique, jetez un oeil à la classe ChoiceType. Il y a trois méthodes qui sont particulièrement importantes : buildform() - Chaque type de champ possède une méthode buildform, qui est l'endroit où vous configurez et construisez n'importe quel(s) champ(s). Notez que c'est la même méthode que vous utilisez pour initialiser vos formulaires, et cela fonctionne de la même manière ici. buildview() - Cette méthode est utilisée pour définir quelconques variables supplémentaires dont vous aurez besoin lors du rendu de votre champ dans un template. Par exemple, dans ChoiceType, une variable multiple est définie et utilisée dans le template pour définir (ou ne pas définir) l'attribut multiple pour le champ select. Voir Créer un Template pour le Champ pour plus de détails. setdefaultoptions() - Cette méthode définit des options pour votre formulaire qui peuvent être utilisées dans buildform() et buildview(). Il y a beaucoup d'options communes à tous les champs (lisez Type de champ Form), mais vous pouvez créer ici n'importe quelle autre dont vous pourriez avoir besoin. Si vous créez un champ qui se compose de beaucoup de champs, alors assurez-vous de définir 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 méthode finishview(). La méthode getname() retourne un identifiant qui devrait être unique dans votre application. Ce dernier est utilisé dans différents endroits, comme par exemple lorsque vous personnalisez la manière dont votre formulaire sera rendu. Le but de ce champ était d'étendre le type «choice» afin d'activer la sélection du sexe/genre. Cela est accompli en définissant choices par une liste de genres possibles generated on November, 0 Chapter : Comment Créer un Type de Champ de Formulaire Personnalisé 0

171 Créer un Template pour le Champ Chaque type de champ est rendu par un fragment de template, qui est déterminé en partie par la valeur retournée par votre méthode getname(). Pour plus d'informations, voir Que sont les thèmes 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 élément ul. Dans le template de votre thème de formulaire (voir le lien ci-dessus pour plus de détails), créez un bloc gender_widget pour le gérer : Listing - 0 # 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 préfixe du widget qui est utilisé. Dans cet exemple, le nom devrait être gender_widget, si l'on se fie à la valeur retournée 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. Listing - # app/config/config.yml twig: form: resources: - 'AcmeDemoBundle:Form:fields.html.twig' Utiliser le Type de Champ Vous pouvez dès lors utiliser votre type de champ personnalisé en créant tout simplement une nouvelle instance du type dans l'un de vos formulaires: Listing - // src/acme/demobundle/form/type/authortype.php namespace Acme\DemoBundle\Form\Type; use Symfony\Component\Form\AbstractType; generated on November, 0 Chapter : Comment Créer un Type de Champ de Formulaire Personnalisé

172 0 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 très simple. Que se passerait-il si les différents genres étaient stockés dans un fichier de configuration ou dans une base de données? La prochaine section explique comment des types de champ plus complexes peuvent résoudre ce problème. Créer votre Type de Champ en tant que Service Jusqu'ici, cet article a supposé que vous aviez un type de champ personnalisé très simple. Mais si vous avez besoin d'accéder à la configuration, à une connexion à la base de données, ou n'importe quel autre service, alors vous allez vouloir déclarer votre type personnalisé en tant que service. Par exemple, supposons que vous stockiez les paramètres du sexe/genre dans une configuration : Listing - # app/config/config.yml parameters: genders: m: Male f: Female Pour utiliser ce paramètre, définissez votre type de champ personnalisé en tant que service, en injectant la valeur du paramètre genders en tant que premier argument de la fonction construct (qui doit être créée) : Listing - # 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 détails. Assurez vous que l'attribut alias du tag corresponde à la valeur retournée par la méthode getname définie plus tôt. Vous allez voir l'importance de cela dans un moment quand vous utiliserez le type de champ personnalisé. Mais tout d'abord, ajoutez une méthode construct à GenderType, qui reçoit la configuration du sexe/genre: Listing - generated on November, 0 Chapter : Comment Créer un Type de Champ de Formulaire Personnalisé

173 0 0 // 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 paramètres de la configuration et déclaré 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: Listing - 0 // 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 référence grâce à l'alias utilisé dans la configuration de votre service, gender. Amusez-vous! generated on November, 0 Chapter : Comment Créer un Type de Champ de Formulaire Personnalisé

174 Chapter Comment créer une extension de type de formulaire Les types de formulaire personnalisés sont supers si vous avez besoin de types de champ qui font quelque chose de spécifique, comme un sélecteur 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 fonctionnalités 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 :. Vous voulez ajouter une fonctionnalité générique sur plusieurs types (comme ajouter un texte d'«aide» sur tout les types de champ);. Vous voulez ajouter une fonctionnalité spécifique sur un type (comme ajouter une fonctionnalité «téléchargement» 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 métier 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 personnalisés, mais au lieu d'être eux-mêmes des types de champ, ils se branchent sur des types existants. Imaginez que vous devez gérer une entité Media, et que chaque média est associé à un fichier. Votre formulaire Media utilise un type file, mais lorsque vous éditez l'entité, vous voulez avoir un aperçu automatique de l'image affiché à côté du champ. Vous pourriez bien évidemment faire cela en personnalisation la manière dont est affiché le champ dans le template, mais les extensions de type de champ vous permettent de le faire sans répéter le code. generated on November, 0 Chapter : Comment créer une extension de type de formulaire

175 Définir l'extension de type de formulaire Votre première tâche est de créer la classe d'extension de type de formulaire. Appelons-la ImageTypeExtension. Par convention, les extensions de formulaire se trouvent habituellement dans le répertoire Form\Extension de l'un de vos bundles. Lorsque vous créez une extension de type de formulaire, vous pouvez soit implémenter l'interface FormTypeExtensionInterface, soit étendre la classe AbstractTypeExtension. Dans la plupart des cas, il est plus simple d'étendre la classe abstraite: Listing - 0 // 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 * string Le nom du type qui est étendu */ public function getextendedtype() return 'file'; La seule méthode que vous devez implémenter est la fonction getextendedtype. Elle est utilisée pour spécifier le nom du type de formulaire qui est étendu par votre extension. La valeur que vous retournez dans la méthode getextendedtype correspond à la valeur retournée par la méthode getname de la classe de type de formulaire que vous désirez étendre. En plus de la fonction getextendedtype, vous allez probablement vouloir surcharger l'une des méthodes suivantes : buildform() buildview() setdefaultoptions() finishview() Pour plus d'informations sur ce que ces méthodes font, vous pouvez lire l'article du Cookbook Créer des types de champ personnalisés. 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 déclarer comme service en utilisant le tag form.type_extension : Listing generated on November, 0 Chapter : Comment créer une extension de type de formulaire

176 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 métier à l'extension Le but de votre extension est d'afficher de jolies images à côté des champs d'upload de fichier (quand le modèle associé contient des images). Pour atteindre cet objectif, supposons que vous utilisez une approche similaire à celle décrite dans Comment gérer l'upload de fichiers avec Doctrine: vous avez un modèle Média avec une propriété file (qui correspond au champ file) et une propriété path (qui correspond au chemin de l'image dans la base de données): Listing // src/acme/demobundle/entity/media.php namespace Acme\DemoBundle\Entity; use Symfony\Component\Validator\Constraints as Assert; class Media //... /** string Le chemin stocké en base de données */ private $path; /** \Symfony\Component\HttpFoundation\File\UploadedFile */ public $file; //... /** * Retourne l'url de l'image * null string */ public function getwebpath() //... $webpath est l'url complète de l'image, qui est utilisée dans le template return $webpath; Votre classe d'extension de type de formulaire devra faire deux choses pour étendre le type de formulaire file :. Surcharger la méthode setdefaultoptions pour ajouter une option image_path;. Surcharger les méthodes buildform et buildview pour passer l'url de l'image à la vue. generated on November, 0 Chapter : Comment créer une extension de type de formulaire

177 La logique est la suivante : lorsque vous ajoutez un champ de formulaire du type file, vous pourrez alors spécifier une nouvelle option : image_path. Cette option indiquera au champ de fichier comment récupérer l'url de l'image actuelle pour l'afficher dans la vue: Listing // 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 * string Le nom du type étendu */ public function getextendedtype() return 'file'; /** * Ajoute l'option image_path * \Symfony\Component\OptionsResolver\OptionsResolverInterface $resolver */ public function setdefaultoptions(optionsresolverinterface $resolver) $resolver->setoptional(array('image_path')); /** * Passe l'url de l'image à la vue * \Symfony\Component\Form\FormView $view \Symfony\Component\Form\FormInterface $form 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; // définit une variable "image_url" qui sera disponible à l'affichage du champ $view->vars['image_url'] = $imageurl; generated on November, 0 Chapter : Comment créer une extension de type de formulaire

178 Surcharger le fragment de template du widget File Chaque type de champ est affiché grâce à un fragment de template. Ces fragments de templates peuvent être surchargés pour personnaliser l'affichage du formulaire. Pour plus d'informations, vous pouvez consulter l'article Que sont les thèmes 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. Spécifiquement, vous devez surcharger le bloc file_widget pour le faire : Listing - 0 # 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 spécifier explicitement que votre formulaire utilise un thème pour que Symfony utilise le bloc que vous avez surchargé. Pour plus d'informations, lisez Que sont les thèmes 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 spécifier l'option image_path qui sera utilisée pour afficher une image à côté du champ. Par exemple: Listing // 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'; generated on November, 0 Chapter : Comment créer une extension de type de formulaire

179 Lorsque vous afficherez le formulaire, si le modèle sous-jacent a déjà été associé à une image, vous la verrez affichée à côté du champ d'upload. generated on November, 0 Chapter : Comment créer une extension de type de formulaire

180 Chapter Comment réduire la duplication de code avec "inherit_data" New in version.: L'option inherit_data s'appelait virtual avant Symfony.. L'option de champ de formulaire inherit_data peut être très utile si vous avez des champs dupliqués dans différentes entités. Par exemple, imaginez que vous avez deux entités, une Company et un Customer: Listing - 0 // src/acme/hellobundle/entity/company.php namespace Acme\HelloBundle\Entity; class Company private $name; private $website; private $address; private $zipcode; private $city; private $country; Listing - // src/acme/hellobundle/entity/customer.php namespace Acme\HelloBundle\Entity; class Customer private $firstname; private $lastname; generated on November, 0 Chapter : Comment réduire la duplication de code avec "inherit_data" 0

181 0 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 créer deux formulaires pour ces entités, CompanyType et CustomerType: Listing - 0 // 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'); Listing - 0 // 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 dupliqués address, zipcode, city et country dans les deux formulaires, créez un troisième formulaire appelé LocationType pour cela: Listing - 0 // 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) generated on November, 0 Chapter : Comment réduire la duplication de code avec "inherit_data"

182 0 0 $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 intéressante qui est définie, inherit_data. Cette option permet au formulaire d'hériter des données de son formulaire parent. S'il est imbriqué dans le formulaire CompanyType, les champs de LocationType accèderont aux propriétés de l'instance de Company. S'il est imbriqué dans le formulaire CustomerType, il accèdera aux propriétés de l'instance de Customer. Facile non? Au lieu de définir l'option inherit_data dans LocationType, vous pouvez aussi (comme toute option) la passer comme troisième argument de $builder->add(). Finalement, ajoutez le formulaire LocationType à vos deux formulaires originaux: Listing - // 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' )); Listing - // 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 définitions de champs dupliqués dans un formulaire séparé que vous pouvez réutilisé où vous le voulez. generated on November, 0 Chapter : Comment réduire la duplication de code avec "inherit_data"

183 Chapter Comment tester unitairement vos formulaires Le composant Formulaire consiste en objets: un type de formulaire (qui implémente FormTypeInterface ), le Form et la FormView. La seule classe habituellement manipulée par les programmeurs est la classe de type de formulaire qui sert de plan pour le formulaire. Elle est utilisée pour générer 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 préférable de la passer à la FormFactory comme c'est le cas dans une véritable application. C'est simple à initialiser et vous pouvez faire confiance aux composants de Symfony pour les utiliser dans vos tests. Il existe déjà une classe dont vous pouvez tirer parti pour tester de simples types de champs : TypeTestCase. Elle est utilisée pour tester les types de champ de Symfony et vous pouvez l'utiliser pour tester vos propres types. New in version.: La classe TypeTestCase a migré vers Symfony\Component\Form\Test dans Symfony.. Auparavant, la classe était située dans Symfony\Component\Form\Tests\Extension\Core\Type. Selon la manière dont vous avez installé Symfony ou le composant Form, les tests ne seront peut être pas téléchargés. Utilisez l'option --prefer-source de Composer si c'est le cas. Les bases L'implémentation la plus simple de TypeTestCase ressemble à: Listing generated on November, 0 Chapter : Comment tester unitairement vos formulaires

184 0 0 0 // 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', 'test' => 'test', ); $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. Premièrement, vous vérifiez que FormType compile. Cela inclut l'héritage de classe de base, la fonction buildform et la résolution d'options. Ce doit être le premier test que vous écrivez.: Listing - $type = new TestedType(); $form = $this->factory->create($type); Ce test vérifie qu'aucune transformation de données utilisée par le formulaire n'échoue. La méthode issynchronized() est simplement définie à false si une transformation de données lance une exception: Listing - $form->submit($formdata); $this->asserttrue($form->issynchronized());. generated on November, 0 Chapter : Comment tester unitairement vos formulaires

185 Ne testez pas la validation : elle est appliquée par un écouteur qui n'est pas actif dans le cas de test et est liée à la configuration de validation. Au lieu de ça, testez unitairement vos contraintes directement. Ensuite, vérifiez la soumission et le mapping du formulaire. Le test ci-dessous vérifie que tous les champs sont correctement spécifiés: Listing - $this->assertequals($object, $form->getdata()); Enfin, vérifiez la création du FormView. Vous devriez véirifier que tous les widgets que vous voulez afficher sont disponible dans la propriété children: Listing - $view = $form->createview(); $children = $view->children; foreach (array_keys($formdata) as $key) $this->assertarrayhaskey($key, $children); Ajouter un Type dont votre formulaire dépend Votre formulaire peut dépendre d'autres types qui sont définis comme services. Cela ressemblerait à ceci: Listing - // src/acme/testbundle/form/type/testedtype.php //... the buildform method $builder->add('acme_test_child_type'); Pour créer correctement votre formulaire, vous devez rendre le type disponible à la Factory dans votre test. La manière la plus simple est de l'enregistrer manuellement avant de créer le formulaire parent en utilisant la classe PreloadedExtension: Listing // 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(); generated on November, 0 Chapter : Comment tester unitairement vos formulaires

186 $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 liées au formulaire que vous testez mais à ses enfants. Ajouter des Extensions spécifiques Il arrive souvent que vous utilisiez des options qui sont ajoutées 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 levée si vous tentez de l'utilisez dans une classe de test qui dépend d'autres extensions. Vous devez ajouter ces extensions à l'objet Factory: Listing // 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); generated on November, 0 Chapter : Comment tester unitairement vos formulaires

187 //... your tests Tester différents jeux de données Si vous n'êtes pas familier avec le fournisseur de données opportunité de l'utiliser: de PHPUnit, cela peut être une bonne Listing // 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 /** getvalidtestdata */ public function testform($data) //... your test public function getvalidtestdata() return array( array( 'data' => array( 'test' => 'test', 'test' => 'test', ), ), array( 'data' => array(), ), array( 'data' => array( 'test' => null, 'test' => null, ), ), ); Le code ci-dessus lancera votre test trois fois avec trois jeux de données différents. Cela permet de découpler les données de test du test lui-même et de tester facilement plusieurs jeux de données Vous pouvez également passer un autre argument, comme un booléen si le formulaire doit être synchronisé avec le jeu de données ou non etc.. generated on November, 0 Chapter : Comment tester unitairement vos formulaires

188 Chapter Comment configurer des données vierges pour une classe de Formulaire L'option empty_data vous permet de spécifier un jeu de données vierges pour votre classe de formulaire. Ces données vierges seront utilisées si vous soumettez votre formulaire, mais sans appeler la méthode setdata() ou sans avoir initialisé votre formulaire lors de sa création. Par exemple: Listing - 0 public function indexaction() $blog =...; // $blog est passé lors de la création, donc l'option empty_data option // n'est pas nécessaire $form = $this->createform(new BlogType(), $blog); // Aucune données n'est passée, donc l'option empty_data est utilisée pour // obtenir des «données initiales» $form = $this->createform(new BlogType()); Par défaut, empty_data est définie à null. Ou alors, si vous avez spécifié l'option data_class de votre classe de formulaire, l'option empty_data sera définie par défaut comme une nouvelle instance de cette classe. L'instance sera créée en appelant le constructeur sans aucun argument. Si vous voulez surcharger ce comportement par défaut, il y a deux manières de procéder. Possibilité : 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 défaut, l'option data_class appelle le constructeur sans argument: Listing - generated on November, 0 Chapter : Comment configurer des données vierges pour une classe de Formulaire

189 0 0 // 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 dépendance dans la classe BlogType au moment où elle est instanciée, puis nous l'utilisons pour instancier un objet Blog. L'important est que vous pouvez définir empty_data comme étant un «nouvel» objet qui correspond exactement à ce que vous voulez utiliser. Possibilité : Fournir une Closure Il est préférable d'utiliser une closure car l'objet ne sera créé uniquement lorsqu'il sera nécessaire. La closure doit prendre une instance de FormInterface comme premier argument: Listing - 0 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());, )); generated on November, 0 Chapter : Comment configurer des données vierges pour une classe de Formulaire

190 Chapter Comment utiliser la fonction submit() pour gérer les soumissions de formulaires New in version.: La méthode handlerequest() a été ajoutée dans Symfony.. Avec la méthode handlerequest() il est très facile de gérer les soumissions de formulaires: Listing 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(), ));. generated on November, 0 Chapter : Comment utiliser la fonction submit() pour gérer les soumissions de formulaires 0

191 Pour en savoir plus sur cette méthode, lisez Gérer la Soumission des Formulaires. Appeler Form::submit() manuellement New in version.: Avant Symfony., la méthode submit() était connue sous le nom de bind(). Dans certains cas, vous voudrez avoir plus de contrôle sur le moment où votre formulaire est soumis et sur les données qui y sont passées. Au lieu d'utiliser la méthode handlerequest(), passez directement les données soumises à submit() : Listing 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 contiennent des champs imbriqués attendent un tableau en argument de submit(). Vous pouvez également soumettre des champs individuels en appelant submit() directement sur un champ: Listing - $form->get('firstname')->submit('fabien'); generated on November, 0 Chapter : Comment utiliser la fonction submit() pour gérer les soumissions de formulaires

192 Passer une requête à Form::submit() (deprécié) New in version.: Avant Symfony., la méthode submit était connue sous le nom de bind. Avant Symfony., la méthode submit() acceptait un objet Request. Un raccourci pratique de l'exemple précédent: Listing 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 à submit() fonctionne toujours, mais c'est déprécié et sera supprimé dans Symfony.0. Vous devriez plutôt utiliser handlerequest() generated on November, 0 Chapter : Comment utiliser la fonction submit() pour gérer les soumissions de formulaires

193 Chapter 0 Comment utiliser l'option de Champ de Formulaire Virtual L'option de champ de formulaire virtual peut être très utile quand vous avez des champs dupliqués dans différentes entités. Par exemple, imaginez que vous ayez deux entités, une Company et une Customer: Listing 0-0 // src/acme/hellobundle/entity/company.php namespace Acme\HelloBundle\Entity; class Company private $name; private $website; private $address; private $zipcode; private $city; private $country; Listing 0-0 // src/acme/hellobundle/entity/customer.php namespace Acme\HelloBundle\Entity; class Customer private $firstname; private $lastname; private $address; private $zipcode; private $city; private $country; generated on November, 0 Chapter 0: Comment utiliser l'option de Champ de Formulaire Virtual

194 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 créer un CompanyType et un CustomerType: Listing 0-0 // 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'); Listing 0-0 // 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 dupliqués doivent être gérés. Vous pouvez voir ci-dessous un (simple) type de formulaire «location» («lieu» en français): Listing 0-0 // 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'); generated on November, 0 Chapter 0: Comment utiliser l'option de Champ de Formulaire Virtual

195 0 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 entités, donc vous ne pouvez pas lier directement votre LocationType à votre CompanyType ou à votre CustomerType. Mais vous voulez absolument avoir un type de formulaire dédié pour gérer le lieu (rappelez-vous, DRY - Don't Repeat Yourself!). L'option de champ de formulaire virtual est la solution. Vous pouvez définir l'option 'virtual' => true dans la méthode setdefaultoptions() de LocationType et commencer à l'utiliser directement dans les deux types de formulaires initiaux. Voyez le résultat: Listing 0- // CompanyType public function buildform(formbuilderinterface $builder, array $options) $builder->add('foo', new LocationType(), array( 'data_class' => 'Acme\HelloBundle\Entity\Company' )); Listing 0- // CustomerType public function buildform(formbuilderinterface $builder, array $options) $builder->add('bar', new LocationType(), array( 'data_class' => 'Acme\HelloBundle\Entity\Customer' )); Avec l'option «virtual» définie à «false» (comportement par défaut), le composant Form s'attend à ce que chaque objet sous-jacent ait une propriété foo (ou bar) qui soit un objet ou un tableau contenant les quatre champs du lieu. Bien sûr, vous n'avez pas cet objet/tableau dans vos entités et vous ne le voulez pas. Avec l'option «virtual» définie à «true», le composant Form ne s'occupe pas de la propriété foo (ou bar), et à la place «récupère» et «définit» («gets» et «sets» en anglais) les champs du lieu directement sur l'objet sous-jacent. Au lieu de définir 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 troisième argument de $builder->add(). generated on November, 0 Chapter 0: Comment utiliser l'option de Champ de Formulaire Virtual

196 Chapter Comment utiliser Monolog pour écrire des Logs Monolog est une bibliothèque pour PHP. servant à écrire des logs et utilisée par Symfony. Elle est inspirée de la bibliothèque Python LogBook. Utilisation Dans Monolog, chaque «logger» définit un canal de «logging». Chaque canal possède une pile de gestionnaires pour écrire les logs (les gestionnaires peuvent être partagés). 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 défaut est le StreamHandler qui écrit les logs dans un «stream» (par défaut dans le fichier app/logs/prod.log dans l'environnement de production et dans app/logs/dev.log dans l'environnement de développement). Monolog est aussi livré avec un puissant gestionnaire intégré pour le «logging» en environnement de production : le FingersCrossedHandler. Il vous permet de stocker les messages dans un «buffer» («mémoire tampon» en français) 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 contrôleur: Listing -. generated on November, 0 Chapter : Comment utiliser Monolog pour écrire des Logs

197 $logger = $this->get('logger'); $logger->info('nous avons récupéré le logger'); $logger->error('une erreur est survenue'); Utiliser uniquement les méthodes de l'interface LoggerInterface permet de changer l'implémentation du logger sans changer votre code. Utiliser plusieurs gestionnaires Le logger utilise une pile de gestionnaires qui sont appelés successivement. Ceci vous permet de «logger» facilement les messages de plusieurs manières. Listing - 0 # 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 définit une pile de gestionnaires qui vont être appelés dans l'ordre où ils sont définis. Le gestionnaire nommé «file» ne va pas être inclus dans la pile elle-même 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 redéfinir tout le bloc. Il ne peut pas être fusionné car l'ordre importe et une fusion ne permet pas de contrôler ce dernier. Changer la mise en forme Le gestionnaire utilise un Formatter pour mettre en forme une entrée avant de la «logger». Tous les gestionnaires Monolog utilisent une instance de Monolog\Formatter\LineFormatter par défaut mais vous pouvez la remplacer facilement. Votre outil de mise en forme doit implémenter Monolog\Formatter\FormatterInterface. Listing - # app/config/config.yml services:. generated on November, 0 Chapter : Comment utiliser Monolog pour écrire des Logs

198 0 my_formatter: class: Monolog\Formatter\JsonFormatter monolog: handlers: file: type: stream level: debug formatter: my_formatter Ajouter des données supplémentaires dans les messages de log Monolog permet de traiter l'entrée avant de la «logger» afin d'y ajouter des données supplémentaires. Un processeur peut être appliqué pour la pile entière des gestionnaires ou uniquement pour un gestionnaire spécifique. Un processeur est simplement un «callable» recevant l'entrée log en tant que son premier argument. Les processeurs sont configurés en utilisant la balise DIC monolog.processor. Voir la référence à propos de celle-ci. Ajouter un jeton de Session/Requête Parfois il est difficile de dire quelles entrées dans le log appartiennent à quelle session et/ou requête. L'exemple suivant va ajouter un jeton unique pour chaque requête en utilisant un processeur. Listing 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, ); catch (\RuntimeException $e) $this->token = '????????'; $this->token.= '-'. substr(uniqid(), -); $record['extra']['token'] = $this->token; return $record; Listing - generated on November, 0 Chapter : Comment utiliser Monolog pour écrire des Logs

199 # 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: ] 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 déclarer le processeur au niveau du gestionnaire au lieu de le faire globalement. generated on November, 0 Chapter : Comment utiliser Monolog pour écrire des Logs

200 Chapter Comment configurer Monolog pour envoyer les erreurs par Monolog peut être configuré pour envoyer un lorsqu'une erreur se produit dans une application. Pour ce faire, la configuration nécessite quelques gestionnaires «imbriqués» afin d'éviter de recevoir trop d' s. Cette configuration paraît compliquée en premier lieu mais chaque gestionnaire est facilement compréhensible lorsqu'on les analyse un par un. Listing - 0 # 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 protected] to_ [email protected] subject: An Error Occurred! level: debug Le gestionnaire mail est un gestionnaire fingers_crossed, ce qui signifie qu'il est déclenché 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 déclenché seulement pour les erreurs HTTP de code xx. Le paramètre «handler» signifie que la «sortie» («output» en anglais) est alors passée au gestionnaire buffered.. generated on November, 0 Chapter : Comment configurer Monolog pour envoyer les erreurs par 00

201 Si vous souhaitez que les erreurs de niveau 00 et 00 déclenchent un , définissez action_level avec la valeur error à la place de critical. Le gestionnaire buffered garde simplement tous les messages pour une requête et les passe ensuite au gestionnaire «imbriqué» en une seule fois. Si vous n'utilisez pas ce gestionnaire alors chaque message sera envoyé par séparément. C'est donc le gestionnaire swift qui prend le relais. C'est ce dernier qui se charge de vous envoyer l'erreur par . Ses paramètres sont simples : «to» («à» en français), «from» («de» en français) et le sujet. Vous pouvez combiner ces gestionnaires avec d'autres afin que les erreurs continuent d'être loguées sur le serveur en même temps qu'elles sont envoyées par Listing # 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 protected] to_ [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 envoyés par . generated on November, 0 Chapter : Comment configurer Monolog pour envoyer les erreurs par 0

202 Chapter Comment loguer des messages dans différents fichiers New in version.: La possibilité de spécifier des canaux pour un gestionnaire spécifique a été ajoutée au MonologBundle dans Symfony.. 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 différents types de messages de logging. Par défaut, Symfony logue tous les messages dans un fichier unique (peu importe le canal). Transférer un canal vers un gestionnaire différent Maintenant, supposons que vous vouliez loguer le canal doctrine dans un fichier différent. Pour faire cela, créez simplement un nouveau gestionnaire et configurez-le comme ceci : Listing - 0 monolog: handlers: main: type: stream path: /var/log/symfony.log channels:!doctrine doctrine: type: stream path: /var/log/doctrine.log channels: doctrine generated on November, 0 Chapter : Comment loguer des messages dans différents fichiers 0

203 Spécification Yaml Vous pouvez spécifier la configuration de différentes façons : Listing - 0 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 listés ci-dessous elements: [ foo, bar ] channels: type: exclusive # Inclus tous, excepté ceux listés ci-dessous elements: [ foo, bar ] Créer 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 spécifiant dans quel canal le service devrait loguer. En faisant cela, le «logger» qui est injecté dans ce service est préconfiguré pour utiliser le canal que vous avez spécifié. Pour plus d'informations - incluant un exemple complet - lisez «monolog.logger» dans la section «Tags d'injection de Dépendance» du document de référence. En savoir plus grâce au Cookbook Comment utiliser Monolog pour écrire des Logs generated on November, 0 Chapter : Comment loguer des messages dans différents fichiers 0

204 Chapter Comment créer un Collecteur de Données personnalisé Le Profiler de Symfony délègue la collection de données aux collecteurs de données. Certains d'entre eux sont fournis avec Symfony, mais vous pouvez facilement créer le vôtre. Créer un Collecteur de Données Personnalisé Créer un collecteur de données personnalisé est aussi simple que d'implémenter la classe DataCollectorInterface : Listing - 0 interface DataCollectorInterface /** * Collects data for the given Request and Response. * Request $request A Request instance Response $response A Response instance \Exception $exception An Exception instance */ function collect(request $request, Response $response, \Exception $exception = null); /** * Returns the name of the collector. * string The collector name */ function getname(); La méthode getname() doit retourner un nom unique. Ceci est utilisé pour accéder à l'information plus tard (voir Comment utiliser le Profiler dans un test fonctionnel par exemple).. generated on November, 0 Chapter : Comment créer un Collecteur de Données personnalisé 0

205 La méthode collect() est responsable de stocker les données auxquelles elle veut donner accès dans des propriétés locales. Comme le profiler sérialise les instances de collecteur de données, vous ne devriez pas stocker des objets ne pouvant pas être sérialisés (comme des objets PDO), ou vous devrez fournir votre propre méthode serialize(). La plupart du temps, il est pratique d'étendre la classe DataCollector et de remplir la propriété $this- >data (elle se charge de sérialiser la propriété $this->data): Listing - 0 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 Données Personnalisés Pour activer un collecteur de données, ajoutez le comme un service ordinaire dans l'une de vos configurations, et «taggez» le avec data_collector : Listing - 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 données collectées par votre Collecteur de Données dans la barre d'outils de débuggage ou dans le profiler web, créez un template Twig en vous appuyant sur l'exemple suivant : Listing - % extends 'WebProfilerBundle:Profiler:layout.html.twig' %. generated on November, 0 Chapter : Comment créer un Collecteur de Données personnalisé 0

206 0 % block toolbar % # le contenu de la barre d'outils de débuggage web # % endblock % % block head % # si le «panel» du profiler web nécessite des fichiers JS ou CSS spécifiques # % 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 débuggage web et les blocs menu et panel sont utilisés pour ajouter un «panel» au profiler web. Tous les blocs ont accès à l'objet collector. Les templates intégrés utilisent une image encodée en base pour la barre d'outils (<img src="src="data:image/png;base,..."). Vous pouvez facilement calculer la valeur en base d'une image avec ce petit script : echo base_encode(file_get_contents($_server['argv'][]));. 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 : Listing - services: data_collector.your_collector_name: class: Acme\DebugBundle\Collector\Class\Name tags: - name: data_collector, template: "AcmeDebugBundle:Collector:templatename", id: "your_collector_name" generated on November, 0 Chapter : Comment créer un Collecteur de Données personnalisé 0

207 Chapter Comment déclarer un nouveau Format de Requête et un Type Mime Chaque Requête a un «format» (par exemple : html, json), qui est utilisé pour déterminer quel type de contenu retourner dans la Réponse. En fait, le format de la requête, accessible via la méthode getrequestformat(), est utilisé pour définir le type MIME de l'en-tête Content-Type de l'objet Response. En interne, Symfony contient un tableau des formats les plus communs (par exemple : text/ html, application/json). Bien sûr, des types de format MIME additionnels peuvent aisément être ajoutés. Ce document va vous montrer comment vous pouvez ajouter le format jsonp ainsi que le type MIME correspondant. Créez un Listener kernel.request La solution pour définir un nouveau type MIME est de créer une classe qui va «écouter» l'évènement kernel.request «dispatché» («réparti» en français) par le kernel de Symfony. L'évènement kernel.request est dispatché très tôt dans le processus de gestion de la requête de Symfony et vous permet de modifier l'objet requête. Créez la classe suivante, en remplaçant le chemin par un chemin vers un bundle de votre projet: Listing - 0 // 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');. generated on November, 0 Chapter : Comment déclarer un nouveau Format de Requête et un Type Mime 0

208 Déclarer 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 déclarer comme listener en ajoutant le tag kernel.event_listener : Listing - 0 <!-- app/config/config.xml --> <?xml version=".0"?> <container xmlns=" xmlns:xsi=" xsi:schemalocation=" dic/services/services-.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'évènement kernel.request. Vous pouvez aussi déclarer le listener dans une configuration de classe extension (voir Importer la Configuration via les Extensions de Conteneur pour plus d'informations). generated on November, 0 Chapter : Comment déclarer un nouveau Format de Requête et un Type Mime 0

209 Chapter Comment forcer les routes à toujours utiliser HTTPS ou HTTP Quelquefois, vous voulez sécuriser certaines routes et être sûr qu'on y accède toujours via le protocole HTTPS. Le composant «Routing» vous permet de forcer le système de l'uri via la condition requise _scheme : Listing - secure: pattern: /secure defaults: _controller: AcmeDemoBundle:Main:secure requirements: _scheme: https La configuration ci-dessus force la route nommée secure à toujours utiliser HTTPS. Pendant la génération de l'url de secure, et si le système actuel est HTTP, Symfony va automatiquement générer une URL absolue avec HTTPS comme «scheme» : Listing - # Si le «scheme» actuel est HTTPS # path('secure') # génère /secure # Si le «scheme» actuel est HTTP # path('secure') # génère # La condition requise est aussi forcée pour les requêtes entrantes. Si vous essayez d'accéder au chemin /secure avec HTTP, vous serez automatiquement redirigé à la même 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. generated on November, 0 Chapter : Comment forcer les routes à toujours utiliser HTTPS ou HTTP 0

210 Le composant Security fournit une autre façon d'imposer HTTP ou HTTPS via le paramètre requires_channel. Cette méthode alternative est mieux adaptée pour sécuriser une «zone» de votre site web (toutes les URLs dans la zone /admin) ou pour sécuriser les URLs définies dans un bundle tiers. generated on November, 0 Chapter : Comment forcer les routes à toujours utiliser HTTPS ou HTTP 0

211 Chapter Comment autoriser un caractère «/» dans un paramètre de route Parfois, on a besoin de construire des URLs avec des paramètres qui peuvent contenir un slash /. Prenons par exemple la route classique /hello/name. Par défaut, /hello/fabien va correspondre à cette route mais pas /hello/fabien/kris. Cela est dû au fait que Symfony utilise ce caractère comme séparateur 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 défaut, les composants de routage de Symfony requièrent que les paramètres correspondent au pattern de regex suivant : [^/]+. Cela veut dire que tous les caractères sont autorisés sauf /. Vous devez explicitement autoriser le caractère / à faire partie de votre paramètre en spécifiant un pattern de regex plus permissif. Listing - _hello: pattern: /hello/name defaults: _controller: AcmeDemoBundle:Demo:hello requirements: name: ".+" C'est tout! Maintenant, le paramètre name peut contenir le caractère /. generated on November, 0 Chapter : Comment autoriser un caractère «/» dans un paramètre de route

212 Chapter Comment configurer une redirection vers une autre route sans contrôleur personnalisé Ce guide explique comment configurer une redirection d'une route vers une autre sans utiliser de contrôleur spécifique. Supposez qu'il n'existe pas de contrôleur par défaut pour l'url / de votre application et que vous voulez rediriger ces requêtes vers /app. Votre configuration ressemblerait à ceci : Listing - 0 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 la gérer. Ce contrôleur est fourni avec Symfony et propose deux actions pour rediriger les requêtes : urlredirect redirige vers un autre chemin. Vous devez spécifier le paramètre 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 définir le paramètre route avec le nom de la route vers laquelle vous voulez rediriger. Le paramètre permanent indique aux deux méthodes de retourner un code de statut HTTP 0 au lieu du code 0 par défaut.. generated on November, 0 Chapter : Comment configurer une redirection vers une autre route sans contrôleur personnalisé

213 Chapter Comment utiliser des méthodes HTTP autres que GET et POST dans les routes La méthode HTTP d'une requête est l'un des prérequis qui peuvent être vérifiés 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 méthodes GET et POST. Vous pouvez également utiliser d'autres méthodes HTTP de la même manière. Par exemple, si vous avez un billet de blog, alors vous pourriez utiliser le même schéma d'url pour l'afficher, le modifier et le supprimer grâce aux méthodes GET, PUT et DELETE. Listing - 0 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 requêtes PUT et DELETE. Heureusement, Symfony vous fournit de manière simple de contourner cette limitation. En incluant le paramètre _method dans la chaîne de caractères de la requête, ou dans les paramètres d'une requête HTTP, Symfony l'utilisera comme méthode pour trouver une route correspondante. Cela peut être fait très facilement dans les formulaires grâce à un champ caché. Supposons que vous ayez un formulaire pour éditer un billet de blog : Listing - generated on November, 0 Chapter : Comment utiliser des méthodes HTTP autres que GET et POST dans les routes

214 <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 requête soumise correspondra maintenant à la route blog_update et l'action updateaction sera utilisée pour traiter le formulaire. De la même manière, le formulaire de suppression peut être modifié pour ressembler à ceci : Listing - <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 utilisée. generated on November, 0 Chapter : Comment utiliser des méthodes HTTP autres que GET et POST dans les routes

215 Chapter 0 Comment utiliser des paramètres du conteneur de services dans vos routes New in version.: La possibilité d'utiliser ces paramètres dans vos routes est une nouveauté de Symfony.. Listing 0- Parfois, vous pouvez trouver utile de rendre certaines parties de vos routes configurables de façon globale. Par exemple, si vous construisez un site internationalisé, vous commencerez probablement pas une ou deux locales. Vous ajouterez certainement un prérequis dans vos routes pour empêcher l'utilisateur d'accéder à une autre locale que celles que vous supportez. Vous pourriez coder en dure votre prérequis _locale dans toutes vos routes. Mais une meilleure solution sera d'utiliser un paramètre 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 contrôler et définir le paramètre acme_demo.locales quelque part dans votre conteneur : Listing 0- # app/config/config.yml parameters: acme_demo.locales: en es Vous pouvez aussi utiliser un paramètre pour définir votre schéma de route (ou une partie de votre schéma) : Listing 0- generated on November, 0 Chapter 0: Comment utiliser des paramètres du conteneur de services dans vos routes

216 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-0%% deviendra /score-0%. generated on November, 0 Chapter 0: Comment utiliser des paramètres du conteneur de services dans vos routes

217 Chapter Comment charger les utilisateurs depuis la base de données (le fournisseur d'entité) La couche de sécurité est l'un des outils les plus intelligents de Symfony. Il gère deux choses : les procédés d'authentification et d'autorisation. Bien qu'il puisse être difficile de comprendre comment cela fonctionne en interne, le système de sécurité est très flexible et vous permet d'intégrer votre application avec n'importe quel «backend» d'authentification, comme Active Directory, un serveur OAuth ou une base de données. Introduction Cet article traite de l'authentification des utilisateurs à travers une table de base de données gérée par une classe entité Doctrine. Le contenu de cet article du Cookbook est divisé en trois parties. La première partie parle de la conception d'une classe entité Doctrine User ainsi que du fait de la rendre utilisable par la couche de sécurité de Symfony. La deuxième partie décrit comment authentifier facilement un utilisateur avec l'objet Doctrine EntityUserProvider fourni avec le «framework» et quelques éléments de configurations. Finalement, le tutoriel va démontrer comment créer un objet personnalisé EntityUserProvider pour récupérer des utilisateurs depuis la base de données sous certaines conditions personnalisées. Ce tutoriel suppose qu'il existe un bundle Acme\UserBundle démarré et chargé dans le kernel de l'application. Le Modèle de Données Pour cet article du cookbook, le bundle AcmeUserBundle contient une classe entité User avec les champs suivants : id, username, salt, password, et isactive. Le champ isactive précise si oui ou non le compte de l'utilisateur est activé generated on November, 0 Chapter : Comment charger les utilisateurs depuis la base de données (le fournisseur d'entité)

218 Pour faire court, les méthodes «getter» et «setter» de chacun des champs ont été supprimées pour se concentrer sur les méthodes les plus importantes qui proviennent de l'interface UserInterface. Listing // 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 * */ class User implements UserInterface, \Serializable /** */ private $id; /** length=, unique=true) */ private $username; /** length=) */ private $salt; /** length=0) */ private $password; /** length=0, unique=true) */ private $ ; /** type="boolean") */ private $isactive; public function construct() $this->isactive = true; $this->salt = md(uniqid(null, true)); /** */. generated on November, 0 Chapter : Comment charger les utilisateurs depuis la base de données (le fournisseur d'entité)

219 public function getusername() return $this->username; /** */ public function getsalt() return $this->salt; /** */ public function getpassword() return $this->password; /** */ public function getroles() return array('role_user'); /** */ public function erasecredentials() /** \Serializable::serialize() */ public function serialize() return serialize(array( $this->id, )); /** \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 sécurité de Symfony, la classe entité doit implémenter l'interface UserInterface. Cette interface force la classe à implémenter generated on November, 0 Chapter : Comment charger les utilisateurs depuis la base de données (le fournisseur d'entité)

220 les cinq méthodes suivantes : * getroles(), * getpassword(), * getsalt(), * getusername(), * erasecredentials() Pour plus de détails sur chacune d'entre elles, voir UserInterface. L'interface Serializable ainsi que ses méthodes serialize et unserialize ont été ajoutées pour permettre à la classe User d'être sérialisable dans la session. Cela peut ou non être nécessaire en fonction de votre configuration, mais c'est certainement une bonne idée. Seule la propriété id a besoin d'être sérialisée, car la méthode refreshuser() recharge l'utilisateur à chaque requête en utilisant la propriété id. Listing - 0 // 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 détails sur la création des entrées utilisateur et l'encodage de leur mot de passe, lisez le chapitre Encoder les mots de passe. Listing mysql> select * from user; id username salt password hhamon 0ebffbdf0 00f0addebeeba0eebe hhamon@exam jsmith ceaccabf0ca0c0edee 00fad0edcbdebf jsmith@exam maxime cd0bbdcfaedd0 effbdefdefadbbac maxime@exam donald cbfd0c0000cadd0f cbcecfedcc00ddbfdbbf donald@exam rows in set (0.00 sec) La base de données contient désormais quatre utilisateurs avec différents noms d'utilisateurs, s et statuts. La prochaine partie va traiter de l'authentification de l'un de ces utilisateurs grâce au fournisseur d'entité utilisateur Doctrine et à quelques lignes de configuration. Authentifier quelqu'un à travers une base de données Authentifier un utilisateur Doctrine à travers une base de données avec la couche de sécurité de Symfony est vraiment très facile. Tout réside dans la configuration du SecurityBundle stockée 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 comparée et vérifiée avec nos entrées d'entité «User» de la base de données : generated on November, 0 Chapter : Comment charger les utilisateurs depuis la base de données (le fournisseur d'entité) 0

221 Listing # app/config/security.yml security: encoders: Acme\UserBundle\Entity\User: algorithm: sha encode_as_base: false iterations: 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 sha à la classe entité. Cela signifie que Symfony va s'attendre à ce que le mot de passe stocké dans la base de données soit encodé à l'aide de cet algorithme. Pour plus de détails sur la création d'un nouvel objet «User» avec un mot de passe encrypté correctement, lisez la section Encoder les mots de passe du chapitre sur la sécurité. La section providers définit un fournisseur d'utilisateur administrators. Un fournisseur d'utilisateur est une «source» indiquant où les utilisateurs sont chargés 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 données en utilisant le champ unique username. En d'autres termes, cela indique à Symfony comment récupérer l'utilisateur depuis la base de données avant de vérifier la validité du mot de passe. Ce code et cette configuration fonctionnent mais ce n'est pas suffisant pour sécuriser l'application pour des utilisateurs activés. En effet, maintenant, vous pouvez toujours vous authentifier avec maxime. La section suivante explique comment interdire l'accès aux utilisateurs non-activés. Interdire les Utilisateurs non-activés La manière la plus facile d'exclure des utilisateurs non-activés est d'implémenter l'interface AdvancedUserInterface qui se charge de vérifier le statut du compte de l'utilisateur. L'interface AdvancedUserInterface étend l'interface UserInterface 0, donc vous devez simplement utiliser la nouvelle interface dans la classe entité AcmeUserBundle:User afin de bénéficier de comportements d'authentification simples et avancés. L'interface AdvancedUserInterface ajoute quatre méthodes supplémentaires pour valider le statut du compte : isaccountnonexpired() vérifie si le compte de l'utilisateur a expiré, generated on November, 0 Chapter : Comment charger les utilisateurs depuis la base de données (le fournisseur d'entité)

222 isaccountnonlocked() vérifie si l'utilisateur est verrouillé, iscredentialsnonexpired() vérifie si les informations de connexion de l'utilisateur (mot de passe) ont expiré, isenabled() vérifie si l'utilisateur est activé. Pour cet exemple, les trois premières méthodes vont retourner true alors que la méthode isenabled() va retourner la valeur booléenne du champ isactive. Listing // 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'accès est maintenant interdit puisque cet utilisateur n'a pas un compte activé. La prochaine section va se concentrer sur l'implémentation d'un fournisseur d'entité personnalisé pour authentifier un utilisateur avec son nom d'utilisateur ou avec son adresse . 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 comme ils sont tous les deux uniques dans la base de données. Malheureusement, le fournisseur d'entité natif est seulement capable de gérer une propriété unique pour récupérer l'utilisateur depuis la base de données. Pour réaliser ceci, créez un fournisseur d'entité personnalisé qui cherche un utilisateur dont le champ «nom d'utilisateur» ou « » 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 implémente UserProviderInterface. Cette interface est fournie avec trois méthodes. generated on November, 0 Chapter : Comment charger les utilisateurs depuis la base de données (le fournisseur d'entité)

223 à implémenter : loaduserbyusername($username), refreshuser(userinterface $user), et supportsclass($class). Pour plus de détails, lisez UserProviderInterface. Le code ci-dessous montre l'implémentation de UserProviderInterface dans la classe UserRepository: Listing // 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. = : ') ->setparameter('username', $username) ->setparameter(' ', $username) ->getquery(); try // La méthode Query::getSingleResult() lance une exception // s'il n'y a pas d'entrée correspondante aux critères $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,. generated on November, 0 Chapter : Comment charger les utilisateurs depuis la base de données (le fournisseur d'entité)

224 $this->getentityname()); Pour finir l'implémentation, la configuration de la couche de sécurité doit être modifiée pour dire à Symfony d'utiliser le nouveau fournisseur d'entité personnalisé à la place du fournisseur d'entité Doctrine générique. Ceci est facile à réaliser en supprimant le champ property dans la section security.providers.administrators.entity du fichier security.yml. Listing - # app/config/security.yml security: #... providers: administrators: entity: class: AcmeUserBundle:User #... En faisant cela, la couche de sécurité va utiliser une instance de UserRepository et appeler sa méthode loaduserbyusername() pour récupérer un utilisateur depuis la base de données, qu'il ait saisi son nom d'utilisateur ou son adresse . Gérer les rôles via la Base de Données La fin de ce tutoriel se concentre sur comment stocker et récupérer une liste de rôles depuis la base de données. Comme précisé précédemment, lorsque votre utilisateur est «chargé», sa méthode getroles() retourne le tableau contenant ses rôles de sécurité qui doivent lui être assignés. Vous pouvez charger ces données depuis n'importe où - une liste codée en dur et utilisée pour tous les utilisateurs (par exemple : array('role_user')), un tableau Doctrine en tant que propriété nommée roles, ou via une relation Doctrine, comme vous allez le voir dans cette section. Avec une installation typique, vous devriez toujours retourner au moins rôle avec la méthode getroles(). Par convention, un rôle appelé ROLE_USER est généralement retourné. Si vous ne réussissez pas à retourner un quelconque rôle, cela voudrait dire que votre utilisateur n'est pas authentifié du tout. Dans cet exemple, la classe entité AcmeUserBundle:User définit 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 rôle, la méthode précédente getroles() retourne maintenant la liste des groupes reliés: Listing - 0 // src/acme/userbundle/entity/user.php namespace Acme\UserBundle\Entity; use Doctrine\Common\Collections\ArrayCollection; //... class User implements AdvancedUserInterface /** inversedby="users") * generated on November, 0 Chapter : Comment charger les utilisateurs depuis la base de données (le fournisseur d'entité)

225 0 */ private $groups; public function construct() $this->groups = new ArrayCollection(); //... public function getroles() return $this->groups->toarray(); La classe entité AcmeUserBundle:Group définit trois champs de table (id, name et role). Le champ unique role contient le nom du rôle utilisé par la couche de sécurité de Symfony pour sécuriser des parties de l'application. La chose la plus importante à noter est que la classe entité AcmeUserBundle:Group implémente l'interface RoleInterface qui la force à avoir une méthode getrole(): Listing // 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; /** */ class Group implements RoleInterface /** type="integer") */ private $id; /** type="string", length=0) */ private $name; /** type="string", length=0, unique=true) */ private $role; /** mappedby="groups") */ private $users;. generated on November, 0 Chapter : Comment charger les utilisateurs depuis la base de données (le fournisseur d'entité)

226 0 0 public function construct() $this->users = new ArrayCollection(); //... getters and setters for each property /** RoleInterface */ public function getrole() return $this->role; Pour améliorer les performances et éviter le «lazy loading» de groupes lors de la récupération d'un utilisateur depuis le fournisseur d'entité personnalisé, la meilleure solution est d'effectuer une jointure avec la relation des groupes dans la méthode UserRepository::loadUserByUsername(). Cela va récupérer l'utilisateur ainsi que ses rôles/groupes associés avec une requête unique: Listing // 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. = : ') ->setparameter('username', $username) ->setparameter(' ', $username) ->getquery(); //... //... La méthode QueryBuilder::leftJoin() joint et cherche les groupes liés depuis la classe modèle AcmeUserBundle:User lorsqu'un utilisateur est récupéré grâce à son adresse ou à son nom d'utilisateur. generated on November, 0 Chapter : Comment charger les utilisateurs depuis la base de données (le fournisseur d'entité)

227 Chapter Comment ajouter la fonctionnalité de login «Se souvenir de moi» Une fois qu'un utilisateur est authentifié, ses informations de connexion sont généralement stockées dans la session. Cela signifie que lorsque la session se termine, cet utilisateur sera déconnecté et devra fournir de nouveau ses informations de login la prochaine fois qu'il voudra accéder à l'application. Vous pouvez laisser aux utilisateurs la possibilité de choisir de rester connecté plus longtemps que la durée d'une session en utilisant un cookie avec l'option pare-feu remember_me. Le pare-feu a besoin d'avoir une clé secrète configurée, qui est utilisée pour encrypter le contenu du cookie. Il possède aussi plusieurs options avec des valeurs par défaut qui sont détaillées ici : Listing - # app/config/security.yml firewalls: main: remember_me: key: "%secret%" lifetime: 000 # jours en secondes path: / domain: ~ # Prend la valeur par défaut du domaine courant depuis $_SERVER C'est une bonne idée 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 manière 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 défini lorsque la «checkbox» sera cochée et que l'utilisateur se sera connecté avec succès. Donc, votre formulaire de login spécifique pourrait au final ressembler à cela : Listing - # 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 " /> generated on November, 0 Chapter : Comment ajouter la fonctionnalité de login «Se souvenir de moi»

228 0 <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'accéder à certaines ressources Lorsque l'utilisateur retourne sur votre site, il ou elle est authentifié automatiquement en se basant sur les informations stockées dans le cookie «remember me». Cela permet à l'utilisateur d'accéder à des ressources protégées 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'accéder à 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 sécurité fournit une manière simple de faire cela. En plus des rôles qui leurs sont explicitement assignés, les utilisateurs possèdent automatiquement l'un des rôles suivants dépendant de la manière dont ils sont authentifiés : IS_AUTHENTICATED_ANONYMOUSLY - automatiquement assigné à un utilisateur qui se trouve dans une zone protégée du site par un pare-feu mais qui ne s'est pas connecté/logué. Cela est possible uniquement si l'accès 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 rôles pour contrôler l'accès en plus des autres rôles explicitement assignés. Si vous avez le rôle IS_AUTHENTICATED_REMEMBERED, alors vous avez aussi le rôle IS_AUTHENTICATED_ANONYMOUSLY. Si vous avez le rôle IS_AUTHENTICATED_FULLY, alors vous possédez aussi les deux autres rôles. En d'autres termes, ces rôles représentent trois niveaux croissants de «force» d'authentification. Vous pouvez utiliser ces rôles additionnels pour effectuer un contrôle d'une granularité plus fine sur l'accès à 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 détails de son compte. Vous pouvez effectuer ceci en sécurisant certaines actions d'un contrôleur spécifique en utilisant ces rôles. L'action «edit» dans le contrôleur pourrait être sécurisée en utilisant le contexte du service. Dans l'exemple suivant, l'action est autorisée seulement si l'utilisateur possède le rôle IS_AUTHENTICATED_FULLY. Listing - generated on November, 0 Chapter : Comment ajouter la fonctionnalité de login «Se souvenir de moi»

229 0 //... 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 qui peut sécuriser votre contrôleur en utilisant des annotations : Listing - use JMS\SecurityExtraBundle\Annotation\Secure; /** */ public function editaction($name) //... Si vous aviez aussi un contrôle d'accès dans votre configuration de sécurité qui requiert qu'un utilisateur possède un rôle ROLE_USER afin d'accéder à n'importe quelle partie de la zone «account», alors vous auriez la situation suivante : Si un utilisateur non-authentifié (ou authentifié anonymement) essaye d'accéder à 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 reçoive le rôle ROLE_USER par votre configuration, ce dernier aura le rôle IS_AUTHENTICATED_FULLY et sera capable d'accéder à n'importe quelle page de la section «account», incluant l'action editaction du contrôleur. Enfin, supposons que la session de l'utilisateur se termine ; quand ce dernier retourne sur le site, il sera capable d'accéder à chaque page de la partie «account» - exceptée la page «edit» - sans être obligé de se ré-authentifier. Cependant, quand il essaye d'accéder à l'action editaction du contrôleur, il sera obligé de se ré-authentifier, puisqu'il n'est pas (encore) totalement authentifié. Pour plus d'informations sur la sécurisation de services ou de méthodes de cette manière, lisez Comment sécuriser n'importe quel service ou méthode de votre application.. generated on November, 0 Chapter : Comment ajouter la fonctionnalité de login «Se souvenir de moi»

230 Chapter Comment implémenter votre propre Voteur pour ajouter des adresses IP sur une liste noire Le composant de sécurité de Symfony fournit plusieurs couches pour authentifier les utilisateurs. L'une de ces couches est appelée un voteur. Un voteur est une classe dédiée qui vérifie si l'utilisateur possède les droits nécessaires pour se connecter à l'application. Par exemple, Symfony fournit une couche qui vérifie si l'utilisateur est totalement authentifié ou s'il possède quelques rôles attendus. Il est parfois utile de créer un voteur personnalisé pour gérer un cas spécifique non-géré par le framework. Dans cette section, vous allez apprendre comment créer un voteur qui vous permettra d'ajouter des utilisateurs sur une liste noire suivant leur adresse IP. L'Interface Voter Un voteur personnalisé doit implémenter VoterInterface, qui requiert les trois méthodes suivantes : Listing - interface VoterInterface function supportsattribute($attribute); function supportsclass($class); function vote(tokeninterface $token, $object, array $attributes); La méthode supportsattribute() est utilisée pour vérifier si le voteur supporte l'attribut de l'utilisateur donné (c-a-d un rôle, une ACL, etc.). La méthode supportsclass() est utilisée pour vérifier si le voteur supporte la classe du token de l'utilisateur courant. La méthode vote() doit implémenter la logique métier qui vérifie si oui ou non un utilisateur est autorisé à accéder à l'application. Cette méthode doit retourner l'une des valeurs suivantes : VoterInterface::ACCESS_GRANTED: L'utilisateur est autorisé à accéder à l'application. generated on November, 0 Chapter : Comment implémenter votre propre Voteur pour ajouter des adresses IP sur une liste noire 0

231 VoterInterface::ACCESS_ABSTAIN: Le voteur ne peut pas décider si l'utilisateur est oui ou non autorisé à accéder à l'application VoterInterface::ACCESS_DENIED: L'utilisateur n'est pas autorisé à accéder à l'application Dans cet exemple, vous allez vérifier 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'accès, et non pas de l'autoriser. Créer 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 : Listing // 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 vérifier 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 sécurité. Cela peut être effectué facilement à l'aide du conteneur de service. generated on November, 0 Chapter : Comment implémenter votre propre Voteur pour ajouter des adresses IP sur une liste noire

232 Listing - Déclarer le Voteur comme service Pour injecter le voteur dans la couche de sécurité, vous devez le déclarer 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, [...,...]] public: false tags: - name: security.voter Soyez sûr 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 définition de services en général, lisez le chapitre Service Container. Changer la stratégie de décision d'accès Afin que votre nouveau voteur soit utilisé, vous devez changer la stratégie de décision d'accès par défaut, qui d'habitude autorise l'accès si n'importe quel voteur autorise l'accès. Dans ce cas, choisissez la stratégie unanimous. Contrairement à la stratégie par défaut affirmative, avec la stratégie unanimous, si seulement un voteur refuse l'accès (par exemple : le ClientIpVoter), alors l'accès n'est pas autorisé pour l'utilisateur final. Pour faire cela, surchargez la section par défaut access_decision_manager du fichier de configuration de votre application avec le code suivant. Listing - # 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 décision de savoir si oui ou non un utilisateur devrait avoir accès, le nouveau voteur va refuser l'accès à quiconque possédant une IP qui se trouve dans la liste noire. generated on November, 0 Chapter : Comment implémenter votre propre Voteur pour ajouter des adresses IP sur une liste noire

233 Chapter Comment utiliser les Access Control Lists (ACLs) («liste de contrôle d'accès» en français) Dans les applications complexes, vous allez souvent rencontrer le problème que les décisions d'accès ne peuvent pas uniquement se baser sur la personne (Token) qui demande l'accès, mais qu'elles impliquent aussi un objet domaine auquel l'accès est demandé. C'est là où le système des ACL intervient. Imaginez que vous êtes en train de créer un système 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-même être capable d'éditer tous les commentaires. Dans ce scénario, Comment (commentaire) serait l'objet auquel vous souhaitez restreindre l'accès. Vous pouvez envisager plusieurs approches pour accomplir cela en utilisant Symfony ; les deux approches basiques sont (liste non-exhaustive) : Forcer la sécurité dans vos méthodes métier : cela signifie garder une référence dans chaque Comment de tous les utilisateurs qui ont accès, et alors de comparer ces utilisateurs avec le Token fourni. Forcer la sécurité avec des rôles : avec cette approche, vous ajouteriez un rôle pour chaque objet Comment, i.e. ROLE_COMMENT_, ROLE_COMMENT_, etc. Les deux approches sont parfaitement valides. Cependant, elles associent votre logique d'autorisation à votre code métier, ce qui rend le tout moins réutilisable ailleurs, et qui augmente aussi la difficulté d'effectuer des tests unitaires. En outre, vous pourriez rencontrer des problèmes de performance si beaucoup d'utilisateurs accédaient à un même et unique objet domaine. Heureusement, il y a une meilleure façon de faire, que vous allez découvrir maintenant. Initialisation («Bootstrapping» en anglais) Maintenant, avant que vous puissiez finalement passer à l'action, vous avez besoin d'effectuer certaines initialisations. Premièrement, vous devez configurer la connexion que le système d'acl est supposé utiliser : Listing - generated on November, 0 Chapter : Comment utiliser les Access Control Lists (ACLs) («liste de contrôle d'accès» en français)

234 # app/config/security.yml security: acl: connection: default Le système ACL requiert qu'une connexion DBAL Doctrine (utilisable par défaut) ou qu'une connexion ODM Doctrine (utilisable avec MongoDBAclBundle ) soit configurée. 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 vôtre. Une fois la connexion configurée, vous devez importer la structure de la base de données. Heureusement, il existe une tâche pour cela. Exécutez simplement la commande suivante : Listing - $ php app/console init:acl Démarrage Revenez à ce petit exemple du début et implémentez ses ACLs. Créer un ACL, et ajouter un ACE Listing // 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(); // préparation du $form et liaison (bind) des données if ($form->isvalid()) $entitymanager = $this->get('doctrine.orm.default_entity_manager'); $entitymanager->persist($comment); $entitymanager->flush(); // création de l'acl $aclprovider = $this->get('security.acl.provider'); $objectidentity = ObjectIdentity::fromDomainObject($comment);. generated on November, 0 Chapter : Comment utiliser les Access Control Lists (ACLs) («liste de contrôle d'accès» en français)

235 0 0 $acl = $aclprovider->createacl($objectidentity); // retrouve l'identifiant de sécurité de l'utilisateur actuellement connecté $securitycontext = $this->get('security.context'); $user = $securitycontext->gettoken()->getuser(); $securityidentity = UserSecurityIdentity::fromAccount($user); // donne accès au propriétaire $acl->insertobjectace($securityidentity, MaskBuilder::MASK_OWNER); $aclprovider->updateacl($acl); Il y a plusieurs décisions d'implémentation 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 implémentations de ObjectIdentityInterface. Cette étape additionnelle d'indirection vous permet de travailler avec les ACLs même si vous n'avez pas d'instance d'objet domaine sous la main. Cela va être extrêmement utile si vous voulez vérifier les permissions pour un grand nombre d'objets sans avoir à les désérialiser. L'autre partie intéressante est l'appel à ->insertobjectace(). Dans l'exemple, vous accordez à l'utilisateur connecté un accès propriétaire au Comment. Le MaskBuilder::MASK_OWNER est un masque binaire prédéfini ; ne vous inquiétez pas, le constructeur de masque va abstraire la plupart des détails techniques, mais en utilisant cette technique vous pouvez stocker plein de permissions différentes dans une même ligne de base de données ; ce qui vous offre un avantage considérable au niveau performance. L'ordre dans lequel les ACEs sont vérifiées est important. En tant que règle générale, vous devriez placer les entrées les plus spécifiques au début. Vérification des Accès Listing - 0 // 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(); //... récupérez le bon objet «comment», et éditez-le ici generated on November, 0 Chapter : Comment utiliser les Access Control Lists (ACLs) («liste de contrôle d'accès» en français)

236 0 Dans cet exemple, vous vérifiez que l'utilisateur possède la permission EDIT. En interne, Symfony fait correspondre la permission avec plusieurs masques binaires, et vérifie si l'utilisateur possède l'un d'entre eux. Vous pouvez définir jusqu'à permissions de base (dépendant du PHP de votre OS, cela pourrait varier entre 0 et ). De plus, vous pouvez aussi définir des permissions cumulées. Permissions Cumulées 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 opération 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 créer des masques binaires facilement en combinant plusieurs permissions de base : Listing - $builder = new MaskBuilder(); $builder ->add('view') ->add('edit') ->add('delete') ->add('undelete') ; $mask = $builder->get(); // int() Ce masque binaire représenté par un entier peut ainsi être utilisé pour accorder à un utilisateur les permissions de base que vous avez ajoutées ci-dessus : Listing - $identity = new UserSecurityIdentity('johannes', 'Acme\UserBundle\Entity\User'); $acl->insertobjectace($identity, $mask); L'utilisateur a désormais le droit de lire, éditer, supprimer, et annuler une suppression sur des objets. generated on November, 0 Chapter : Comment utiliser les Access Control Lists (ACLs) («liste de contrôle d'accès» en français)

237 Chapter Comment utiliser les concepts d'acl avancés Le but de ce chapitre est de donner une vision plus profonde du système des ACL («Liste de Contrôle d'accès» en français), et aussi d'expliquer certaines décisions prises en ce qui concerne sa conception. Concepts d'architecture Les capacités de sécurité de l'instance objet de Symfony sont basées sur le concept d'une «Access Control List». Chaque instance d'un objet domaine a sa propre ACL. L'instance ACL contient une liste détaillée des «Access Control Entries» (ACEs ou «Entrées de Contrôle d'accès» en français) qui sont utilisées pour prendre les décisions d'accès. Le système d'acl de Symfony se concentre sur deux objectifs : fournir un moyen de récupérer de manière efficace un grand nombre d'acls/aces pour vos objets domaine, et de les modifier ; fournir un moyen de prendre les décisions facilement quant à savoir si une personne est autorisée à effectuer une action sur un objet domaine ou non. Comme spécifié dans le premier point, l'une des principales facultés du système ACL de Symfony est de fournir une manière très performante de récupérer des ACLs/ACEs. Ceci est extrêmement important sachant que chaque ACL pourrait avoir plusieurs ACEs, et hériter d'une autre ACL à la manière d'une structure en arbre. Donc, nous ne nous servons pas d'un ORM spécifique, mais l'implémentation par défaut intéragit avec votre connexion en utilisant directement le DBAL de Doctrine. Identités d'objet Le système ACL est complètement découplé de vos objets domaine. Ils ne doivent même pas être stockés dans la même base de données, ou sur le même serveur. Pour pouvoir accomplir ce découplage, vos objets sont représentés dans le système ACL par des objets d'identité d'objet. Chaque fois que vous voulez récupérer une ACL pour un objet domaine, le système ACL va d'abord créer une identité d'objet pour votre objet domaine, et va ensuite passer cette identité d'objet au fournisseur d'acl pour un traitement ultérieur. generated on November, 0 Chapter : Comment utiliser les concepts d'acl avancés

238 Identités de Sécurité Ceci est similaire à l'identité d'objet, mais représente un utilisateur, ou un rôle dans votre application. Chaque rôle ou utilisateur possède sa propre identité de sécurité. Structure de Table dans la Base de Données L'implémentation par défaut utilise cinq tables de base de données qui sont listées ci-dessous. Les tables sont classées 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 identités de sécurité (SID) qui détiennent les ACEs. L'implémentation par défaut est fournie avec deux identités de sécurité : RoleSecurityIdentity, et UserSecurityIdentity ; acl_classes : Cette table fait correspondre les noms de classe avec un id unique qui peut être référencé depuis d'autres tables ; acl_object_identities : Chaque ligne dans cette table représente une unique instance d'objet domaine ; acl_object_identity_ancestors : Cette table nous autorise à déterminer tous les ancêtres d'une ACL de manière très 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 façon significative les performances. Portée des «Access Control Entries» Les entrées de contrôle d'accès peuvent avoir différentes portées dans lesquelles elles s'appliquent. Dans Symfony, il existe principalement deux portées différentes : Portée de la Classe : Ces entrées s'appliquent à tous les objets ayant la même classe. Portée de l'objet : Ceci est la portée utilisée dans le chapitre précédent, et elle s'applique uniquement à un objet spécifique. Parfois, vous aurez besoin d'appliquer une ACE uniquement sur le champ spécifique 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 problème commun, deux sous-portées supplémentaires ont été ajoutées : Portée d'un Champ de Classe : Ces entrées s'appliquent à tous les objets ayant la même classe, mais uniquement à un champ spécifique de ces objets. Portée d'un Champ d'objet : Ces entrées s'appliquent à un objet spécifique, et uniquement à un champ spécifique de cet objet. Décisions de pré-autorisation Pour les décisions de pré-autorisation, que ce soit des décisions prises avant quelconque méthode ou bien une action sécurisée qui est invoquée, le service «AccessDecisionManager» est utilisé. Ce service est également utilisé pour connaître les décisions d'autorisation basées sur des rôles. Comme les rôles, le système d'acl ajoute plusieurs nouveaux attributs qui pourraient être utilisés pour vérifier différentes permissions. generated on November, 0 Chapter : Comment utiliser les concepts d'acl avancés

239 Table de Permission Intégrée Attribut Signification Masques Binaires VIEW EDIT CREATE DELETE UNDELETE OPERATOR MASTER OWNER Si quelqu'un est autorisé à voir l'objet domaine. Si quelqu'un est autorisé à effectuer des changements sur l'objet domaine. Si quelqu'un est autorisé à créer l'objet domaine. Si quelqu'un est autorisé à supprimer l'objet domaine. Si quelqu'un est autorisé à restaurer un objet domaine précédemment supprimé. Si quelqu'un est autorisé à effectuer toutes les actions ci-dessus. 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. Si quelqu'un possède l'objet domaine. Un propriétaire peut effectuer n'importe laquelle des actions ci-dessus et affecter les permissions master et owner. VIEW, EDIT, OPERATOR, MASTER, or OWNER EDIT, OPERATOR, MASTER, or OWNER CREATE, OPERATOR, MASTER, or OWNER DELETE, OPERATOR, MASTER, or OWNER UNDELETE, OPERATOR, MASTER, or OWNER OPERATOR, MASTER, or OWNER MASTER, or OWNER OWNER Attributs de Permission vs. Masques Binaires de Permission Les attributs sont utilisés par l'«accessdecisionmanager», tout comme les rôles. Souvent, ces attributs représentent en fait une agrégation de masques binaires. D'un autre côté, les masques binaires sous forme d'entier sont utilisés par le système d'acl en interne pour stocker de manière efficace les permissions de vos utilisateurs dans la base de données, et pour effectuer des vérifications en utilisant des opérations sur les masques binaires extrêmement rapides. Extensibilité La table de permissions ci-dessus n'est en rien statique, et pourrait théoriquement être complètement remplacée. Cependant, elle devrait couvrir la plupart des problèmes que vous pourriez rencontrer, et pour des raisons d'intéropérabilité avec d'autres bundles, vous êtes encouragé à conserver les significations initialement prévues pour ces permissions. Décisions de post-autorisation Les décisions de post-autorisation sont effectuées après qu'une méthode sécurisée a été invoquée, et impliquent typiquement l'objet domaine qui est retourné par une telle méthode. Après invocations, les fournisseurs permettent aussi de modifier, ou de filtrer l'objet domaine avant qu'il ne soit retourné. generated on November, 0 Chapter : Comment utiliser les concepts d'acl avancés

240 A cause de limitations actuelles du langage PHP, il n'y a pas de fonctionnalités de post-autorisation implémentées dans le composant coeur «Security». Néanmoins, il y a un bundle expérimental appelé JMSSecurityExtraBundle qui ajoute ces fonctionnalités. Lisez sa documentation pour avoir plus d'informations pour comprendre comment ceci est réalisé. Processus pour connaître les décisions d'autorisation La classe ACL fournit deux méthodes pour déterminer si une identité de sécurité possède les masques binaires requis, isgranted et isfieldgranted. Lorsque l'acl reçoit une requête d'autorisation à travers l'une de ces méthodes, elle délègue cette requête à une implémentation de «PermissionGrantingStrategy». Cela vous permet de remplacer la manière dont les décisions d'accès sont atteintes sans modifier la classe ACL elle-même. La «PermissionGrantingStrategy» vérifie en premier toutes les ACEs de vos portées d'objet ; si aucune n'est applicable, les ACEs de vos portées de classe vont être vérifiées, et si aucune n'est applicable, alors le processus va être répété avec les ACEs du parent de l'acl. Si aucun parent de l'acl n'existe, une exception sera lancée.. generated on November, 0 Chapter : Comment utiliser les concepts d'acl avancés 0

241 Chapter Comment forcer HTTPS ou HTTP pour des URLs Différentes Vous pouvez forcer certaines parties de votre site à utiliser le protocole HTTPS dans la configuration de la sécurité. Cela s'effectue grâce aux règles access_control en utilisant l'option requires_channel. Par exemple, si vous voulez forcer toutes les URLs commençant par /secure à utiliser HTTPS, alors vous pourriez utiliser la configuration suivante : Listing - access_control: - path: ^/secure roles: ROLE_ADMIN requires_channel: https Le formulaire de login lui-même a besoin d'autoriser un accès anonyme, sinon les utilisateurs seront incapables de s'authentifier. Pour le forcer à utiliser HTTPS vous pouvez toujours utiliser les règles de access_control en vous servant du rôle IS_AUTHENTICATED_ANONYMOUSLY : Listing - access_control: - path: ^/login roles: IS_AUTHENTICATED_ANONYMOUSLY requires_channel: https Il est aussi possible de spécifier l'utilisation d'https dans la configuration de routage, lisez Comment forcer les routes à toujours utiliser HTTPS ou HTTP pour plus de détails. generated on November, 0 Chapter : Comment forcer HTTPS ou HTTP pour des URLs Différentes

242 Chapter Comment personnaliser votre formulaire de login Utiliser un formulaire de login est une méthode classique et flexible pour gérer l'authentification dans Symfony. Quasiment chaque aspect du formulaire de login peut être personnalisé. La configuration complète par défaut est détaillée dans la prochaine section. Référence de Configuration du formulaire de login Listing # 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 défini à 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 défaut, le formulaire de login *doit* être un POST, # et pas un GET post_only: true # options de redirection lorsque le login a réussi (vous # pouvez en lire plus ci-dessous) always_use_default_target_path: false default_target_path: / target_path_parameter: _target_path generated on November, 0 Chapter : Comment personnaliser votre formulaire de login

243 0 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 après un succès Vous pouvez changer l'url de redirection après que le formulaire de login a été soumis avec succès via plusieurs options de configuration. Par défaut, le formulaire va rediriger l'utilisateur vers l'url qu'il a demandée (c-a-d l'url qui a déclenchée le formulaire de login qui est montré). Par exemple, si l'utilisateur a demandé alors après, il sera éventuellement redirigé vers dans le cas d'un succès de connexion. Cela est effectué en stockant l'url demandée dans la session. Si aucune URL n'est présente 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 défaut, qui est / (c-a-d. la page d'accueil) par défaut. Vous pouvez changer ce comportement de différentes façons. Comme précisé, par défaut, l'utilisateur est redirigé vers la page qu'il avait demandée à la base. Quelquefois, cela peut poser des problèmes, comme par exemple si une requête AJAX en arrièreplan «apparaît» comme étant la dernière URL visitée, redirigeant l'utilisateur vers cette dernière. Pour plus d'informations sur comment contrôler ce comportement, lisez Comment changer le comportement par défaut du chemin cible. Changer la page par défaut Tout d'abord, la page par défaut peut être définie (c-a-d la page vers laquelle l'utilisateur est redirigée si aucune page n'avait été précédemment stockée dans la session). Pour la définir en tant que /admin, utilisez la configuration suivante : Listing - # app/config/security.yml security: firewalls: main: form_login: #... default_target_path: /admin Maintenant, quand aucune URL n'est définie dans la session, l'utilisateur va être envoyé vers /admin. generated on November, 0 Chapter : Comment personnaliser votre formulaire de login

244 Toujours rediriger vers la page par défaut Vous pouvez faire en sorte que les utilisateurs soient toujours redirigés vers la page par défaut sans tenir compte de l'url qu'ils avaient demandée en définissant l'option always_use_default_target_path à «true» : Listing - # app/config/security.yml security: firewalls: main: form_login: #... always_use_default_target_path: true Utiliser l'url référante Dans le cas où aucune URL n'a été stockée dans la session, vous pourriez souhaiter essayer d'utiliser HTTP_REFERER à la place, comme ce dernier sera souvent identique. Vous pouvez effectuer cela en définissant use_referer à «true» (par défaut la valeur est «false») : Listing - # app/config/security.yml security: firewalls: main: form_login: #... use_referer: true New in version.: Depuis la version., si le référant est égal à l'option login_path, l'utilisateur sera redirigé vers le default_target_path. Contrôler l'url de redirection depuis le formulaire Vous pouvez aussi surcharger le chemin vers lequel l'utilisateur est redirigé via le formulaire lui-même en incluant un champ caché avec le nom _target_path. Par exemple, pour rediriger vers l'url définie par une route account, utilisez ce qui suit : Listing - 0 # 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> generated on November, 0 Chapter : Comment personnaliser votre formulaire de login

245 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 même changer le nom du champ caché du formulaire en changeant l'option target_path_parameter. Listing - # 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 réussit à se connecter, vous pouvez aussi définir l'url vers laquelle l'utilisateur devrait être redirigé après un échec lors de la phase de login (par exemple : un nom d'utilisateur ou mot de passe non-valide a été soumis). Par défaut, l'utilisateur est redirigé vers le formulaire de login lui-même. Vous pouvez définir une URL différente en utilisant la configuration suivante : Listing - # app/config/security.yml security: firewalls: main: form_login: #... failure_path: /login_failure generated on November, 0 Chapter : Comment personnaliser votre formulaire de login

246 Chapter Comment sécuriser n'importe quel service ou méthode de votre application Dans le chapitre sur la sécurité, vous pouvez voir comment sécuriser un contrôleur en récupérant le service security.context depuis le Conteneur de Service et en vérifiant le rôle actuel de l'utilisateur: Listing - 0 //... 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 sécuriser n'importe quel service d'une manière similaire en lui injectant le service security.context. Pour une introduction générale sur l'injection de dépendances dans un service, voyez le chapitre Service Container du book. Par exemple, supposons que vous ayez une classe NewsletterManager qui envoie des s et que vous souhaitiez restreindre son utilisation aux utilisateurs ayant un rôle nommé ROLE_NEWSLETTER_ADMIN uniquement. Avant l'ajout de cette sécurité, la classe ressemble à ceci : Listing - // src/acme/hellobundle/newsletter/newslettermanager.php namespace Acme\HelloBundle\Newsletter; class NewsletterManager public function sendnewsletter() // c'est ici que vous effectuez le travail generated on November, 0 Chapter : Comment sécuriser n'importe quel service ou méthode de votre application

247 0 //... Votre but est de vérifier le rôle de l'utilisateur lorsque la méthode sendnewsletter() est appelée. La première é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 vérification de sécurité, nous avons ici un candidat idéal pour l'injection via le constructeur, qui garantit que l'objet du contexte de sécurité sera disponible dans la classe NewsletterManager: Listing - 0 namespace Acme\HelloBundle\Newsletter; use Symfony\Component\Security\Core\SecurityContextInterface; class NewsletterManager protected $securitycontext; public function construct(securitycontextinterface $securitycontext) $this->securitycontext = $securitycontext; //... Listing - 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 dès lors être utilisé pour effectuer la vérification de sécurité lorsque la méthode sendnewsletter() est appelée: Listing - 0 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() generated on November, 0 Chapter : Comment sécuriser n'importe quel service ou méthode de votre application

248 0 //... if (false === $this->securitycontext->isgranted('role_newsletter_admin')) throw new AccessDeniedException(); //... Si l'utilisateur actuel ne possède pas le rôle ROLE_NEWSLETTER_ADMIN, il lui sera demandé de se connecter. Sécuriser des méthodes en utilisant des annotations Vous pouvez aussi sécuriser des appels de méthodes dans n'importe quel service avec des annotations en utilisant le bundle facultatif JMSSecurityExtraBundle. Ce bundle n'est pas inclus dans la Distribution Standard de Symfony, mais vous pouvez choisir de l'installer. Pour activer la fonctionnalité des annotations, taggez le service que vous voulez sécuriser avec le tag security.secure_service (vous pouvez aussi activer automatiquement cette fonctionnalité pour tous les services, voir l'encadré ci-dessous) : Listing - # src/acme/hellobundle/resources/config/services.yml #... services: newsletter_manager: #... tags: - name: security.secure_service Vous pouvez ainsi parvenir aux mêmes résultats que ci-dessus en utilisant une annotation: Listing - 0 namespace Acme\HelloBundle\Newsletter; use JMS\SecurityExtraBundle\Annotation\Secure; //... class NewsletterManager /** */ public function sendnewsletter() //... // generated on November, 0 Chapter : Comment sécuriser n'importe quel service ou méthode de votre application

249 Les annotations fonctionnent car une classe proxy est créée pour votre classe qui effectue les vérifications de sécurité. Cela signifie que vous pouvez utiliser les annotations sur des méthodes «public» ou «protected», mais que vous ne pouvez pas les utiliser avec des méthodes «private» ou avec des méthodes marquées comme «final» Le JMSSecurityExtraBundle vous permet aussi de sécuriser les paramètres et les valeurs retournées par les méthodes. Pour plus d'informations, lisez la documentation du JMSSecurityExtraBundle. Activer la Fonctionnalité des Annotations pour tous les Services Quand vous sécurisez la méthode 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, définissez l'option de configuration secure_all_services à «true» : Listing - # app/config/config.yml jms_security_extra: #... secure_all_services: true L'inconvénient de cette méthode est que, si elle est activée, le chargement initial de la page pourrait être très lent selon le nombre de services que vous avez défini.. generated on November, 0 Chapter : Comment sécuriser n'importe quel service ou méthode de votre application

250 Chapter Comment créer un Fournisseur d'utilisateur personnalisé Une partie du processus d'authentification standard de Symfony dépend 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 vérifie alors si le mot de passe de cet utilisateur est correct ou non et génère un token de sécurité afin que l'utilisateur reste authentifié pendant la session courante. Symfony possède par défaut un fournisseur d'utilisateur «in_memory» et «entity». Dans cet article, vous verrez comment vous pouvez créer votre propre fournisseur d'utilisateur, ce qui pourrait être utile si vous accédez à vos utilisateurs via une base de données personnalisée, un fichier, ou - comme le montre cet exemple - à travers un service web. Créer une Classe Utilisateur Tout d'abord, quelle que soit la source de vos données utilisateurs, vous allez avoir besoin de créer une classe User qui représente ces données. Le User peut ressembler à ce que vous voulez et contenir n'importe quelles données. La seule condition requise est que la classe implémente UserInterface. Les méthodes de cette interface doivent donc être définies dans la classe utilisateur personnalisée : getroles(), getpassword(), getsalt(), getusername(), erasecredentials(), equals(). Voyons cela en action: Listing - // 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;. generated on November, 0 Chapter : Comment créer un Fournisseur d'utilisateur personnalisé 0

251 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; generated on November, 0 Chapter : Comment créer un Fournisseur d'utilisateur personnalisé

252 Si vous avez plus d'informations à propos de vos utilisateurs - comme un «prénom» - alors vous pouvez ajouter un champ firstname pour contenir cette donnée. Pour plus de détails sur chacune de ces méthodes, voir l'interface UserInterface. Créer un Fournisseur d'utilisateur Maintenant que vous avez une classe User, vous allez créer un fournisseur d'utilisateur qui va récupérer les informations utilisateur depuis un service web, et nous allons aussi créer un objet WebserviceUser et le remplir avec des données. Le fournisseur d'utilisateur est juste une classe PHP qui doit implémenter UserProviderInterface, qui requiert que trois méthodes soient définies : loaduserbyusername($username), refreshuser(userinterface $user), et supportsclass($class). Pour plus de détails, voir l'interface UserProviderInterface. Voici un exemple de ce à quoi cela pourrait ressembler: Listing // 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 succès, 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()); generated on November, 0 Chapter : Comment créer un Fournisseur d'utilisateur personnalisé

253 0 public function supportsclass($class) return $class === 'Acme\WebserviceUserBundle\Security\User\WebserviceUser'; Créer un Service pour le Fournisseur d'utilisateur Maintenant, vous allez rendre le fournisseur d'utilisateur disponible en tant que service. Listing - # 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'implémentation réelle du fournisseur d'utilisateur aura probablement certaines dépendances, des options de configuration ou d'autres services. Ajoutez ces derniers en tant qu'arguments dans la définition du service. Assurez-vous que le fichier des services est importé. Lisez Importer la Configuration avec imports pour plus de détails. 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 spécifiez l'id du service que vous venez de définir. Listing - 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. Listing - security: encoders: Acme\WebserviceUserBundle\Security\User\WebserviceUser: sha generated on November, 0 Chapter : Comment créer un Fournisseur d'utilisateur personnalisé

254 La valeur spécifiée ici devrait correspondre à l'algorithme utilisé initialement pour l'encodage des mots de passe lors de la création de vos utilisateurs (qu'importe la manière 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 méthode getpassword(). De plus, en fonction de vos options, le mot de passe pourrait être encodé plusieurs fois et encodé ensuite en base. Détails sur la manière dont les mots de passe sont encryptés Symfony utilise une méthode spécifique 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 spécifié dans le fichier security.yml. Si un salt est spécifié, alors la valeur suivante est créée et ensuite assemblée pour former un «hash» via l'algorithme : $password.''.$salt.''; Si vos utilisateurs externes ont leurs mots de passe encodés d'une façon différente, alors vous aurez besoin de travailler un peu plus afin que Symfony encode correctement le mot de passe. Ceci est au-delà de la portée de cet article, mais devrait inclure de créer une sous-classe MessageDigestPasswordEncoder ainsi que surcharger la méthode mergepasswordandsalt. De surcroît, le «hash», par défaut, est encodé plusieurs fois puis encodé en base. Pour des détails plus spécifiques, voir MessageDigestPasswordEncoder. Pour éviter cela, configurez-le dans le fichier security.yml : Listing - security: encoders: Acme\WebserviceUserBundle\Security\User\WebserviceUser: algorithm: sha encode_as_base: false iterations:. generated on November, 0 Chapter : Comment créer un Fournisseur d'utilisateur personnalisé

255 Chapter 0 Comment créer un Fournisseur d'authentification Personnalisé Si vous avez lu le chapitre La sécurité, vous comprenez la distinction que Symfony fait entre authentification et autorisation dans l'implémentation de la sécurité. Ce chapitre traite des classes noyaux impliquées dans le processus d'authentification, et de l'implémentation d'un fournisseur d'authentification personnalisé. Comme l'authentification et l'autorisation sont des concepts séparés, cette extension sera indépendante du «fournisseur d'utilisateur», et fonctionnera avec les fournisseurs d'utilisateur de votre application, qu'ils soient stockés en mémoire, dans une base de données, ou n'importe où ailleurs. A la rencontre de WSSE Le chapitre suivant démontre comment créer un fournisseur d'authentification personnalisé pour une authentification WSSE. Le protocole de sécurité pour WSSE fournit plusieurs avantages concernant la sécurité :. Encryptage du Nom d'utilisateur / Mot de passe. Sécurité contre les attaques de type «replay». Aucune configuration de serveur web requise WSSE est très utile pour sécuriser des services web, qu'ils soient SOAP ou REST. Il existe de nombreuses et très bonnes documentations sur WSSE, mais cet article ne va pas se focaliser sur le protocole de sécurité, mais plutôt sur la manière dont un protocole personnalisé peut être ajouté à votre application Symfony. La base de WSSE est qu'un en-tête de requête est vérifié pour y trouver des informations de connexions encryptées en utilisant un «timestamp» et un «nonce», et est authentifié pour l'utilisateur demandé en utilisant un «digest» du mot de passe generated on November, 0 Chapter 0: Comment créer un Fournisseur d'authentification Personnalisé

256 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 rôle du token dans le contexte de sécurité de Symfony est important. Un token représente les données d'authentification de l'utilisateur présentes dans la requête. Une fois qu'une requête est authentifiée, le token conserve les données de l'utilisateur, et délivre ces données au travers du contexte de sécurité. Premièrement, vous allez créer votre classe token. Cela permettra de passer toutes les informations pertinentes à votre fournisseur d'authentification. Listing // 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 rôles, on le considère comme authentifié $this->setauthenticated(count($roles) > 0); public function getcredentials() return ''; La classe WsseUserToken étend la classe du composant de sécurité AbstractToken, qui fournit la fonctionnalité basique du token. Implémentez l'interface TokenInterface dans chaque classe que vous souhaitez utiliser comme token. Le Listener Ensuite, vous avez besoin d'un listener pour «écouter» le contexte de sécurité. Le listener est chargé de transmettre les requêtes au pare-feu et d'appeler le fournisseur d'authentification. Un listener doit être une instance de ListenerInterface. Un listener de sécurité devrait gérer l'évènement GetResponseEvent, et définir un token authentifié dans le contexte de sécurité en cas de succès generated on November, 0 Chapter 0: Comment créer un Fournisseur d'authentification Personnalisé

257 Listing // 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')!== preg_match($wsseregex, $request->headers->get('x-wsse'), $matches)) return; page. $token = new WsseUserToken(); $token->setuser($matches[]); $token->digest $token->nonce $token->created = $matches[]; = $matches[]; = $matches[]; 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 // $this->securitycontext->settoken(null); // return; // Deny authentication with a '0 Forbidden' HTTP response $response = new Response(); $response->setstatuscode(0); $event->setresponse($response); generated on November, 0 Chapter 0: Comment créer un Fournisseur d'authentification Personnalisé

258 Ce listener vérifie l'en-tête X-WSSE attendu dans la réponse, fait correspondre la valeur retournée pour l'information WSSE attendue, crée 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, alors une réponse 0 est retournée. Une classe non utilisée ci-dessus, la classe AbstractAuthenticationListener, est une classe de base très utile qui fournit certaines fonctionnalités communes pour les extensions de sécurité. Ceci inclut le fait de maintenir le token dans la session, fournir des gestionnaires en cas de succès/é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 vérification du WsseUserToken. C'est-à-dire que le fournisseur va vérifier que la valeur de l'en-tête Created est valide dans les cinq minutes, que la valeur de l'en-tête Nonce est unique dans les cinq minutes, et que la valeur de l'en-tête PasswordDigest correspond au mot de passe de l'utilisateur. Listing // 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);. generated on November, 0 Chapter 0: Comment créer un Fournisseur d'authentification Personnalisé

259 0 0 return $authenticatedtoken; throw new AuthenticationException('The WSSE authentication failed.'); protected function validatedigest($digest, $nonce, $created, $secret) // Expire le timestamp après minutes if (time() - strtotime($created) > 00) return false; // Valide que le nonce est unique dans les minutes if (file_exists($this->cachedir.'/'.$nonce) && file_get_contents($this->cachedir.'/'.$nonce) + 00 > time()) throw new NonceExpiredException('Previously used nonce detected'); file_put_contents($this->cachedir.'/'.$nonce, time()); // Valide le Secret $expected = base_encode(sha(base_decode($nonce).$created.$secret, true)); return $digest === $expected; public function supports(tokeninterface $token) return $token instanceof WsseUserToken; La classe AuthenticationProviderInterface requiert une méthode authenticate sur le token de l'utilisateur ainsi qu'une méthode 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 déplacera alors jusqu'au prochain fournisseur dans la liste. La Factory («l'usine» en français) 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 sécurité? La réponse est : en utilisant une factory. Une «factory» est là où vous intervenez dans le composant de sécurité en lui disant le nom de votre fournisseur ainsi que toutes ses options de configuration disponibles. Tout d'abord, vous devez créer une classe qui implémente SecurityFactoryInterface 0. Listing 0- // src/acme/demobundle/dependencyinjection/security/factory/wssefactory.php namespace Acme\DemoBundle\DependencyInjection\Security\Factory; generated on November, 0 Chapter 0: Comment créer un Fournisseur d'authentification Personnalisé

260 0 0 0 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 requiert les méthodes suivantes : la méthode create, qui ajoute le listener et le fournisseur d'authentification au conteneur d'injection de Dépendances pour le contexte de sécurité approprié ; la méthode getposition, qui doit être de type pre_auth, form, http et remember_me et qui définit le moment auquel le fournisseur est appelé ; la méthode getkey qui définit la clé de configuration utilisée pour référencer le fournisseur ; la méthode addconfiguration, qui est utilisée pour définir les options de configuration en dessous de la clé de configuration dans votre configuration de sécurité. Comment définir les options de configuration sera expliqué plus tard dans ce chapitre. Une classe non utilisée dans cet exemple, AbstractFactory, est une classe de base très utile qui fournit certaines fonctionnalités communes pour les «factories» de sécurité. Cela pourrait être utile lors de la définition d'un fournisseur d'authentification d'un type différent.. generated on November, 0 Chapter 0: Comment créer un Fournisseur d'authentification Personnalisé 0

261 Maintenant que vous avez créé une classe factory, la clé wsse peut être utilisée comme un pare-feu dans votre configuration de sécurité. Vous vous demandez peut-être «pourquoi avez vous besoin d'une classe factory spéciale pour ajouter des listeners et fournisseurs à un conteneur d'injection de dépendances?». C'est une très bonne question. La raison est que vous pouvez utiliser votre pare-feu plusieurs fois afin de sécuriser plusieurs parties de votre application. Grâce à cela, chaque fois que votre pare-feu sera utilisé, un nouveau service sera créé dans le conteneur d'injection de dépendances. La factory est ce qui crée ces nouveaux services. Listing 0- 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 première chose est d'ajouter les services ci-dessus dans le conteneur d'injection de dépendances. Votre classe factory ci-dessus fait référence à des IDs de service qui n'existent pas encore : wsse.security.authentication.provider et wsse.security.authentication.listener. Il est temps de définir 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: Maintenant que vos services sont définis, informez votre contexte de sécurité de l'existence de votre factory dans la classe de votre bundle : New in version.: Avant., la factory ci-dessous était ajoutée via le fichier security.yml à la place. Listing 0-0 // 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());. generated on November, 0 Chapter 0: Comment créer un Fournisseur d'authentification Personnalisé

262 Vous avez terminé! Vous pouvez maintenant définir des parties de votre application comme étant sous la protection de WSSE. Listing 0- security: firewalls: wsse_secured: pattern: wsse: /api/.* true Félicitations! Vous avez écrit votre tout premier fournisseur d'authentification de sécurité personnalisé! Un Petit Extra Que diriez-vous de rendre votre fournisseur d'authentification WSSE un peu plus excitant? Les possibilités sont sans fin. Voyons comment nous pouvons apporter plus d'éclat à tout cela! Configuration Vous pouvez ajouter des options personnalisées sous la clé wsse de votre configuration de sécurité. Par exemple, le temps alloué avant que l'en-tête «Created» expire est, par défaut, minutes. Rendez cela configurable, afin que différents pares-feu puissent avoir des longueurs de «timeout» différentes. Vous allez tout d'abord avoir besoin d'éditer WsseFactory puis ensuite de définir la nouvelle option dans la méthode addconfiguration. Listing 0-0 class WsseFactory implements SecurityFactoryInterface //... public function addconfiguration(nodedefinition $node) $node ->children() ->scalarnode('lifetime')->defaultvalue(00) ->end(); Maintenant, dans la méthode create de la factory, l'argument $config va contenir une clé «lifetime», déclarée à minutes (00 secondes) à moins qu'elle soit définie ailleurs dans la configuration. Passez cet argument à votre fournisseur d'authentification afin qu'il l'utilise. Listing 0- 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')) generated on November, 0 Chapter 0: Comment créer un Fournisseur d'authentification Personnalisé

263 0 //... ->replaceargument(0, new Reference($userProvider)) ->replaceargument(, $config['lifetime']); //... Vous allez aussi avoir besoin d'ajouter un troisième 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 troisième argument dans son constructeur - la valeur «lifetime» - qu'elle devrait utiliser à la place des 00 secondes codées en dur. Ces deux étapes ne sont pas montrées ici. La valeur «lifetime» de chaque requête wsse est maintenant configurable, et peut être définie par quelconque valeur que ce soit par pare-feu. Listing 0-0 security: firewalls: wsse_secured: pattern: /api/.* wsse: lifetime: 0 Le reste dépend de vous! N'importe quels autres points de configuration peuvent être définis dans la factory et consommé ou passé à d'autres classes dans le conteneur. generated on November, 0 Chapter 0: Comment créer un Fournisseur d'authentification Personnalisé

264 Chapter Comment changer le comportement par défaut du chemin cible Par défaut, le composant de sécurité conserve l'information de l'uri de la dernière requête dans une variable de session nommée _security.target_path. Lors d'une connexion réussie, l'utilisateur est redirigé vers ce chemin afin de l'aider à continuer sa visite en le renvoyant vers la dernière page connue qu'il a parcourue. Dans certains cas, ce n'est pas la meilleure solution. Par exemple, quand l'uri de la dernière requête est une méthode POST HTTP avec une route configurée pour accepter seulement une méthode POST, l'utilisateur est redirigé vers cette route et se retrouvera confronté inévitablement à une erreur 0. Pour contourner ce comportement, vous auriez simplement besoin d'étendre la classe ExceptionListener et surcharger la méthode par défaut nommée settargetpath(). Tout d'abord, surchargez le paramètre 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 : Listing - # src/acme/hellobundle/resources/config/services.yml parameters: #... security.exception_listener.class: Acme\HelloBundle\Security\Firewall\ExceptionListener Ensuite, créez votre propre ExceptionListener: Listing - 0 // 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) generated on November, 0 Chapter : Comment changer le comportement par défaut du chemin cible

265 0 // Ne conservez pas de chemin cible pour les requêtes XHR et non-get // Vous pouvez ajouter n'importe quelle logique supplémentaire 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 scénario le requiert! generated on November, 0 Chapter : Comment changer le comportement par défaut du chemin cible

266 Chapter Comment utiliser le Serializer Sérialiser et désérialiser vers et depuis des objets et différents formats (ex JSON ou XML) est un sujet très complexe. Symfony est fourni avec un Composant Serializer, qui vous donne certains outils pour résoudre vos problèmes. En fait, avant de commencer, familiarisez vous avec le sérialiseur, les normaliseurs et les encodeurs en lisant la documentation du Serializer. Vous pouvez également vous renseigner sur le JMSSerializerBundle qui étend certaines des fonctionnalités offertes par le Serializer de Symfony. Activer le Serializer New in version.: Le Serializer a toujours existé dans Symfony mais, avant la version., vous deviez construire le service serializer vous même. Le service serializer n'est pas disponible par défaut. Pour le rendre disponible, vous devez l'activer dans votre configuration : Listing - # app/config/config.yml framework: #... serializer: enabled: true. generated on November, 0 Chapter : Comment utiliser le Serializer

267 Ajouter des Normaliseurs et des Encodeurs Une fois activé, le service serializer sera disponible dans le conteneur et sera chargé avec deux encodeurs (JsonEncoder et XmlEncoder ) mais aucun normaliseurs, ce qui veut dire que vous devrez charger le vôtre. Vous pouvez charger des normaliseurs et/ou des encodeurs en les taggant comme serializer.normalizer et serializer.encoder. Il est également possible de définir la priorité du tag pour influencer l'ordre dans lequel il seront pris. Voici un exemple de chargement du GetSetMethodNormalizer : Listing - # app/config/config.yml services: get_set_method_normalizer: class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer tags: - name: serializer.normalizer La classe GetSetMethodNormalizer est inefficace de par sa conception. Dès que vous aurez un modèle de données circulaire, une boucle infinie sera créée à l'appel des getters. Vous êtes donc invité à ajouter vos propres normaliseurs pour répondre à ce besoin generated on November, 0 Chapter : Comment utiliser le Serializer

268 Chapter Comment créer un «listener» («écouteur» en français) d'évènement Symfony possède divers évènements et «hooks» qui peuvent être utilisés pour déclencher un comportement personnalisé dans votre application. Ces évènements sont lancés par le composant HttpKernel et peuvent être consultés dans la classe KernelEvents. Afin de personnaliser un évènement avec votre propre logique, vous devez créer un service qui va agir en tant que «listener» d'évènement pour cet évènement. Dans cet article, nous allons créer un service qui agit en tant que «Listener» d'exception, vous permettant de modifier comment les exceptions sont affichées par notre application. L'évènement KernelEvents::EXCEPTION est l'un des évènements du coeur du noyau: Listing // 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 récupérons l'objet exception depuis l'évènement reçu $exception = $event->getexception(); $message = 'My Error says: '. $exception->getmessage(). ' with code: '. $exception->getcode(); // personnalise notre objet réponse pour afficher les détails de notre exception $response = new Response(); $response->setcontent($message); // HttpExceptionInterface est un type d'exception spécial qui. generated on November, 0 Chapter : Comment créer un «listener» («écouteur» en français) d'évènement

269 0 // contient le code statut et les détails de l'entête if ($exception instanceof HttpExceptionInterface) $response->setstatuscode($exception->getstatuscode()); $response->headers->replace($exception->getheaders()); else $response->setstatuscode(00); // envoie notre objet réponse modifié à l'évènement $event->setresponse($response); Chaque évènement reçoit un objet de type $event légèrement différent. Pour l'évènement kernel.exception, c'est GetResponseForExceptionEvent. Pour voir quel est le type d'objet que chaque «listener» d'évènement reçoit, voyez KernelEvents. Maintenant que la classe est créée, nous devons juste la définir en tant que service et notifier Symfony que c'est un «listener» de l'évènement kernel.exception en utilisant un «tag» spécifique : Listing - # 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 défaut 0. Cette valeur peut aller de - à, et les «listeners» seront exécutées 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 exécuté avant un autre. Évènement de requête, vérification des types Une même page peut faire plusieurs requêtes (une requête principale et plusieurs sous-requêtes, c'est pourquoi, lorsque vous travaillez avec l'évènement KernelEvents::REQUEST, vous pourriez avoir besoin de vérifier le type de la requête. Cela peut être effectué très facilement comme ceci: Listing - // 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). generated on November, 0 Chapter : Comment créer un «listener» («écouteur» en français) d'évènement

270 0 if (HttpKernel::MASTER_REQUEST!= $event->getrequesttype()) // ne rien faire si ce n'est pas la requête principale return; //... Deux types de requête sont disponibles dans l'interface HttpKernelInterface : HttpKernelInterface::MASTER_REQUEST et HttpKernelInterface::SUB_REQUEST.. generated on November, 0 Chapter : Comment créer un «listener» («écouteur» en français) d'évènement 0

271 Chapter 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 déjà observé une erreur mentionnant le terme «scopes» lors de la création de vos services, ou avez eu besoin de créer des services qui dépendent du service request, cet article est fait pour vous. Comprendre les champs d'applications Le champs d'application d'un service contrôle les intéractions d'une instance de ce service avec son conteneur. Le composant d'injection de dépendance fourni deux champs d'applications génériques : container (valeur par défaut): la même instance est utilisée à chaque requête. prototype: Une nouvelle instance est créée à chaque requête. Le FrameworkBundle définit lui un troisième champ d'application : request. Celui-ci le lie à la requête. Ainsi pour chaque sous-requête une nouvelle instance est créée. La conséquence est qu'il est indisponible en dehors de la requête (en ligne de commande par example). Les champs d'applications ajoutent une contrainte sur les dépendances d'un service : un service ne peut pas dépendre d'un champ d'application moins large. Par exemple, si vous créez un service générique my_foo, mais essayez d'injecter le composant request, vous recevrez une ScopeWideningInjectionException au moment de la compilation du conteneur. Pour plus de détails, lisez les notes dans la barre latérale.. generated on November, 0 Chapter : Comment travailler avec les champs d'applications («scopes» en anglais)

272 Champs d'application et dépendances Imaginez que vous ayez configuré un service my_mailer, sans configurer de champs d'application pour ce service ; par défaut il sera réglé sur container. Ainsi chaque fois que vous appellerez le conteneur du service my_mailer, vous recevrez le même 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 requête courante. Vous l'ajoutez comme un argument du constructeur. Observons quelles pourraient être les conséquences : Quand vous appelez my_mailer, une instance de my_mailer (appelons le MailerA) est créée et un service request (RequestA) lui est envoyé. Facile! Vous effectuez maintenant une sous-requête dans Symfony, ce qui est une façon sympatique de dire que vous avez appelez, par exemple, la fonction twig % render... %, à partir d'un autre contrôleur. En interne, l'ancien service request (RequestA) est ainsi remplacé par une nouvelle instance (RequestB). Cela se déroule en arrière-plan et est tout à fait normal. Dans votre contrôleur intégré, vous interrogez une nouvelle fois le service my_mailer. Votre service ayant le champ d'application container, la même instance (MailerA) est réutilisée. Et voilà le problème : l'instance MailerA contient toujours l'ancien objet RequestA, qui ne correspond plus maintenant à l'objet requête mis à jour (RequestB est maintenant le service courant request). C'est subtile mais l'erreur pourrait engendrer des problèmes majeurs, et cela explique pourquoi cela est interdit. Ainsi, voilà pourquoi les champs d'applications existent, et comment il peuvent causer des problèmes. En continuant cette lecture nous vous indiquerons les solutions préconisées. Un service peut bien entendu dépendre d'un service provenant d'un champ d'application plus étendu. Configurer le champ d'application dans la définition Le champ d'application d'un service est indiqué dans la définition de ce service à l'aide du paramètre scope : Listing - # src/acme/hellobundle/resources/config/services.yml services: greeting_card_manager: class: Acme\HelloBundle\Mail\GreetingCardManager scope: request Si vous n'indiquez pas ce paramètre, il sera lié par défaut au conteneur, ce qui est le fonctionnement habituel d'un service. A moins que votre service ne dépende 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. generated on November, 0 Chapter : Comment travailler avec les champs d'applications («scopes» en anglais)

273 Utiliser un service provenant d'un champ d'application restreint Si votre service dépend d'un autre service au champ d'application déterminé, la meilleure solution est de définir le même 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 l environnement Twig dont elle est dépendante). Dans ces cas de figure, vous devrez configurer votre conteneur en tant que service et charger les dépendances provenant d'un champ d'application restreint à chaque appel, afin d'être certain d'obtenir les instances mises à jour: Listing // 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 send () $request = $this->container->get('request'); // Utilisez la requête ici Faites attention à ne pas enregistrer la requête dans une propriété de votre objet pour un appel futur ; cela engendrerait les mêmes inconsistances que celles décrites précédemment (excepté que dans ce cas, Symfony ne pourrait détecter cette erreur). La configuration du service pour cette classe : Listing - 0 # 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 généralement à proscrire (injectez seulement les paramètres utiles). Dans quelques rares cas, cela est nécessaire quand vous avez un service dans un champ d'application container qui a besoin d'un service du champ d'application request. generated on November, 0 Chapter : Comment travailler avec les champs d'applications («scopes» en anglais)

274 Si vous définissez un contrôleur comme un service, alors vous pourrez appelez l'objet Request sans injecter le conteneur comme un argument de votre méthode action. Voir La Requête en tant qu'argument du Contrôleur pour plus de détails. generated on November, 0 Chapter : Comment travailler avec les champs d'applications («scopes» en anglais)

275 Chapter Comment travailler avec les Passes de Compilation dans les Bundles Les passes de compilation vous donnent l'opportunité de manipuler d'autres définitions de service qui ont été définies via le conteneur de service. Pour savoir comment les créer, vous pouvez lire la section des composants «Compiler le Conteneur». Pour définir une passe de compilation depuis un bundle, vous devez l'ajouter à la méthode build se situant dans la classe du bundle: Listing - 0 // src/acme/mailerbundle/acm erbundle.php namespace Acme\MailerBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\DependencyInjection\ContainerBuilder; use Acme\MailerBundle\DependencyInjection\Compiler\CustomCompilerPass; class Acm erBundle extends Bundle public function build(containerbuilder $container) parent::build($container); $container->addcompilerpass(new CustomCompilerPass()); L'un des cas d'utilisation les plus fréquents des passes de compilation est lorsque vous travaillez avec des services taggés (apprenez-en plus à propos des tags en lisant la section sur les composants «Travailler avec des Services Taggés»). Si vous utilisez des tags personnalisés 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 séparateurs), suivi par un point, et finalement le nom «réel». Par exemple, si vous voulez introduire un tag «transport» dans votre Acm erBundle, vous devriez l'appeler acme_mailer.transport. generated on November, 0 Chapter : Comment travailler avec les Passes de Compilation dans les Bundles

276 Chapter Exemple de Session Proxy Le mécanisme de proxy de session possède une variété d'utilisations et cet exemple présente deux utilisations communes. Plutôt 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 Listing - 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 réels exemples qui peuvent être utilisés pour la classe YourProxy : le chiffrement des données de session et les sessions invitées en lecture seules. Chiffrement des Données en Session Si vous vouliez chiffrer les données en session, vous pouvez utiliser le proxy pour chiffrer ou déchiffrer la session comme suit Listing - 0 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) generated on November, 0 Chapter : Exemple de Session Proxy

277 0 $data = parent::read($id); return mcrypt_decrypt(\mcrypt_des, $this->key, $data); public function write($id, $data) $data = mcrypt_encrypt(\mcrypt_des, $this->key, $data); return parent::write($id, $data); Les sessions invitées en lecture seule Il y a quelques applications où une session est requise pour les utilisateurs invités, mais il n'y a pas spécialement besoin de persister cette session. Dans ce cas vous pouvez intercepter la session avant qu'elle ne soit écrite Listing 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); generated on November, 0 Chapter : Exemple de Session Proxy

278 Chapter Faire que la Locale soit "persistente" durant la session de l'utilisateur Avant Symfony., la locale était enregistrée dans un attribut de session appelé _locale. Depuis., c'est stocké dans la Request, ce qui signifie qu'elle n'est pas "persistante" durant la requête de l'utilisateur. Dans cet article, vous allez apprendre comment faire que la locale de l'utilisateur soit persistante pour que, une fois fixée, la même locale soit utilisée pour chaque requête suivante. Créer un LocaleListener Pour simuler le fait que la locale soit stockée en session, vous devez créer et enregistrer un nouvel event listener (écouteur d'évènement). Le listener ressemblera à quelque chose comme le code qui suit. Typiquement, _locale est utilisé comme paramètre du routing pour indiquer la locale, ceci dit la manière dont vous déterminez la locale désirée pour une requête n'est pas important Listing - 0 // 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) generated on November, 0 Chapter : Faire que la Locale soit "persistente" durant la session de l'utilisateur

279 0 0 0 $request = $event->getrequest(); if (!$request->hasprevioussession()) return; // on essaie de voir si la locale a été fixée dans le paramètre de routing _locale if ($locale = $request->attributes->get('_locale')) $request->getsession()->set('_locale', $locale); else // si aucune locale n'a été fixée explicitement dans la requête, 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 défaut KernelEvents::REQUEST => array(array('onkernelrequest', )), ); Puis enregistrez le listener : Listing - services: acme_locale.locale_listener: class: Acme\LocaleBundle\EventListener\LocaleListener arguments: ["%kernel.default_locale%"] tags: - name: kernel.event_subscriber Et voilà! Maintenant célébrons en changeant la locale de l'utilisateur qui est persisté au fil des requêtes. Souvenez-vous, pour récupérer la locale de l'utilisateur, utilisez toujours la méthode Request::getLocale Listing - // depuis un controller... use Symfony\Component\HttpFoundation\Request; public function indexaction(request $request) $locale = $request->getlocale();. generated on November, 0 Chapter : Faire que la Locale soit "persistente" durant la session de l'utilisateur

280 Chapter Configurer le Dossier où les Fichiers pour les Sessions sont Enregistrés Par défaut, la Symfony Standard Edition utilise les valeurs par défaut de php.ini pour session.save_handler et session.save_path pour déterminer l'endroit où persister les données de session. Cela est possible grâce à la configuration suivante : Listing - # 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 stockées. Néanmoins, si vous disposez de la configuration suivante, Symfony stockera les données 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 effacées : Listing - # app/config/config.yml framework: session: ~ Utiliser un dossier différent pour enregistrer les données de session est l'une des méthodes permettant d'assurer que les sessions courantes ne seront pas perdues lorsque le vous nettoierez le cache Symfony. Une excellente méthode (la plus complexe) de gestion de session avec Symfony est d'utiliser un handler d'enregistrement de session différent. 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 données. generated on November, 0 Chapter : Configurer le Dossier où les Fichiers pour les Sessions sont Enregistrés 0

281 Pour modifier le dossier dans lequel Symfony enregistre les données 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 : Listing - # app/config/config.yml framework: session: handler_id: session.handler.native_file save_path: "%kernel.root_dir%/sessions" generated on November, 0 Chapter : Configurer le Dossier où les Fichiers pour les Sessions sont Enregistrés

282 Chapter Combler une application legacy avec les sessions de Symfony New in version.: Cette intégration avec la session PHP legacy a été introduite en Symfony.. Si vous intégrez le framework Symfony full-stack dans une application legacy qui démarre 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 spécifier à null le paramètre handler_id : Listing - framework: session: storage_id: session.storage.php_bridge handler_id: ~ Autrement, si le problème est simplement que vous ne pouvez pas éviter que l'application ne démarre la session avec session_start(), vous pouvez encore utiliser un handler de sauvegarde basé sur Symfony en spécifiant le handler de sauvergarde comme dans l'exemple qui suit : Listing - framework: session: storage_id: session.storage.php_bridge handler_id: session.handler.native_file generated on November, 0 Chapter : Combler une application legacy avec les sessions de Symfony

283 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 démarrée. Si l'application démarre la session avant que Symfony ne soit initialisé, le handler de sauvegarde sera déja fixé. Dans ce cas, vous aurez besoin de handler_id: ~`. Surchargez le handler de sauvegarde uniquement si vous êtes sûr que l'application legacy peut utiliser le handler de sauvegarde de Symfony sans effets de bord et que la session n'ait pas été démarrée avant que Symfony n'ait été initialisé. Pour plus de détails, consultez Intégration avec les session "Legacy". generated on November, 0 Chapter : Combler une application legacy avec les sessions de Symfony

284 Chapter 0 Limiter les Écritures de Metadonnées en Session New in version.: La capacité de limiter les écritures de métadonnées en session a été introduite dans Symfony.. Le comportement par défaut de la session PHP est de persister la session indépendamment du fait que les données de la session aient changé ou pas. Dans Symfony, à chaque fois que l'on accède à la session, les métadonnées sont enregistrées (session créée/utilisée pour la dernière fois) qui peuvent être utilisées pour déterminer l'âge de la session et l'idle time (temps d'inactivité). Si pour des raisons de performance vous souhaitez limiter la fréquence à laquelle la session est persistée, cette fonctionnalité peut ajuster la granularité à laquelle les métadonnées sont mises à jour et persister la session moins souvent tout en gardant des métadonnées précises. Si d'autres données en session sont changées, la session sera toujours persistée. Il vous est possible de dire à Symfony de ne pas mettre à jour les métadonnées de la "session dernièrement modifiée" jusqu'à ce qu'un certain temps soit passé, en fixant framework.session.metadata_update_threshold à une valeur en seconde plus grande que zéro : Listing 0- framework: session: metadata_update_threshold: 0 Le comportement par défaut de PHP est de sauvegarder la session qu'elle ait été modifée ou pas. En utilisant framework.session.metadata_update_threshold Symfony décorera le session handler (configuré avec framework.session.handler_id) avec le WriteCheckSessionHandler. Cela préviendra toute écriture dans la session si cette dernière n'a pas été modifiée. generated on November, 0 Chapter 0: Limiter les Écritures de Metadonnées en Session

285 Sachez que si la session n'est pas écrite à chaque requête, cela peut être collecté par le garbage collector plus tôt que d'habitude. Cela signifie que vos utilisateurs peuvent être déconnectés plus tôt que prévu. generated on November, 0 Chapter 0: Limiter les Écritures de Metadonnées en Session

286 Chapter En quoi Symfony diffère de Symfony Le framework Symfony correspond à une évolution majeure si on le compare à la première version du framework. Heureusement, comme il est basé sur l'architecture MVC, les qualités utilisées pour maîtriser un projet symfony continuent d'être pertinentes pour développer avec Symfony. Bien entendu, app.yml n'existe plus mais le routage, les contrôleurs et les templates sont toujours présents. Dans ce chapitre, nous allons parcourir les différences entre symfony et Symfony. Comme vous allez le voir, de nombreuses opérations sont effectuées de manière légèrement différente. Vous apprécierez ces différences mineures car elles permettent des comportements stables, sans surprises, testables et une séparation claire des logiques utilisées au sein de vos applications Symfony. Ainsi, asseyez vous et relaxez vous pendant votre voyage du «passé» au «présent». Arborescence des répertoires Quand vous observez un projet Symfony - par exemple, l'édition Standard Symfony - vous pouvez noter une structure de répertoire très différente de celle présente dans symfony. Les differences sont cependant superficielles. Le dossier app/ Dans symfony, vos projets avaient une ou plusieurs applications, et chacune se situait dans le répertoire apps/ (ex. apps/frontend). Par défaut, dans Symfony, vous avez une seule application représentée par le dossier app/. Comme dans symfony, le dossier app/ contient une configuration spécifique à l'application. Il contient aussi le cache, les logs et les templates spécifiques à l'application ainsi qu'une classe Kernel (AppKernel), qui est l'objet de base représentant l'application. A la différence de symfony, très peu de code PHP se trouve dans le dossier app/. Ce répertoire n'est pas destiné à contenir les modules maison ou les fichiers des bibliothèques comme il le faisait dans symfony. Il correspond au répertoire où se situent les fichiers de configuration et autres ressources générales (templates, fichiers de traduction).. generated on November, 0 Chapter : En quoi Symfony diffère de Symfony

287 Le dossier src/ En résumé, votre code se trouve ici. Dans Symfony, tout le code applicatif se trouve dans un bundle (plus ou moins équivalent aux plugins de symfony) et, par défaut, chaque bundle se place dans le dossier src. Sur cet aspect, le répertoire src est un peu comme le dossier plugins dans symfony, tout en comportant beaucoup plus de flexibilité. De plus, pendant que vos bundles sont dans le répertoire src/, les bundles de bibliothèques tierces se situeront dans le répertoire vendor/. Afin d'avoir une idée plus précise du répertoire src/, pensez d'abord à une application symfony. Premièrement, certaines parties de votre code sont situées 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 répertoire config de votre projet et généré de nombreux fichiers de modèles. Finalement, afin d'utiliser certaines fonctionnalités communes, vous avez utilisé de nombreuses bibliothèques externes présentes dans le répertoire plugins/. En d'autres termes, le code qui fait fonctionner votre application se trouve à plusieurs endroits. Dans Symfony, la vie est plus simple car tout le code Symfony doit être placé dans un bundle. Dans les projets symfony, tout le code applicatif pourrait être déplacé dans un ou plusieurs plugins (ce qui est une bonne pratique en fait). Supposons que tous les modules, les classes PHP, les schémas, les configurations des routes, etc... soient déplacées dans un plugin, alors le dossier symfony plugins/ serait très similaire au dossier Symfony src/. Résumons de manière simple, le dossier src/ est l'endroit où placer tout le code, les ressources, les templates et tous les outils spécifiques à votre projet. Le dossier vendor/ Le dossier vendor/ est grossièrement l'équivalent du dossier lib/vendor/ présent dans symfony, qui était le dossier conventionnel pour toutes les bibliothèques et les bundles externes. Par défaut, vous trouverez les fichiers de la bibliothèque Symfony dans ce répertoire, ainsi que de nombreuses autres bibliothèques comme Doctrine, Twig et Swiftmailer. Les bundles tierces intégrés à Symfony se situent quelque part dans le répertoire vendor/. Le dossier web/ Peu de choses ont changé dans le dossier web/. Les différences 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 organisées dans votre bundle, tout en permettant de les rendre publiques. Afin de vous assurer que tous les bundles soient disponibles, lancez la commande suivante: Listing - php app/console assets:install web Cette commande est l'équivalent Symfony de la commande symfony 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 référence à n'importe quelle classe de votre generated on November, 0 Chapter : En quoi Symfony diffère de Symfony

288 project et ainsi être certain qu'elle est disponible. L'autoloader a changé dans Symfony afin d'être plus universel, plus rapide, et moins dépendant du nettoyage du cache. Dans symfony, le chargement automatique était réalisé en recherchant, dans tout le projet, la présence de classes PHP et en mettant en cache cette information dans un gigantesque tableau. Ce tableau disait à symfony les correspondances exactes entre les fichiers et les classes. Dans l'environnement de production, cela impliquait de nettoyer le cache lorsque des classes étaient ajoutées ou déplacées. Dans Symfony, une nouvelle classe - UniversalClassLoader - effectue ce travail. L'idée derrière 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 Symfony comme exemple: Listing - namespace Sensio\Bundle\FrameworkExtraBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; //... class SensioFrameworkExtraBundle extends Bundle //... Le fichier lui même est présent 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 précisément, l'espace de nom Sensio\Bundle\FrameworkExtraBundle, correspond au répertoire 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 répertoire vendor/sensio: Listing - // 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 Symfony, une erreur "class does not exist" implique que l'espace de nom de la classe incriminée et son emplacement physique ne correspondent pas. Plus simplement, Symfony recherche cette classe dans à un emplacement précis, mais cet emplacement n'existe pas (ou contient une classe différente). Pour qu'une classe soit chargée automatiquement, vous n'avez jamais besoin de nettoyer le cache dans Symfony. Comme mentionné précédemment, 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 entièrement sous votre contrôle via le fichier app/autoload.php. Si vous observez le contrôleur HelloController de l'édition Standard de Symfony vous remarquerez qu'il est placé dans l'espace de nom Acme\DemoBundle\Controller. Cependant, l'espace de nom Acme n'est pas défini dans le fichier app/autoload.php. En effet, par défaut vous n'avez pas à définir explicitement l'emplacement de vos bundles présents à l'intérieur du répertoire src/. L'UniversalClassLoader est configuré pour rechercher par défaut dans le répertoire src/ en utilisant la méthode registernamespacefallbacks: generated on November, 0 Chapter : En quoi Symfony diffère de Symfony

289 Listing - // app/autoload.php //... $loader->registernamespacefallbacks(array( DIR.'/../src', )); Utilisation de la console Dans symfony, la console est dans le répertoire racine de votre projet et est appelée symfony: Listing - php symfony Dans Symfony, la console est maintenant dans le sous-dossier app et est appelée console: Listing - php app/console Applications Dans un projet symfony, 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 Symfony, vous n'avez besoin de créer qu'une application (un blog, une application intranet,...). Le plus souvent, si vous voulez créer une seconde application, vous devriez plutôt créer un autre projet et partager certains bundles entre eux. Et si vous avez besoin de séparer la partie frontend de la partie backend de certains bundles, vous pouvez créer des sous-espaces de noms pour les contrôleurs, des sous-répertoires pour les templates, différentes configurations sémantiques, séparer 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 décider. Une deuxième application impliquerait un nouveau répertoire, par exemple my_app/, avec la même configuration que le répertoire app/. Vous pouvez lire à ce sujet la définition des termes Projet, Application, et Bundle dans le glossaire. Bundles et Plugins Dans un projet symfony, un plugin pouvait contenir de la configuration, des modules, des bibliothèques PHP, des ressources ou tout autre fichier en relation avec votre projet. Dans Symfony, l'idée de plugin est remplacée par celle de «bundle». Un bundle est encore plus puissant qu'un plugin, la preuve le coeur du framework Symfony est composé d'une série de bundles. Dans Symfony, les bundles sont les citoyens de première classe si flexibles que même le coeur de Symfony est lui-même un bundle. Dans symfony, un plugin doit être activé à l'intérieur de la classe ProjectConfiguration: Listing - generated on November, 0 Chapter : En quoi Symfony diffère de Symfony

290 // config/projectconfiguration.class.php public function setup() $this->enableallpluginsexcept(array(/* some plugins here */)); Dans Symfony, les bundles sont activés à l'intérieur du noyau applicatif: Listing - 0 // 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 symfony, les fichiers de configurations routing.yml et app.yml étaient automatiquement chargés depuis un plugin. Dans Symfony, les configurations de routages et d'applications inclues dans un bundle doivent être chargées manuellement. Par exemple, pour inclure un fichier de routage à partir d'un bundle appelé AcmeDemoBundle, vous devez faire: Listing - # app/config/routing.yml _hello: resource: "@AcmeDemoBundle/Resources/config/routing.yml" Cela chargera automatiquement les routes trouvées dans le fichier Resources/config/routing.yml du bundle AcmeDemoBundle. La est un raccourci qui, en interne, est remplacé par le chemin complet du bundle. Vous pouvez utiliser la même stratégie pour charger une configuration provenant d'un bundle: Listing -0 # app/config/config.yml imports: - resource: "@AcmeDemoBundle/Resources/config/config.yml" Dans Symfony, la configuration ressemble au app.yml présent dans symfony, excepté qu'elle est mieux encadrée. Dans app.yml, vous pouviez créer toutes les clefs dont vous aviez besoin. Par défaut, ces entrées étaient dénuées de sens et dépendaient entièrement de comment vous les utilisiez dans votre application : Listing - # un fichier app.yml provenant de symfony all: from_address: [email protected] Dans Symfony, vous pouvez aussi créer des clefs arbitraires à l'intérieur de la clef parameters de votre configuration: generated on November, 0 Chapter : En quoi Symfony diffère de Symfony 0

291 Listing - parameters: .from_address: [email protected] Vous pouvez maintenant accèder à cette valeur depuis votre contrôleur, par exemple: Listing - public function helloaction($name) $fromaddress = $this->container->getparameter(' .from_address'); En réalité, la configuration de Symfony est beaucoup plus puissante et est utilisée principalement pour configurer les objets que vous pouvez utiliser. Pour plus d'informations, consultez le chapitre intitulé «Service Container». generated on November, 0 Chapter : En quoi Symfony diffère de Symfony

292 Chapter Comment injecter des variables dans tous les modèles (i.e. Variables Globales) Parfois vous voulez qu'une variable soit accessible dans tous les modèles que vous utilisez. C'est possible à l'intérieur du fichier app/config/config.yml: Listing - # app/config/config.yml twig: #... globals: ga_tracking: UA-xxxxx-x Maintenant, la variable ga_tracking est disponible dans tous les modèles Twig : Listing - <p>our google tracking code is: ga_tracking </p> Listing - C'est aussi simple que cela! Vous pouvez aussi tirer parti du système intégré Paramètres de Service, qui vous permet d'isoler ou de réutiliser une valeur : ; app/config/parameters.yml [parameters] ga_tracking: UA-xxxxx-x Listing - # app/config/config.yml twig: globals: ga_tracking: "%ga_tracking%" La même variable est disponible exactement comme précédemment. generated on November, 0 Chapter : Comment injecter des variables dans tous les modèles (i.e. Variables Globales)

293 Des variables globales complexes Si vous voulez utiliser une variable globale plus complexe, comme un objet, alors vous devez utiliser une autre méthode. Ainsi vous aurez besoin de créer une Extension Twig et de retourner la variable globale comme une des valeurs du tableau retourné par la méthode getglobals. generated on November, 0 Chapter : Comment injecter des variables dans tous les modèles (i.e. Variables Globales)

294 Chapter Comment utiliser et enregistrer des chemins Twig namespacés Habituellement, lorsque vous vous référez à un template, vous allez utiliser le format MyBundle:Subdir:filename.html.twig (consultez Nommage de template et Emplacements). Twig offre également nativement une fonctionnalité appelée "chemins namespacés" ("namespaced paths" en anglais), et elle est intégrée automatiquement dans vos bundles. Prenons les chemins suivants comme exemple : Listing - % extends "AcmeDemoBundle::layout.html.twig" % % include "AcmeDemoBundle:Foo:bar.html.twig" % Avec les chemins namespacés, ce qui suit fonctionne également : Listing - % extends "@AcmeDemo/layout.html.twig" % % include "@AcmeDemo/Foo/bar.html.twig" % Les deux chemins sont valides et fonctionnel par défaut en Symfony. Comme bonus, la syntaxe namespacée est plus rapide. Enregistrer vos propres namespaces Vous pouvez également enregistrer vos propes namespaces. Supposez que vous utilisez une quelconque bibliothèque tierce incluant des templates Twig placés dans vendor/acme/foo-bar/templates. Premièrement, enregistrez un namespace pour pour ce dossier : Listing - generated on November, 0 Chapter : Comment utiliser et enregistrer des chemins Twig namespacés

295 # app/config/config.yml twig: #... paths: "%kernel.root_dir%/../vendor/acme/foo-bar/templates": foo_bar Le namespace enregistré est appelé foo_bar, se référant au dossier vendor/acme/foo-bar/templates. Supposant qu'il y est un fichier nommé sidebar.twig dans ce dossier, vous pouvez utiliser facilement : Listing - % include '@foo_bar/side.bar.twig' % generated on November, 0 Chapter : Comment utiliser et enregistrer des chemins Twig namespacés

296 Chapter Comment utiliser PHP plutôt que Twig dans les templates Même si Symfony utilise Twig en tant que moteur de rendu par défaut, vous pouvez utiliser du code PHP si vous le désirez. Ces deux moteurs de rendu sont en effet supportés à part égal au sein de Symfony. Symfony ajoute même à PHP quelques possibilités utiles permettant d'écrire des modèles 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: Listing - # app/config/config.yml framework: #... templating: engines: ['twig', 'php'] Vous pouvez maintenant utiliser le moteur de rendu en utilisant des templates PHP plutôt que des templates Twig simplement en utilisant l'extension.php dans le nom de vos templates à la place de l'extension.twig. Le contrôleur suivant délivre ainsi le template index.html.php Listing - // src/acme/hellobundle/controller/hellocontroller.php //... public function indexaction($name) return $this->render('acmehellobundle:hello:index.html.php', array('name' => $name)); generated on November, 0 Chapter : Comment utiliser PHP plutôt que Twig dans les templates

297 Ou vous pouvez utiliser le pour afficher le template par défaut AcmeHelloBundle:Hello:index.html.php: Listing - 0 // src/acme/hellobundle/controller/hellocontroller.php //... /** */ public function indexaction($name) return array('name' => $name); Templates de décorations Très souvent, les templates au sein d'un même projet partagent des composants communs comme l'entête bien connu ou le pied de page. Chez Symfony, nous aimons penser à ce problème différemment : un template peut être décoré par un autre. Le template index.html.php est décoré par layout.html.php, grâce à l'appel de la méthode extend() : Listing - <!-- 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 familière ; c'est en effet la même notation qui est utilisée pour référencer un template à l'intérieur d'un contrôleur. La partie :: s'expliquant simplement par l'absence d'un sous-dossier correspondant habituellement au contrôleur et qui sera donc cherché directement à la racine du dossier views/. Maintenant, regardons d'un peu plus près le fichier layout.html.php : Listing - <!-- src/acme/hellobundle/resources/views/layout.html.php --> <?php $view->extend('::base.html.php')?> <h>hello Application</h> <?php $view['slots']->output('_content')?> Le décorateur ou layout est lui-même décoré par un autre (::base.html.php). Symfony supporte en effet de multiples niveaux de décoration : un décorateur peut lui-même être décoré par un autre, et celà indéfiniment. Quand la partie bundle du nom du template est vide, les vues sont recherchées dans le dossier app/resources/views/. Ce dossier contient donc les vues globales utilisées dans tout le projet. Listing - <!-- app/resources/views/base.html.php --> <!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content="text/html; charset=utf-" /> <title><?php $view['slots']->output('title', 'Hello Application')?></title> </head> <body> generated on November, 0 Chapter : Comment utiliser PHP plutôt que Twig dans les templates

298 0 <?php $view['slots']->output('_content')?> </body> </html> Pour les deux décorateurs, l'expression $view['slots']->output('_content') est remplacée 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, Symfony fourni des méthodes sur l'objet $view. Dans un template, la variable $view est toujours disponible et réfère à un objet fournissant un ensemble de méthodes rendant le moteur de rendu puissant. Travailler avec les slots Un slot est un bout de code défini dans un template et réutilisable dans tous les décorateurs de ce template. Ainsi dans le template index.html.php un slot title correspond à : Listing - <!-- 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 décorateur de base a déjà le code pour afficher le titre dans le header html : Listing - <!-- app/resources/views/base.html.php --> <head> <meta http-equiv="content-type" content="text/html; charset=utf-" /> <title><?php $view['slots']->output('title', 'Hello Application')?></title> </head> La méthode output() insert le contenu d'un slot et optionnellement prend une valeur par défaut si le slot n'est pas défini. _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 : Listing - <?php $view['slots']->start('title')?> Du code html sur de nombreuses lignes <?php $view['slots']->stop()?> Inclure d'autres templates La meilleure façon de partager une partie d'un template est de définir un template qui pourra être inclus dans d'autres. Créez un template hello.html.php : Listing -0 <!-- src/acme/hellobundle/resources/views/hello/hello.html.php --> Hello <?php echo $name?>! Et changez le template index.html.php pour qu'il comporte : generated on November, 0 Chapter : Comment utiliser PHP plutôt que Twig dans les templates

299 Listing - <!-- 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 méthode render() évalue et retourne le contenu d'un autre template (c'est exactement la même méthode que celle utilisée dans le contrôleur). Intégrer d'autre contrôleurs Intégrer le résultat d'un contrôleur dans un template peut être très utile afin de factoriser certaines partie de l'application, en particulier lors de traitements Ajax, ou quand les templates intégrés ont besoin de certaines variables non-incluses dans le template principal. Si vous créez une action nommée fancy, et que vous voulez l'inclure dans le template index.html.php, utilisez simplement le code suivant : Listing - <!-- src/acme/hellobundle/resources/views/hello/index.html.php --> <?php echo $view['actions']->render('acmehellobundle:hello:fancy', array('name' => $name, 'color' => 'green'))?> Ici, la chaîne de caractères AcmeHelloBundle:Hello:fancy fait référence à l'action fancy du contrôleur Hello Listing - 0 // 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 défini le tableau d'éléments $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 système de rendu par template utilisé par Symfony peut être étendu facilement grace à des «helpers». Les «helpers» sont des objets PHP qui fournissent des possibilités utiles dans le contexte des templates. actions et slots sont ainsi deux des nombreux «helpers» intégrés dans Symfony. Créer des liens entre les pages A l'intérieur d'une application web, créez des liens entre les pages nécessite d'utiliser des méthode propres à l'application si l'on souhaite conserver une évolutivité et une maintenabilité sans failles. Ainsi generated on November, 0 Chapter : Comment utiliser PHP plutôt que Twig dans les templates

300 l'utilisation d'un «helper» router à l'intérieur des template permet de générer des URLs basées sur la configuration du routage. De cette façon, toutes les URLs peuvent facilement être mises à jour directement en changeant simplement la configuration: Listing - <a href="<?php echo $view['router']->generate('hello', array('name' => 'Thomas'))?>"> Greet Thomas! </a> La méthode generate() prend comme arguments le nom de la route et un tableau de paramètres. Le nom de la route est la clé principale sous laquelle celle-ci est définie, les paramètres sont des valeurs remplaçant les paramètres inclus dans celle-ci : Listing - # 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? Symfony fourni le tag assets pour les utiliser facilement : Listing - <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. Grâce à ceux-ci, vous pouvez déplacer le répertoire principal de votre application où vous le souhaitez à l'intérieur 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 conservées tant qu'elles ne sont pas affichées à l'utilisateur: Listing - <?php echo $view->escape($var)?> Par défaut, la méthode escape() assume que la variable est affichée dans un contexte HTML. Le second argument vous permet de définir le contexte. Par exemple, pour afficher cette variable dans un script JavaScript, il est possible d'utiliser le contexte js: Listing - <?php echo $view->escape($var, 'js')?> generated on November, 0 Chapter : Comment utiliser PHP plutôt que Twig dans les templates 00

301 Chapter Comment écrire une Extension Twig personnalisée La motivation principale d'écrire une extension est de déplacer du code souvent utilisé dans une classe réutilisable, comme par exemple ajouter le support pour l'internationalisation. Une extension peut définir des tags, des filtres, des tests, des opérateurs, des variables globales, des fonctions et des noeuds visiteurs. Créer une extension permet aussi une meilleure séparation du code qui est exécuté au moment de la compilation et du code nécessaire lors de l'exécution. De ce fait, cela rend votre code plus rapide. Avant d'écrire vos propres extensions, jetez un oeil au dépôt officiel des extensions Twig. Créer la Classe Extension Pour avoir votre fonctionnalité personnalisée, vous devez créer en premier lieu une classe Extension Twig. En tant qu'exemple, nous allons créer un filtre «prix» afin de formatter un nombre donné en un prix: Listing - 0 // 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')), );. generated on November, 0 Chapter : Comment écrire une Extension Twig personnalisée 0

302 0 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 même style que les filtres personnalisés, vous pouvez aussi ajouter des fonctions personnalisées et définir des variables globales. Définir une Extension en tant que Service Maintenant, vous devez informer le Conteneur de Service de l'existence de votre Extension Twig nouvellement créée : Listing - <!-- 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 mémoire que les Extensions Twig ne sont pas chargées de manière 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 dépendants du service de requête. Pour plus d'informations, jetez un oeil sur Comment travailler avec les champs d'applications («scopes» en anglais). Utiliser l'extension personnalisée Utiliser votre Extension Twig nouvellement créée n'est en rien différent des autres : Listing - # affiche $,00.00 # '00' price Passez d'autres arguments à votre filtre : generated on November, 0 Chapter : Comment écrire une Extension Twig personnalisée 0

303 Listing - # affiche $00, # '00.' price(, ',', '') En savoir plus Pour étudier le sujet des Extensions Twig plus en détail, veuillez jeter un coup d'oeil à la documentation des extensions Twig.. generated on November, 0 Chapter : Comment écrire une Extension Twig personnalisée 0

304 Chapter Comment rendre un template sans passer par un contrôleur Normalement, quand vous avez besoin de créer une page, vous devez créer un contrôleur et rendre un template depuis ce contrôleur. Mais si vous avez besoin d'afficher un simple template, qui ne nécessite pas de passage de paramètre, vous pouvez vous passer complètement de la création d'un contrôleur, en utilisant le contrôleur intégré FrameworkBundle:Template:template. Par exemple, supposons que vous vouliez afficher un template AcmeBundle:Static:privacy.html.twig, qui ne nécessite aucun passage de variable. Vous pouvez le faire sans créer de contrôleur. Listing - acme_privacy: path: /privacy defaults: _controller: FrameworkBundle:Template:template template: 'AcmeBundle:Static:privacy.html.twig' Le contrôleur FrameworkBundle:Template:template va simplement afficher le template que vous aurez définit comme paramètre template. Vous pouvez aussi, bien sûr, utiliser cette astuce lors du rendu de contrôleurs imbriqués dans un template. Le but de rendre un controleur depuis un template est typiquement de préparer des données pour un contrôleur personalisé. C'est probablement utile, uniquement, si vous voulez mettre partiellement en cache cette page (voir Mettre en cache les templates statiques). Listing - render(url('acme_privacy')) generated on November, 0 Chapter : Comment rendre un template sans passer par un contrôleur 0

305 Mettre en cache les templates statiques New in version.: La possibilité de mettre en cache les templates rendu par FrameworkBundle:Template:template est nouvelle en Symfony.. Puisque les templates affichés par cette méthode 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 contrôler la façon dont vos pages sont mise en cache: Listing - acme_privacy: path: /privacy defaults: _controller: FrameworkBundle:Template:template template: 'AcmeBundle:Static:privacy.html.twig' maxage: 00 sharedmaxage: 00 Les valeurs de maxage et sharedmaxage sont utilisées pour modifier l'objet Response créé dans le contrôleur. Pour plus d'informations sur la mise en cache, voir /book/http_cache. Il existe également un paramètre private (non présenté ici). Par défaut, la réponse est faite de manière public (la réponse peut être mise en cache, à la fois par les caches privés et les caches publics) tant que les paramètres maxage ou sharedmaxage sont renseignés. Si elle est définie à true la réponse devient privé (la réponse concerne un unique utilisateur et ne doit pas être stockée dans les caches publics). generated on November, 0 Chapter : Comment rendre un template sans passer par un contrôleur 0

306 Chapter 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 méthode createclient(): Listing - $client = static::createclient(array(), array( 'PHP_AUTH_USER' => 'username', 'PHP_AUTH_PW' => 'pa$$word', )); Vous pouvez aussi les surcharger directement dans l'objet requête: Listing - $client->request('delete', '/post/', 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 manière, vous pouvez utiliser le code décrit ci-dessus pour vous authentifier dans les tests, mais néanmoins 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'intérieur du conteneur traitant le form_login : Listing - # app/config/config_test.yml security: firewalls: your_firewall_name: http_basic: generated on November, 0 Chapter : Comment simuler une authentification HTTP dans un Test Fonctionnel 0

307 Chapter Comment simuler une authentification avec un token dans un test fonctionnel Les requêtes d'authentification dans les tests fonctionnels peuvent ralentir la suite de tests. Cela peut devenir un problème particulièrement lorsque le form_login est utilisé, puisque cela requiert des requêtes 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 façon de faire serait de créer un token vous-même et de le stocker dans une session. En faisant cela, vous devez vous assurer que le cookie approprié est envoyé avec la requête. L'exemple suivant vous montre comment faire Listing // 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()); generated on November, 0 Chapter : Comment simuler une authentification avec un token dans un test fonctionnel 0

308 0 $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 décrite dans Comment simuler une authentification HTTP dans un Test Fonctionnel est plus propre et de ce fait préférée. generated on November, 0 Chapter : Comment simuler une authentification avec un token dans un test fonctionnel 0

309 Chapter Comment tester les interactions de multiples clients Si vous avez besoin de simuler des interactions entre différents clients (pour un «chat» par exemple), créez plusieurs clients: Listing - $harry = static::createclient(); $sally = static::createclient(); $harry->request('post', '/say/sally/hello'); $sally->request('get', '/messages'); $this->assertequals(0, $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 bibliothèques tierces n'utilise de techniques requérant des états globaux. Dans ces cas vous devrez isoler les clients: Listing - 0 $harry = static::createclient(); $sally = static::createclient(); $harry->insulate(); $sally->insulate(); $harry->request('post', '/say/sally/hello'); $sally->request('get', '/messages'); $this->assertequals(0, $harry->getresponse()->getstatus()); $this->assertregexp('/hello/', $sally->getresponse()->getcontent()); Les clients isolés exécute de manière transparente leur requête dans un processus PHP dédié et «sain», et évitent donc quelconque effet de bord. generated on November, 0 Chapter : Comment tester les interactions de multiples clients 0

310 Comme un client isolé est plus lent, vous pouvez garder un client dans le processus principal et n'isoler que les autres. generated on November, 0 Chapter : Comment tester les interactions de multiples clients 0

311 Chapter 0 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 désirerez peut-être écrire des tests sur les données provenant du profiler. En effet ce dernier fournit des méthodes efficaces, permettant de contrôler de nombreux comportements et d'appliquer certaines métriques. Le Profiler de Symfony collecte de nombreuses informations à chaque requête. Utilisez celle-ci pour vérifier le nombre d'appels à la base de données, le temps passé dans l'exécution du framework,... Avant de pouvoir écrire des assertions, vous devez activer le profiler et vous assurez qu'il est disponible (il est activé par défaut dans l'environnement de test ): Listing class HelloControllerTest extends WebTestCase public function testindex() $client = static::createclient(); // Active le profiler pour la prochaine requête // (cela ne fait rien si le profiler n'est pas disponible) $client->enableprofiler(); $crawler = $client->request('get', '/hello/fabien'); // Ecrire des assertions concernant la réponse // Vérifier que le profiler est activé if ($profile = $client->getprofile()) // Vérifier le nombre de requêtes $this->assertlessthan(0, $profile->getcollector('db')->getquerycount()); // Vérifier le temps utilisé par le framework $this->assertlessthan((00, $profile->getcollector('time')->getduration()); generated on November, 0 Chapter 0: Comment utiliser le Profiler dans un test fonctionnel

312 Si un test échoue à cause des données du profilage (trop d'appels à la base de données par exemple), vous pouvez utiliser le Profiler Web afin d analyser la requête après que les tests soient terminés. Cela est réalisable simplement en intégrant le token suivant dans les messages d'erreurs: Listing 0- $this->assertlessthan( 0, $profile->get('db')->getquerycount(), sprintf('checks that query count is less than 0 (token %s)', $profile->gettoken()) ); Le profiler conserve des données différentes suivant l'environnement utilisé (particulièrement si vous utilisez SQLite, ce qui est la configuration par défaut). Les informations du profiler sont disponibles même si vous isolez un client ou utilisez une couche HTTP particulière pour vos tests. L'API intégrée vous permet de connaître les méthodes et les interfaces disponibles : data collectors. generated on November, 0 Chapter 0: Comment utiliser le Profiler dans un test fonctionnel

313 Chapter Comment tester du code interagissant avec une base de données Si votre code interagit avec une base de données, si par exemple il lit ou enregistre des données, vous devez ajuster vos tests pour prendre en compte ceci. Il y a de nombreuses façons de gérer ce cas. Dans un test unitaire, vous pouvez créer 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 préparer une base de données de test avec des valeurs prédéfinies pour assurer que votre test fonctionne toujours avec les mêmes données. Si vous voulez tester directement vos requêtes, consultez Comment tester les dépôts Doctrine. Mocker le Repository dans un test unitaire Si vous souhaitez tester du code dépendant 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 récupérer 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 idée) d'injecter votre repository directement en l'enregistrant comme un service factory. Cela demande un peu plus de travail pour le paramétrage, 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: Listing - namespace Acme\DemoBundle\Salary; use Doctrine\Common\Persistence\ObjectManager; generated on November, 0 Chapter : Comment tester du code interagissant avec une base de données

314 0 0 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 Listing use Acme\DemoBundle\Salary\SalaryCalculator; class SalaryCalculatorTest extends \PHPUnit_Framework_TestCase public function testcalculatetotalsalary() // Premièrement, 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(000)); $employee->expects($this->once()) ->method('getbonus') ->will($this->returnvalue(00)); // Maintenant, mockez 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(00, $salarycalculator->calculatetotalsalary()); generated on November, 0 Chapter : Comment tester du code interagissant avec une base de données

315 Dans cet exemple, vous construisez les mocks de l'intérieur vers l'extérieur, en créant premièrement l'employee qui est retourné par le Repository, qui lui-même est retourné par l' entitymanager. De cette façon, aucune vraie classe n'est impliquée dans le test. Changer le paramétrage de la base de données pour les tests fonctionnels Si vous avez des tests fonctionnels, vous souhaitez qu'ils interagissent avec une vraie base de données. La plupart du temps, vous souhaitez une base de données dédiée durant le développement de l'application ainsi qu'être capable de nettoyer la base de données avant chaque test. Pour faire cela, il vous est possible de spécifier la configuration de la base de données, qui remplacera la configuration par défaut: Listing - # app/config/config_test.yml doctrine: #... dbal: host: localhost dbname: testdb user: testdb password: testdb Assurez vous que votre base de données tourne sur localhost, qu'elle a une base de données définie et que les droits utilisateurs sont configurés. generated on November, 0 Chapter : Comment tester du code interagissant avec une base de données

316 Chapter Comment tester les dépôts Doctrine Les tests unitaires des dépôts Doctrines à l intérieur d'un projet Symfony ne sont pas recommandés. Lorsque vous travaillez avec un dépôt, vous travaillez avec quelque chose qui est réellement sensé être testé avec une véritable connexion à une base de données. Heureusement, vous pouvez facilement tester vos requêtes avec une vraie base de données, comme décrit ci-dessous Tests fonctionnels Si vous avez besoin de tester l exécution d'une requête, vous devez démarrer le kernel afin d'obtenir une connexion valide. Dans ce cas, votre classe doit hériter de WebTestCase, une classe qui simplifiera les processus de test: Listing // src/acme/storebundle/tests/entity/productrepositoryfunctionaltest.php namespace Acme\StoreBundle\Tests\Entity; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class ProductRepositoryFunctionalTest extends WebTestCase /** \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() generated on November, 0 Chapter : Comment tester les dépôts Doctrine

317 $products = $this->em ->getrepository('acmestorebundle:product') ->searchbycategoryname('foo') ; $this->assertcount(, $products); generated on November, 0 Chapter : Comment tester les dépôts Doctrine

318 Chapter 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: Listing - 0 // 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 : Listing - <!-- app/phpunit.xml.dist --> bootstrap = "tests.bootstrap.php" Maintenant, vous pouvez définir pour quel environnement vous voulez vider le cache dans votre fichier phpunit.xml.dist : Listing - <!-- app/phpunit.xml.dist --> <php> <env name="bootstrap_clear_cache_env" value="test"/> </php> generated on November, 0 Chapter : Comment personnaliser le processus de bootstrap avant les tests

319 Cela devient maintenant une variable d'environnement (c-a-d $_ENV) qui est disponible dans votre fichier bootstrap personnalisé (tests.bootstrap.php). generated on November, 0 Chapter : Comment personnaliser le processus de bootstrap avant les tests

320 Chapter Comment créer une Contrainte de Validation Personnalisée Vous pouvez créer une contrainte personnalisée en étendant la classe Constraint. A titre d'exemple, nous allons créer un simple validateur qui vérifie qu'une chaîne de caractères ne contient que des caractères alphanumériques. Créer une classe de contrainte Tout d'abord, vous devez créer une classe de contrainte qui étend la classe Constraint : Listing - 0 // src/acme/demobundle/validator/constraints/containsalphanumeric.php namespace Acme\DemoBundle\Validator\Constraints; use Symfony\Component\Validator\Constraint; /** */ class ContainsAlphanumeric extends Constraint public $message = 'La chaîne "%string%" contient un caractère non autorisé : elle ne peut contenir que des lettres et des chiffres.'; est nécessaire pour cette nouvelle contrainte afin de la rendre disponible via les annotations dans les autres classes. Les options de votre contrainte sont représentées par des propriétés publiques dans la classe de contrainte generated on November, 0 Chapter : Comment créer une Contrainte de Validation Personnalisée 0

321 Créer le validateur en lui-même Comme vous pouvez le voir, une classe «contrainte» est minimale. La validation est réalisée par un autre validateur de contrainte. La classe de validation est définie par l'implémentation de la méthode validatedby(), incluant une logique prédéfinie: Listing - // dans la classe de base Symfony\Component\Validator\Constraint public function validatedby() return get_class($this).'validator'; Si vous créez une contrainte personnalisée (ex: MyConstraint), Symfony recherchera automatiquement une autre classe, MyConstraintValidator lorsqu'il effectue la validation. La classe validatrice ne requiert qu'une méthode : validate: Listing - 0 // 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-]+$/', $value, $matches)) $this->context->addviolation($constraint->message, array('%string%' => $value)); La méthode validate ne retourne pas de valeur; à la place, elle ajoute des violations de contraintes à la propriété context du validateur grâce à l'appel à la méthode addviolation dans le cas d'erreurs de validation. Par conséquent, une valeur peut être considérée comme validée si aucune violation de contrainte n'est ajoutée au contexte. Le premier paramètre de l'appel à addviolation est le message d'erreur utilisé pour la violation. New in version.: La méthode isvalid est dépréciée au profit de validate dans Symfony.. La méthode setmessage est également dépréciée, en faveur de l'appel à la méthode addviolation du contexte. Utiliser le nouveau validateur Utiliser un validateur personnalisé est très facile, tout comme ceux fournis par Symfony lui-même : Listing - # src/acme/blogbundle/resources/config/validation.yml Acme\DemoBundle\Entity\AcmeEntity: properties: generated on November, 0 Chapter : Comment créer une Contrainte de Validation Personnalisée

322 name: - NotBlank: ~ - Acme\DemoBundle\Validator\Constraints\ContainsAlphanumeric: ~ Si votre contrainte contient des options, alors elles devraient être des propriétés publiques de la classe de contrainte personnalisée que vous avez créée plus tôt. Ces options peuvent être configurées comme toutes les options des contraintes de Symfony. Contraintes de validation avec dépendances Si votre validateur possède des dépendances, comme une connexion à une base de données, il faudra le configurer comme un service dans le conteneur d'injection de dépendances. Ce service doit inclure le tag validator.constraint_validator et un attribut alias : Listing - 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 référencer le validateur approprié: Listing - public function validatedby() return 'alias_name'; Comme mentionné précédemment, Symfony recherchera automatiquement une classe nommée d'après le nom de la contrainte et suffixée par Validator. Si votre validateur de contrainte est défini comme un service, il est important de surcharger la méthode validatedby() afin qu'elle renvoie l'alias utilisé pour définir le service ; autrement, Symfony n'utilisera pas le service de validation, et instanciera la classe, sans injecter les dépendances requises. Contrainte de validation de classe Outre la validation d'une propriété de classe, une contrainte peut avoir une portée de classe en renseignant une cible: Listing - public function gettargets() return self::class_constraint; Avec ceci, la méthode validate() du validateur prend un objet comme premier argument: Listing - class ProtocolClassValidator extends ConstraintValidator public function validate($protocol, Constraint $constraint) if ($protocol->getfoo()!= $protocol->getbar()) $this->context->addviolationat('foo', $constraint->message, array(), null); generated on November, 0 Chapter : Comment créer une Contrainte de Validation Personnalisée

323 0 Notez bien qu'une contrainte de validation de classe est appliquée à la classe elle-même, et pas à la propriété : Listing - # src/acme/blogbundle/resources/config/validation.yml Acme\DemoBundle\Entity\AcmeEntity: constraints: - ContainsAlphanumeric generated on November, 0 Chapter : Comment créer une Contrainte de Validation Personnalisée

324 Chapter Comment créer des web services SOAP à l'intérieur d'un contrôleur Symfony Configurer un contrôleur afin qu'il agisse comme un serveur est réalisé simplement avec quelques outils. Vous devez, bien sûr, avoir installé l'extension PHP SOAP. Comme l'extension PHP SOAP ne peut actuellement pas générer un WSDL, vous devez soit en créer un, soit utiliser un générateur provenant d'une bibliothèque tierce. Il existe de nombreuses implémentations de serveur SOAP disponibles compatibles avec PHP. Zend SOAP et NuSOAP en sont deux exemples. Bien que nous utilisions l'extension PHP SOAP dans nos exemples, l'idée générale devrait être applicable aux autres implementations. SOAP fonctionne en exposant les méthodes d'un objet PHP à une entité externe (c'est-à-dire le programme utilisant le service SOAP). Pour commencer, créez une classe - HelloService - qui représente les fonctionnalités que vous mettrez à disposition dans votre service SOAP. Dans ce cas, le service SOAP permettra à un client d'appeler la méthode nommée hello, qui engendrera l'envoi d'un courriel: Listing - 0 // 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) generated on November, 0 Chapter : Comment créer des web services SOAP à l'intérieur d'un contrôleur Symfony

325 0 $message = \Swift_Message::newInstance() ->setto('[email protected]') ->setsubject('hello Service') ->setbody($name. ' dit bonjour!'); $this->mailer->send($message); return 'Bonjour, '.$name; Listing - Ensuite, vous devrez permettre à Symfony de créer 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 adéquat : # app/config/config.yml services: hello_service: class: Acme\SoapBundle\Services\HelloService arguments: [@mailer] Voici un exemple de contrôleur capable de gérer une requête SOAP. Si indexaction() est accessible via la route /soap, alors le document WSDL peut être atteint via /soap?wsdl. Listing 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--'); ob_start(); $server->handle(); $response->setcontent(ob_get_clean()); return $response; Notez les appels à ob_start() et ob_get_clean(). Ces méthodes contrôlent le tampon de sortie qui vous permettent «d'intercepter» les flux de sortie de la méthode $server->handle(). Cela est nécessaire car Symfony attend de votre contrôleur un objet Response contenant ce flux. Rappelez vous aussi de définir l'entête 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. generated on November, 0 Chapter : Comment créer des web services SOAP à l'intérieur d'un contrôleur Symfony

326 ob_get_clean() pour mettre la sortie dans le contenu de la Réponse et vider le tampon de sortie. Finalement, vous êtes prêt à retourner l'objet Response. Voici un exemple qui appelle un service en utilisant le client NuSOAP. Cet exemple suppose que le indexaction présent dans le contrôleur ci-dessus est accessible via la route /soap: Listing - $client = new \Soapclient(' true); $result = $client->call('hello', array('name' => 'Scott')); Un exemple d'un flux WSDL : Listing <?xml version=".0" encoding="iso--"?> <definitions xmlns:soap-env=" xmlns:xsd=" xmlns:xsi=" xmlns:soap-enc=" xmlns:tns="urn:arnleadservicewsdl" xmlns:soap=" xmlns:wsdl=" xmlns=" targetnamespace="urn:helloservicewsdl"> <types> <xsd:schema targetnamespace="urn:hellowsdl"> <xsd:import namespace=" /> <xsd:import namespace=" /> </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=" <operation name="hello"> <soap:operation soapaction="urn:arnleadservicewsdl#hello" style="rpc"/> <input> <soap:body use="encoded" namespace="urn:hellowsdl" encodingstyle=" </input> <output> <soap:body use="encoded" namespace="urn:hellowsdl" encodingstyle=" </output> </operation> </binding> <service name="hellowsdl"> <port name="hellowsdlport" binding="tns:hellowsdlbinding">. generated on November, 0 Chapter : Comment créer des web services SOAP à l'intérieur d'un contrôleur Symfony

327 <soap:address location=" /> </port> </service> </definitions> generated on November, 0 Chapter : Comment créer des web services SOAP à l'intérieur d'un contrôleur Symfony

328 Chapter Comment créer et stocker un projet Symfony dans git Bien que cet article soit spécifique à git, les mêmes principes génériques s'appliqueront si vous stockez votre projet dans Subversion. Une fois que vous aurez lu La création de pages avec Symfony et que vous deviendrez familier avec l'usage de Symfony, vous serez sans aucun doute prêt à démarrer votre propre projet. Dans cet article du Cookbook, vous allez apprendre la meilleure façon qui soit de démarrer un nouveau projet Symfony stocké dans le système de gestion de contrôle de source git. Configuration Initiale du Projet Pour démarrer, vous aurez besoin de télécharger Symfony et d'initialiser votre dépôt local git :. Télécharger Symfony Standard Edition sans les «vendors».. Dézippez/détarez la distribution. Cela va créer un dossier nommé Symfony avec votre nouvelle structure de projet, les fichiers de configuration, etc. Renommez-le en ce que vous voulez.. Créez un nouveau fichier nommé.gitignore à la racine de votre nouveau projet (par exemple : à côté du fichier deps) et coller ce qui suit dedans. Les fichiers correspondants à ces patterns seront ignorés par git : Listing - /web/bundles/ /app/bootstrap* /app/cache/* /app/logs/*. generated on November, 0 Chapter : Comment créer et stocker un projet Symfony dans git

329 /vendor/ /app/config/parameters.yml Vous pouvez aussi avoir un fichier.gitignore qui peut être utilisé sur tout votre système, dans ce cas, vous pourrez trouver plus d'informations ici : Github.gitignore. De cette manière, vous pouvez exclure les fichiers/dossiers souvent utilisés par votre IDE pour l'ensemble de vos projets.. 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 paramètres spécifiques à la machine comme les mots de passe de base de données ne soient pas committés. En créant le fichier parameters.yml.dist, les nouveaux développeurs peuvent rapidement cloner le projet, copier ce fichier vers parameters.yml, l'adapter, et commencer à développer.. Initialisez votre dépôt git : Listing - $ git init. Ajoutez tous les fichiers initiaux dans git : Listing - $ git add.. Créez un commit initial avec votre nouveau projet : Listing - $ git commit -m "Initial commit". Finalement, téléchargez toutes les bibliothèques tierces en utilisant composer. vous pourrez trouver plus d'informations sur Mettre à jour les Vendors. A ce point, vous disposez d'un projet Symfony totalement fonctionnel qui est correctement committé sous git. Vous pouvez immédiatement commencer à développer, en committant les nouveaux changements dans votre dépôt git. Vous pouvez continuer en lisant le chapitre La création de pages avec Symfony pour en apprendre davantage sur comment configurer et développer votre application en interne. L'Edition Standard Symfony est fournie avec des exemples d'utilisation. Pour supprimer le code de démonstration, suivez les instructions contenues dans le fichier Readme de la Standard Edition. Gérer les bibliothèques vendors avec bin/vendors et deps Comment ça marche? Chaque projet Symfony utilise des bibliothèques tierces regroupées sous l'appellation «vendors». D'une façon ou d'une autre, le but est de vous permettre de télécharger ces bibliothèques dans le répertoire vendor/ et de pouvoir obtenir pour chacune la version désirée. Par défaut, ces bibliothèques sont téléchargées en exécutant le script de téléchargement php bin/ vendors install. Ce script utilise le fichier deps` situé à la racine de votre projet. Ce fichier au format. generated on November, 0 Chapter : Comment créer et stocker un projet Symfony dans git

330 INI contient la liste des bibliothèques nécessaires à votre projet, ainsi que le répertoire cible où chacune doit être téléchargée, et (de manière optionnelle) la version à télécharger. Le script bin/vendors` utilise git pour ce téléchargement, car la plupart des bibliothèques sont gérées et disponibles via git. Le script bin/ vendors se base également sur le fichier deps.lock, lequel vous permet d'arrêter la version à une révision précise. Nota bene : ces bibliothèques «vendor» ne sont pas gérées dans votre dépôt; ce sont de simples fichiers installés (et ignorés du dépôt git) dans le répertoire vendor/ par le script bin/vendors. Or, puisque toute l'information nécessaire pour télécharger ces fichiers est disponible dans deps et deps.lock (qui, eux, sont gérés par votre dépôt), tout autre développeur peut utiliser votre projet, lancer php bin/vendors install, et obtenir les mêmes bibliothèques (et version). Vous contrôlez donc exactement le contenu de chaque bibliothèque, sans avoir besoin de le versionner dans votre dépôt. Ainsi, lorsqu'un développeur veut travailler sur votre projet, il lui suffit de lancer le script php vendors install pour s'assurer que toutes les bibliothèques nécessaires soient téléchargées. bin/ Mettre à jour Symfony Puisque Symfony est une bibliothèque tierce de votre projet et qu'elle est donc gérée entièrement via deps et``deps.lock``, mettre à jour Symfony signifie simplement mettre à jour ces deux fichiers afin qu'ils correspondent à la dernière version de l'édition standard de Symfony. Bien sûr, si vous avez ajouté des entrées aux fichiers deps ou deps.lock, veillez à ne remplacer que les parties originales afin de ne pas supprimer les entrées supplémentaires. Hacker les bibliothèques vendor Parfois, il est nécessaire de télécharger une version précise (branche, tag) d'une bibliothèque. Vous pouvez le faire directement dans le fichier deps : Listing - [AcmeAwesomeBundle] git= target=/bundles/acme/awesomebundle version=ma-version-trop-bien L'option git définit l'url de la bibliothèque. Elle peut utiliser divers protocoles, comme ou git://. L'option target définit le répertoire cible, le répertoire dans lequel la bibliothèque va être téléchargée. Par convention, les bundles devraient aller dans le répertoire vendor/bundles/ Acme, les autres bibliothèques iront dans vendor/ma-biblio-trop-bien, le répertoire vendor est également le répertoire cible par défaut. L'option version vous permet de définir une révision spécifique. Vous pouvez lui attribuer un nom de tag (version=origin/0.) ou de branche (refs/remotes/origin/ma-branchetrop-bien). Version par défaut: origin/head. Flux de mise à jour lors de l'éxécution de php bin/vendors install, le script vérifie d'abord si le répertoire cible existe pour chaque bibliothèque. 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 répertoire sera donc cloné une seule et unique fois. Pour faire des changements liés au dépôt distant il est nécessaire d'effacer le répertoire cible lui-même, et non seulement son contenu. generated on November, 0 Chapter : Comment créer et stocker un projet Symfony dans git 0

331 Vendors et Submodules Au lieu d'utiliser le système composer.json pour gérer les bibliothèques vendor, vous pourriez choisir à la place le système natif git submodules. Il n'y a rien d'incorrect dans cette approche, bien que le système composer.json soit la manière officielle de résoudre ce problème 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 développeurs puissent collaborer au projet. La façon la plus facile de stocker votre projet sur un serveur distant est via GitHub. Les dépôts publics sont gratuits, cependant vous devrez payer un abonnement mensuel pour héberger des dépôts privés. Une autre alternative est de stocker votre dépôt git sur n'importe quel serveur en créant un dépôt barebones et en «pushant» ce dernier. Une bibliothèque qui aide à gérer ceci est Gitolite generated on November, 0 Chapter : Comment créer et stocker un projet Symfony dans git

332 Chapter Comment créer et stocker un projet Symfony dans Subversion Cette article est spécifique à Subversion et basé sur des principes que vous trouverez dans l'article Comment créer et stocker un projet Symfony dans git. Une fois que vous avez lu La création de pages avec Symfony et que vous êtes devenu familier avec l'usage de Symfony, vous serez sans aucun doute prêt à démarrer votre propre projet. La méthode préférée pour gérer des projets Symfony est d'utiliser git mais certains préfèrent utiliser Subversion, ce qui ne pose aucun problème! Dans cet article du Cookbook, vous allez apprendre comment gérer votre projet en utilisant svn de la même manière que si vous l'aviez fait avec git. Ceci est une méthode parmi tant d'autres de stocker votre projet Symfony dans un dépôt Subversion. Il y a plusieurs manières de faire cela et celle-ci en est simplement une qui fonctionne. Le Dépôt Subversion Pour cet article, nous supposerons que votre schéma de dépôt suit la structure standard et répandue : Listing - myproject/ branches/ tags/ trunk/ generated on November, 0 Chapter : Comment créer et stocker un projet Symfony dans Subversion

333 La plupart des hébergements subversion devraient suivre cette pratique standard. C'est le schéma recommandé par Contrôle de version avec Subversion et utilisé par la plupart des hébergements gratuits (voir Solutions d'hébergement Subversion). Configuration Initiale du Projet Pour démarrer, vous aurez besoin de télécharger Symfony et d'effectuer la configuration basique de Subversion :. Téléchargez Symfony Standard Edition avec ou sans les «vendors».. Dézippez/détarez la distribution. Cela va créer un dossier nommé Symfony avec votre nouvelle structure de projet, les fichiers de configuration, etc. Renommez-le en ce que vous voulez.. Effectuez un «checkout» du dépôt Subversion qui va héberger ce projet. Disons qu'il est hébergé sur Google code et nommé myproject : Listing - $ svn checkout myproject. Copiez les fichiers du projet Symfony dans le dossier subversion : Listing - $ mv Symfony/* myproject/. Ecrivons maintenant les patterns pour les fichiers à ignorer. Tout ne doit pas être stocké dans votre dépôt subversion. Quelques fichiers (comme le cache) sont générés et d'autres (comme la configuration de la base de données) sont destinés à être adaptés sur chaque machine. Ainsi, nous utilisons la propriété svn:ignore afin de pouvoir ignorer ces fichiers spécifiques. Listing - 0 $ 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)". Le reste des fichiers peut maintenant être ajouté et committé dans le projet : generated on November, 0 Chapter : Comment créer et stocker un projet Symfony dans Subversion

334 Listing - $ svn add --force. $ svn ci -m "add basic Symfony Standard.X.Y". 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 paramètres spécifiques à la machine comme les mots de passe de base de données ne soient pas committés. En créant le fichier parameters.yml.dist, les nouveaux développeurs peuvent rapidement cloner le projet, copier ce fichier vers parameters.yml, l'adapter, et commencer à développer.. Finalement, téléchargez toutes les bibliothèques tierces : Listing - $ php bin/vendors install git doit être installé pour pouvoir exécuter la commande bin/vendors ; c'est le protocole utilisé pour aller récupérer les bibliothèques vendor. Cela signifie seulement que git est utilisé comme outil pour aider au téléchargement des bibliothèques dans le répertoire vendor/. A ce point, vous avez un projet Symfony entièrement fonctionnel stocké dans votre dépôt Subversion. Le développement peut démarrer avec des commits dans ce dernier. Vous pouvez continuer en lisant le chapitre La création de pages avec Symfony pour en apprendre plus sur comment configurer et développer votre application en interne. L'Edition Standard Symfony vient avec des exemples de fonctionnalités. Pour supprimer le code de démonstration, suivez les instructions du fichier Readme de la Standard Edition. Gérer les bibliothèques vendors avec bin/vendors et deps Comment ça marche? Chaque projet Symfony utilise des bibliothèques tierces regroupées sous l'appellation «vendors». D'une façon ou d'une autre, le but est de vous permettre de télécharger ces bibliothèques dans le répertoire vendor/ et de pouvoir obtenir pour chacune la version désirée. Par défaut, ces bibliothèques sont téléchargées en exécutant le script de téléchargement 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 bibliothèques nécessaires à votre projet, ainsi que le répertoire cible où chacune doit être téléchargée, et (de manière optionnelle) la version à télécharger. Le script bin/vendors` utilise git pour ce téléchargement, car la plupart des bibliothèques sont gérées et disponibles via git. Le script bin/ vendors se base également sur le fichier deps.lock, lequel vous permet d'arrêter la version à une révision précise. Nota bene : ces bibliothèques «vendor» ne sont pas gérées dans votre dépôt; ce sont de simples fichiers installés (et ignorés du dépôt git) dans le répertoire vendor/ par le script bin/vendors. Or, puisque toute. generated on November, 0 Chapter : Comment créer et stocker un projet Symfony dans Subversion

335 l'information nécessaire pour télécharger ces fichiers est disponible dans deps et deps.lock (qui, eux, sont gérés par votre dépôt), tout autre développeur peut utiliser votre projet, lancer php bin/vendors install, et obtenir les mêmes bibliothèques (et version). Vous contrôlez donc exactement le contenu de chaque bibliothèque, sans avoir besoin de le versionner dans votre dépôt. Ainsi, lorsqu'un développeur veut travailler sur votre projet, il lui suffit de lancer le script php vendors install pour s'assurer que toutes les bibliothèques nécessaires soient téléchargées. bin/ Mettre à jour Symfony Puisque Symfony est une bibliothèque tierce de votre projet et qu'elle est donc gérée entièrement via deps et``deps.lock``, mettre à jour Symfony signifie simplement mettre à jour ces deux fichiers afin qu'ils correspondent à la dernière version de l'édition standard de Symfony. Bien sûr, si vous avez ajouté des entrées aux fichiers deps ou deps.lock, veillez à ne remplacer que les parties originales afin de ne pas supprimer les entrées supplémentaires. Hacker les bibliothèques vendor Parfois, il est nécessaire de télécharger une version précise (branche, tag) d'une bibliothèque. Vous pouvez le faire directement dans le fichier deps : Listing - [AcmeAwesomeBundle] git= target=/bundles/acme/awesomebundle version=ma-version-trop-bien L'option git définit l'url de la bibliothèque. Elle peut utiliser divers protocoles, comme ou git://. L'option target définit le répertoire cible, le répertoire dans lequel la bibliothèque va être téléchargée. Par convention, les bundles devraient aller dans le répertoire vendor/bundles/ Acme, les autres bibliothèques iront dans vendor/ma-biblio-trop-bien, le répertoire vendor est également le répertoire cible par défaut. L'option version vous permet de définir une révision spécifique. Vous pouvez lui attribuer un nom de tag (version=origin/0.) ou de branche (refs/remotes/origin/ma-branchetrop-bien). Version par défaut: origin/head. Flux de mise à jour lors de l'éxécution de php bin/vendors install, le script vérifie d'abord si le répertoire cible existe pour chaque bibliothèque. 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 répertoire sera donc cloné une seule et unique fois. Pour faire des changements liés au dépôt distant il est nécessaire d'effacer le répertoire cible lui-même, et non seulement son contenu. Solutions d'hébergement Subversion La plus grosse différence entre git 0 et svn est que Subversion a besoin d'un dépôt central pour fonctionner. Vous avez donc plusieurs solutions : generated on November, 0 Chapter : Comment créer et stocker un projet Symfony dans Subversion

336 Hébergement par vos soins : créez votre propre dépôt et accédez-y soit grâce au système de fichiers, soit via le réseau. Pour vous aider dans cette tâche, vous pouvez lire Contrôle de version avec Subversion. Hébergement via une entité tierce : il y a beaucoup de solutions d'hébergement gratuites et sérieuses disponibles comme GitHub, Google code, SourceForge ou Gna. Certaines d'entre elles offrent aussi des possibilités d'hébergement git generated on November, 0 Chapter : Comment créer et stocker un projet Symfony dans Subversion

337

338

PHP 5.4 Développez un site web dynamique et interactif

PHP 5.4 Développez un site web dynamique et interactif Editions ENI PHP 5.4 Développez un site web dynamique et interactif Collection Ressources Informatiques Table des matières Table des matières 1 Chapitre 1 Introduction 1. Objectif de l'ouvrage.............................................

Plus en détail

Devenez un véritable développeur web en 3 mois!

Devenez un véritable développeur web en 3 mois! Devenez un véritable développeur web en 3 mois! L objectif de la 3W Academy est de former des petits groupes d élèves au développement de sites web dynamiques ainsi qu à la création d applications web

Plus en détail

ADMINISTRATION DE ADOBE LIVECYCLE MOSAIC 9.5

ADMINISTRATION DE ADOBE LIVECYCLE MOSAIC 9.5 ADMINISTRATION DE ADOBE LIVECYCLE MOSAIC 9.5 Informations juridiques Copyright 2010 Adobe Systems Incorporated and its licensors. All rights reserved. Administration d Adobe LiveCycle Mosaic 9.5 13 octobre

Plus en détail

Auguria_PCM Product & Combination Manager

Auguria_PCM Product & Combination Manager Auguria_PCM Product & Combination Manager Guide utilisateurs v1.5 Auguria 9, rue Alfred Kastler 44300 NANTES FRANCE +33251135012 [email protected] Plan 1 Description générale du module...3 2 Mise en

Plus en détail

MEGA Web Front-End Installation Guide MEGA HOPEX V1R1 FR. Révisé le : 5 novembre 2013 Créé le : 31 octobre 2013. Auteur : Noé LAVALLEE

MEGA Web Front-End Installation Guide MEGA HOPEX V1R1 FR. Révisé le : 5 novembre 2013 Créé le : 31 octobre 2013. Auteur : Noé LAVALLEE MEGA HOPEX V1R1 FR Révisé le : 5 novembre 2013 Créé le : 31 octobre 2013 Auteur : Noé LAVALLEE SOMMAIRE Sommaire... 2 Étape préliminaire... 3 Système d exploitation... 3.Net... 3 Rôles nécessaires... 3

Plus en détail

Développement d'applications Web HTML5 L'art et la manière avec Visual Studio 2015 et TFS

Développement d'applications Web HTML5 L'art et la manière avec Visual Studio 2015 et TFS Avant de commencer 1. Introduction 15 2. HTML5 oui, mais pas que... 15 2.1 HTML5 16 2.2 JavaScript 17 2.3 CSS 18 3. Les outils 18 Préparation des outils et création du projet 1. Introduction 21 2. Team

Plus en détail

DRUPAL Réalisez des développements professionnels avec PHP (2ième édition)

DRUPAL Réalisez des développements professionnels avec PHP (2ième édition) Introduction 1. Les systèmes de gestion de contenu 11 2. Les avantages de Drupal 15 3. Le fonctionnement de Drupal 17 4. L'environnement de développement 20 5. L'installation de Drupal 25 6. Le passage

Plus en détail

FORMATION PcVue. Mise en œuvre de WEBVUE. Journées de formation au logiciel de supervision PcVue 8.1. Lieu : Lycée Pablo Neruda Saint Martin d hères

FORMATION PcVue. Mise en œuvre de WEBVUE. Journées de formation au logiciel de supervision PcVue 8.1. Lieu : Lycée Pablo Neruda Saint Martin d hères FORMATION PcVue Mise en œuvre de WEBVUE Journées de formation au logiciel de supervision PcVue 8.1 Lieu : Lycée Pablo Neruda Saint Martin d hères Centre ressource Génie Electrique Intervenant : Enseignant

Plus en détail

Phone Manager Soutien de l'application OCTOBER 2014 DOCUMENT RELEASE 4.1 SOUTIEN DE L'APPLICATION

Phone Manager Soutien de l'application OCTOBER 2014 DOCUMENT RELEASE 4.1 SOUTIEN DE L'APPLICATION Phone Manager Soutien de l'application OCTOBER 2014 DOCUMENT RELEASE 4.1 SOUTIEN DE L'APPLICATION Salesforce NOTICE The information contained in this document is believed to be accurate in all respects

Plus en détail

Sage CRM. Sage CRM 7.3 Guide du portable

Sage CRM. Sage CRM 7.3 Guide du portable Sage CRM Sage CRM 7.3 Guide du portable Copyright 2014 Sage Technologies Limited, éditeur de ce produit. Tous droits réservés. Il est interdit de copier, photocopier, reproduire, traduire, copier sur

Plus en détail

ECLIPSE ET PDT (Php development tools)

ECLIPSE ET PDT (Php development tools) ECLIPSE ET PDT (Php development tools) Eclipse Eclipse est un IDE (Integrated Development Environment)).C estun projet de la Fondation Eclipse visant à développer tout un environnement de développement

Plus en détail

Présentation du Framework BootstrapTwitter

Présentation du Framework BootstrapTwitter COUARD Kévin HELVIG-LARBRET Blandine Présentation du Framework BootstrapTwitter IUT Nice-Sophia LP-SIL IDSE Octobre 2012 Sommaire I. INTRODUCTION... 3 Définition d'un framework... 3 A propos de BootstrapTwitter...

Plus en détail

Auteur LARDOUX Guillaume Contact [email protected] Année 2014 DEVELOPPEMENT MOBILE AVEC CORDOVA

Auteur LARDOUX Guillaume Contact guillaume.lardoux@epitech.eu Année 2014 DEVELOPPEMENT MOBILE AVEC CORDOVA Auteur LARDOUX Guillaume Contact [email protected] Année 2014 DEVELOPPEMENT MOBILE AVEC CORDOVA Sommaire 1. Introduction 2. Installation 3. Fonctionnement 4. Développement 5. Démonstration 2

Plus en détail

Serveur Acronis Backup & Recovery 10 pour Linux. Update 5. Guide d'installation

Serveur Acronis Backup & Recovery 10 pour Linux. Update 5. Guide d'installation Serveur Acronis Backup & Recovery 10 pour Linux Update 5 Guide d'installation Table des matières 1 Avant l'installation...3 1.1 Composants d'acronis Backup & Recovery 10... 3 1.1.1 Agent pour Linux...

Plus en détail

Didacticiel de mise à jour Web

Didacticiel de mise à jour Web Didacticiel de mise à jour Web Copyright 1995-2012 Esri All rights reserved. Table of Contents Didacticiel : Création d'une application de mise à jour Web.................. 0 Copyright 1995-2012 Esri.

Plus en détail

Sage CRM. 7.2 Guide de Portail Client

Sage CRM. 7.2 Guide de Portail Client Sage CRM 7.2 Guide de Portail Client Copyright 2013 Sage Technologies Limited, éditeur de ce produit. Tous droits réservés. Il est interdit de copier, photocopier, reproduire, traduire, copier sur microfilm,

Plus en détail

3W Academy Programme de Formation Développeur Intégrateur web Total : 400 heures

3W Academy Programme de Formation Développeur Intégrateur web Total : 400 heures 3W Academy Programme de Formation Développeur Intégrateur web Total : 400 heures Objectif global : A l issue de la formation, les stagiaires doivent être opérationnels dans la création d un site internet

Plus en détail

Mise en place Active Directory, DNS Mise en place Active directory, DNS sous Windows Serveur 2008 R2

Mise en place Active Directory, DNS Mise en place Active directory, DNS sous Windows Serveur 2008 R2 BTS SIO Mise en place Active Directory, DNS Mise en place Active directory, DNS sous Windows Serveur 2008 R2 Frédéric Talbourdet Centre de formation Morlaix - GRETA BTS SIO CAHIER D ES CHARGES - Projet

Plus en détail

Automatisation de l administration système

Automatisation de l administration système Automatisation de l administration système Plan Problèmatique : trop de systèmes, trop de solutions Typage des solutions Puppet : gestion de configuration de systèmes Capistrano : déploiement d applications

Plus en détail

Comment développer et intégrer un module à PhpMyLab?

Comment développer et intégrer un module à PhpMyLab? Comment développer et intégrer un module à PhpMyLab? La structure du fichier Afin de conserver une homogénéité et une cohérence entre chaque module, une structure commune est utilisée pour chacun des modules

Plus en détail

1. Installation du Module

1. Installation du Module 1 sur 10 Mise en place du Module Magento V 1.5.7 1. Installation du Module Vous pouvez installer le module de deux façons différentes, en passant par Magento Connect, ou directement via les fichiers de

Plus en détail

4. SERVICES WEB REST 46

4. SERVICES WEB REST 46 4. SERVICES WEB REST 46 REST REST acronyme de REpresentational State Transfert Concept introduit en 2000 dans la thèse de Roy FIELDING Est un style d architecture inspiré de l architecture WEB En 2010,

Plus en détail

TP1. Outils Java Eléments de correction

TP1. Outils Java Eléments de correction c sep. 2008, v2.1 Java TP1. Outils Java Eléments de correction Sébastien Jean Le but de ce TP, sur une séance, est de se familiariser avec les outils de développement et de documentation Java fournis par

Plus en détail

Nouveautés joomla 3 1/14

Nouveautés joomla 3 1/14 Nouveautés joomla 3 1/14 Table des matières 1 Responsive... 1 2 Bootstrap... 1 3 LESS CSS intégré... 1 4. JUI (pour les développeurs d'extensions)... 1 5. Le Mambo days vs le Génial UX... 2 6. 7 étapes

Plus en détail

Serveur d application WebDev

Serveur d application WebDev Serveur d application WebDev Serveur d application WebDev Version 14 Serveur application WebDev - 14-1 - 1208 Visitez régulièrement le site www.pcsoft.fr, espace téléchargement, pour vérifier si des mises

Plus en détail

Guide de déploiement

Guide de déploiement Guide de déploiement Installation du logiciel - Table des matières Présentation du déploiement du logiciel CommNet Server Windows Cluster Windows - Serveur virtuel CommNet Agent Windows Cluster Windows

Plus en détail

FileMaker Server 12. publication Web personnalisée avec XML

FileMaker Server 12. publication Web personnalisée avec XML FileMaker Server 12 publication Web personnalisée avec XML 2007-2012 FileMaker, Inc. Tous droits réservés. FileMaker, Inc. 5201 Patrick Henry Drive Santa Clara, California 95054 FileMaker et Bento sont

Plus en détail

Installation d'un TSE (Terminal Serveur Edition)

Installation d'un TSE (Terminal Serveur Edition) Installation d'un TSE (Terminal Serveur Edition) Par LoiselJP Le 01/05/2013 (R2) 1 Objectifs Le TSE, comprenez Terminal Server Edition est une application de type 'main-frame' de Microsoft qui réside dans

Plus en détail

SHERLOCK 7. Version 1.2.0 du 01/09/09 JAVASCRIPT 1.5

SHERLOCK 7. Version 1.2.0 du 01/09/09 JAVASCRIPT 1.5 SHERLOCK 7 Version 1.2.0 du 01/09/09 JAVASCRIPT 1.5 Cette note montre comment intégrer un script Java dans une investigation Sherlock et les différents aspects de Java script. S T E M M E R I M A G I N

Plus en détail

Documentation technique

Documentation technique Documentation technique Documentation technique Destinataires : Direction EIP Nom du fichier : 2011_TD1_FR_Symbiosys.odt Promotion : 2011 (Epitech 5) Date de création : 10.04.2009 Chef de groupe : Manfred

Plus en détail

Préparation à l installation d Active Directory

Préparation à l installation d Active Directory Laboratoire 03 Étape 1 : Installation d Active Directory et du service DNS Noter que vous ne pourrez pas réaliser ce laboratoire sans avoir fait le précédent laboratoire. Avant de commencer, le professeur

Plus en détail

Titre: Version: Dernière modification: Auteur: Statut: Licence:

Titre: Version: Dernière modification: Auteur: Statut: Licence: Titre: Mise en œuvre de mod_webobjects Version: 2.0 Dernière modification: 2010/09/06 20:00 Auteur: Aurélien Minet Statut: version finale Licence: Creative Commons

Plus en détail

Préparer la synchronisation d'annuaires

Préparer la synchronisation d'annuaires 1 sur 6 16/02/2015 14:24 En utilisant ce site, vous autorisez les cookies à des fins d'analyse, de pertinence et de publicité En savoir plus France (Français) Se connecter Rechercher sur TechNet avec Bing

Plus en détail

Technologies du Web. Créer et héberger un site Web. Pierre Senellart. Page 1 / 26 Licence de droits d usage

Technologies du Web. Créer et héberger un site Web. Pierre Senellart. Page 1 / 26 Licence de droits d usage Technologies du Web Créer et héberger un site Web Page 1 / 26 Plan Planification Choisir une solution d hébergement Administration Développement du site Page 2 / 26 Cahier des charges Objectifs du site

Plus en détail

FileMaker Server 11. Publication Web personnalisée avec XML et XSLT

FileMaker Server 11. Publication Web personnalisée avec XML et XSLT FileMaker Server 11 Publication Web personnalisée avec XML et XSLT 2007-2010 FileMaker, Inc. Tous droits réservés. FileMaker, Inc. 5201 Patrick Henry Drive Santa Clara, Californie 95054 FileMaker est une

Plus en détail

SERVEUR DÉDIÉ DOCUMENTATION

SERVEUR DÉDIÉ DOCUMENTATION SERVEUR DÉDIÉ DOCUMENTATION Release 5.0.6.0 19 Juillet 2013 Copyright 2013 GIANTS Software GmbH, All Rights Reserved. 1/9 CHANGE LOG Correction de bug divers (5.0.6.0) Ajout d une option de relance automatique

Plus en détail

Serveur d'application Client HTML/JS. Apache Thrift Bootcamp

Serveur d'application Client HTML/JS. Apache Thrift Bootcamp Serveur d'application Client HTML/JS Apache Thrift Bootcamp Pré-requis La liste ci-dessous de logiciels doit être installée et opérationnelle sur la machine des participants : Compilateur thrift http://thrift.apache.org/

Plus en détail

Comment Utiliser les Versions, les Modification, les Comparaisons, Dans les Documents

Comment Utiliser les Versions, les Modification, les Comparaisons, Dans les Documents Comment Utiliser les Versions, les Modification, les Comparaisons, Dans les Documents Diffusé par Le Projet Documentation OpenOffice.org Table des Matières 1. Les Versions...3 2. Les Modifications...5

Plus en détail

Avant-propos 1. Avant-propos...3 2. Organisation du guide...3 3. À qui s'adresse ce guide?...4

Avant-propos 1. Avant-propos...3 2. Organisation du guide...3 3. À qui s'adresse ce guide?...4 Les exemples cités tout au long de cet ouvrage sont téléchargeables à l'adresse suivante : http://www.editions-eni.fr. Saisissez la référence ENI de l'ouvrage EP5EJAV dans la zone de recherche et validez.

Plus en détail

CA ARCserve Backup Patch Manager pour Windows

CA ARCserve Backup Patch Manager pour Windows CA ARCserve Backup Patch Manager pour Windows Manuel de l'utilisateur r16 La présente documentation, qui inclut des systèmes d'aide et du matériel distribués électroniquement (ci-après nommés "Documentation"),

Plus en détail

Nouveautés de Drupal 8. Léon Cros @chipway

Nouveautés de Drupal 8. Léon Cros @chipway Nouveautés de Drupal 8 Léon Cros @chipway.. Léon Cros Communauté Drupal Président de l'association Drupal France et Francophonie @chipway / chipway-drupal. JDLL 2014. Chipway : Spécialiste Formations Drupal

Plus en détail

Projet 2. Gestion des services enseignants CENTRE D ENSEIGNEMENT ET DE RECHERCHE EN INFORMATIQUE. G r o u p e :

Projet 2. Gestion des services enseignants CENTRE D ENSEIGNEMENT ET DE RECHERCHE EN INFORMATIQUE. G r o u p e : CENTRE D ENSEIGNEMENT ET DE RECHERCHE EN INFORMATIQUE Projet 2 Gestion des services enseignants G r o u p e : B E L G H I T Y a s m i n e S A N C H E Z - D U B R O N T Y u r i f e r M O N T A Z E R S i

Plus en détail

Livre Blanc WebSphere Transcoding Publisher

Livre Blanc WebSphere Transcoding Publisher Livre Blanc WebSphere Transcoding Publisher Introduction WebSphere Transcoding Publisher vous permet d'offrir aux utilisateurs des informations Web adaptées à leurs besoins. Il vous permet, par exemple,

Plus en détail

Installation et configuration de Vulture Lundi 2 février 2009

Installation et configuration de Vulture Lundi 2 février 2009 Installation et configuration de Vulture Lundi 2 février 2009 V1.0 Page 1/15 Tables des matières A. Informations (Page. 3/15) B. Installation (Page. 3/15) 1- Téléchargement des paquets nécessaires. 2-

Plus en détail

Introduction aux concepts d ez Publish

Introduction aux concepts d ez Publish Introduction aux concepts d ez Publish Tutoriel rédigé par Bergfrid Skaara. Traduit de l Anglais par Benjamin Lemoine Mercredi 30 Janvier 2008 Sommaire Concepts d ez Publish... 3 Système de Gestion de

Plus en détail

Programme ASI Développeur

Programme ASI Développeur Programme ASI Développeur Titre de niveau II inscrit au RNCP Objectifs : Savoir utiliser un langage dynamique dans la création et la gestion d un site web. Apprendre à développer des programmes en objet.

Plus en détail

7.0 Guide de la solution Portable sans fil

7.0 Guide de la solution Portable sans fil 7.0 Guide de la solution Portable sans fil Copyright 2010 Sage Technologies Limited, éditeur de ce produit. Tous droits réservés. Il est interdit de copier, photocopier, reproduire, traduire, copier sur

Plus en détail

Guide de l'utilisateur

Guide de l'utilisateur BlackBerry Internet Service Version: 4.5.1 Guide de l'utilisateur Publié : 2014-01-08 SWD-20140108170135662 Table des matières 1 Mise en route...7 À propos des formules d'abonnement pour BlackBerry Internet

Plus en détail

Présentation du logiciel Cobian Backup

Présentation du logiciel Cobian Backup Présentation du logiciel Cobian Backup Cobian Backup est un utilitaire qui sert à sauvegarder (manuelle et automatiquement) des fichiers et des dossiers de votre choix. Le répertoire de sauvegarde peut

Plus en détail

Slony1 2.1 Londiste 3

Slony1 2.1 Londiste 3 Slony1 2.1 Londiste 3 Cédric Villemain FRANCE PgConf.EU 2011 20/10/2011, Amsterdam License Creative Commons: Attribution-Non-Commercial-Share Alike 2.5 You are free: to copy, distribute,

Plus en détail

Initiation à html et à la création d'un site web

Initiation à html et à la création d'un site web Initiation à html et à la création d'un site web Introduction : Concevoir un site web consiste à définir : - l'emplacement où ce site sera hébergé - à qui ce site s'adresse - le design des pages qui le

Plus en détail

Web Front-End Installation Guide HOPEX V1R2-V1R3 FR. Révisé le : 17 août 2015 Créé le : 12 mars 2014. Olivier SCHIAVI

Web Front-End Installation Guide HOPEX V1R2-V1R3 FR. Révisé le : 17 août 2015 Créé le : 12 mars 2014. Olivier SCHIAVI Révisé le : 17 août 2015 Créé le : 12 mars 2014 Auteurs : Noé LAVALLEE Olivier SCHIAVI SOMMAIRE Sommaire... 2 Conditions préalables... 4 Système d exploitation... 4.Net... 4 Rôles nécessaires... 5 Configuration

Plus en détail

INF2015 Développement de logiciels dans un environnement Agile. Examen intra 20 février 2014 17:30 à 20:30

INF2015 Développement de logiciels dans un environnement Agile. Examen intra 20 février 2014 17:30 à 20:30 Examen intra 20 février 2014 17:30 à 20:30 Nom, prénom : Code permanent : Répondez directement sur le questionnaire. Question #1 5% Quelle influence peut avoir le typage dynamique sur la maintenabilité

Plus en détail

Création d'un site dynamique en PHP avec Dreamweaver et MySQL

Création d'un site dynamique en PHP avec Dreamweaver et MySQL Création d'un site dynamique en PHP avec Dreamweaver et MySQL 1. Création et configuration du site 1.1. Configuration de Dreamweaver Avant de commencer, il est nécessaire de connaître l'emplacement du

Plus en détail

Bonnes pratiques de développement JavaScript

Bonnes pratiques de développement JavaScript Bonnes pratiques de développement JavaScript Titre présentation Conférencier François Béliveau Romain Dorgueil A propos de nous... François Béliveau Développeur web depuis 8 ans Utilise symfony depuis

Plus en détail

1 / Introduction. 2 / Gestion des comptes cpanel. Guide débuter avec WHM. 2.1Créer un package. 2.2Créer un compte cpanel

1 / Introduction. 2 / Gestion des comptes cpanel. Guide débuter avec WHM. 2.1Créer un package. 2.2Créer un compte cpanel Guide débuter avec WHM 1 / Introduction WHM signifie Web Host Manager (ou gestionnaire d'hébergement web). WHM va donc vous permettre de gérer des comptes d'hébergement pour vos clients. (création de compte,

Plus en détail

ASP.NET MVC 4 Développement d'applications Web en C# - Concepts et bonnes pratiques

ASP.NET MVC 4 Développement d'applications Web en C# - Concepts et bonnes pratiques Introduction 1. Introduction 11 2. La plateforme de développement web de Microsoft 11 3. Définition du modèle de programmation MVC 14 4. L'historique d'asp.net MVC 17 4.1 ASP.NET MVC 1 (2008) 17 4.2 ASP.NET

Plus en détail

2010 Ing. Punzenberger COPA-DATA GmbH. Tous droits réservés.

2010 Ing. Punzenberger COPA-DATA GmbH. Tous droits réservés. 2010 Ing. Punzenberger COPA-DATA GmbH Tous droits réservés. La distribution et/ou reproduction de ce document ou partie de ce document sous n'importe quelle forme n'est autorisée qu'avec la permission

Plus en détail

The Components Book Version: 2.7

The Components Book Version: 2.7 The Components Book Version:. generated on September, 0 The Components Book (.) This work is licensed under the Attribution-Share Alike.0 Unported license (http://creativecommons.org/ licenses/by-sa/.0/).

Plus en détail

Gestion d identités PSL Exploitation IdP Authentic

Gestion d identités PSL Exploitation IdP Authentic Gestion d identités PSL Exploitation IdP Authentic Entr ouvert SCOP http ://www.entrouvert.com Table des matières 1 Arrêt et démarrage 2 2 Configuration 2 2.1 Intégration à la fédération............................

Plus en détail

Phone Manager Soutien de l'application OCTOBER 2014 DOCUMENT RELEASE 4.1 SOUTIEN DE L'APPLICATION

Phone Manager Soutien de l'application OCTOBER 2014 DOCUMENT RELEASE 4.1 SOUTIEN DE L'APPLICATION Phone Manager Soutien de l'application OCTOBER 2014 DOCUMENT RELEASE 4.1 SOUTIEN DE L'APPLICATION Sage CRM NOTICE The information contained in this document is believed to be accurate in all respects but

Plus en détail

Guide de configuration de SQL Server pour BusinessObjects Planning

Guide de configuration de SQL Server pour BusinessObjects Planning Guide de configuration de SQL Server pour BusinessObjects Planning BusinessObjects Planning XI Release 2 Copyright 2007 Business Objects. Tous droits réservés. Business Objects est propriétaire des brevets

Plus en détail

Optimiser les performances d un site web. Nicolas Chevallier Camille Roux

Optimiser les performances d un site web. Nicolas Chevallier Camille Roux Optimiser les performances d un site web Nicolas Chevallier Camille Roux Intellicore Tech Talks Des conférences pour partager son savoir Le mardi au CICA Sophia Antipolis http://techtalks.intellicore.net

Plus en détail

Harp - Basculement des élèves en début d année

Harp - Basculement des élèves en début d année Ministère de l'education Nationale - Académie de Grenoble - Région Rhône-Alpes C entre A cadémique de R essources et de M aintenance I nformatique Le 04/09/2012 Equipements - Assistance - Maintenance Code

Plus en détail

v7.1 SP2 Guide des Nouveautés

v7.1 SP2 Guide des Nouveautés v7.1 SP2 Guide des Nouveautés Copyright 2012 Sage Technologies Limited, éditeur de ce produit. Tous droits réservés. Il est interdit de copier, photocopier, reproduire, traduire, copier sur microfilm,

Plus en détail

Faire fonctionner symfony sous wamp et windows avec des vhost. Installation de wamp

Faire fonctionner symfony sous wamp et windows avec des vhost. Installation de wamp Vous avez certainement déjà souhaiter gérer plusieurs projets symfony sur votre machine après avoir installé Wamp Server. Ce n'est pas simple en apparence, un seul dossier www et des sous répertoire en

Plus en détail

Stratégie de groupe dans Active Directory

Stratégie de groupe dans Active Directory Stratégie de groupe dans Active Directory 16 novembre 2012 Dans ce document vous trouverez des informations fondamentales sur les fonctionnements de Active Directory, et de ses fonctionnalités, peut être

Plus en détail

Sage 100 CRM - Guide d installation Version 8.01. Mise à jour : 2015 version 8

Sage 100 CRM - Guide d installation Version 8.01. Mise à jour : 2015 version 8 Sage 100 CRM - Guide d installation Version 8.01 Mise à jour : 2015 version 8 Composition du progiciel Votre progiciel est composé d un boîtier de rangement comprenant : le cédérom sur lequel est enregistré

Plus en détail

Mettre à jour PrestaShop

Mettre à jour PrestaShop Mettre à jour PrestaShop De nouvelles versions de PrestaShop sortent régulièrement. Certaines sont des versions majeures, la plupart sont mineures, mais toutes apportent leur lot d'innovation, d'amélioration

Plus en détail

GUIDE DE L UTILISATEUR Recoveo Récupérateur de données

GUIDE DE L UTILISATEUR Recoveo Récupérateur de données Table d index : 1. Généralités 1 2. Installation du logiciel 2 3. Suppression du logiciel 2 4. Activation du logiciel 3 5. Récupération de données perdues 4 6. Interprétation du résultat 6 7. Enregistrement

Plus en détail

Installation de Windows 2003 Serveur

Installation de Windows 2003 Serveur Installation de Windows 2003 Serveur Introduction Ce document n'explique pas les concepts, il se contente de décrire, avec copies d'écran, la méthode que j'utilise habituellement pour installer un Windows

Plus en détail

Joomla! Création et administration d'un site web - Version numérique

Joomla! Création et administration d'un site web - Version numérique Avant-propos 1. Objectifs du livre 15 1.1 Orientation 15 1.2 À qui s adresse ce livre? 16 2. Contenu de l ouvrage 17 3. Conclusion 18 Introduction 1. Un peu d histoire pour commencer... 19 1.1 Du web statique

Plus en détail

Utiliser Access ou Excel pour gérer vos données

Utiliser Access ou Excel pour gérer vos données Page 1 of 5 Microsoft Office Access Utiliser Access ou Excel pour gérer vos données S'applique à : Microsoft Office Access 2007 Masquer tout Les programmes de feuilles de calcul automatisées, tels que

Plus en détail

arcopole Studio Annexe 7 Architectures Site du programme arcopole : www.arcopole.fr

arcopole Studio Annexe 7 Architectures Site du programme arcopole : www.arcopole.fr 4 arcopole Studio Annexe 7 Architectures Site du programme arcopole : www.arcopole.fr Auteur du document : Esri France Version de la documentation : 1.2 Date de dernière mise à jour : 26/02/2015 Sommaire

Plus en détail

Nuxeo Enterprise Platform: Guide utilisateur

Nuxeo Enterprise Platform: Guide utilisateur Nuxeo Enterprise Platform: Guide utilisateur Copyright 2000-2008, Nuxeo SAS. Vous pouvez copier, diffuser et/ou modifier ce document selon les termes de la GNU Free Documentation License, Version 1.2;

Plus en détail

Oracle Database SQL Developer Guide D'Installation Release 4.0 E38928-06

Oracle Database SQL Developer Guide D'Installation Release 4.0 E38928-06 Oracle Database SQL Developer Guide D'Installation Release 4.0 E38928-06 Pour accéder à la documentation détaillée de SQL Developer : voir Oracle Database SQL Developer Installation Guide Installer Oracle

Plus en détail

SUGARCRM Sugar Open Source Guide d Installation de French SugarCRM Open Source Version 4.2

SUGARCRM Sugar Open Source Guide d Installation de French SugarCRM Open Source Version 4.2 SUGARCRM Sugar Open Source Guide d Installation de French SugarCRM Open Source Version 4.2 Version 1.0.5 Mentions légales Mentions légales Ce document est susceptible de modification à tout moment sans

Plus en détail

OWASP Open Web Application Security Project. Jean-Marc Robert Génie logiciel et des TI

OWASP Open Web Application Security Project. Jean-Marc Robert Génie logiciel et des TI OWASP Open Web Application Security Project Jean-Marc Robert Génie logiciel et des TI A1: Injection Une faille d'injection, telle l'injection SQL, OS et LDAP, se produit quand une donnée non fiable est

Plus en détail

Chapitre 1 : Introduction aux bases de données

Chapitre 1 : Introduction aux bases de données Chapitre 1 : Introduction aux bases de données Les Bases de Données occupent aujourd'hui une place de plus en plus importante dans les systèmes informatiques. Les Systèmes de Gestion de Bases de Données

Plus en détail

Magento. Magento. Réussir son site e-commerce. Réussir son site e-commerce BLANCHARD. Préface de Sébastien L e p e r s

Magento. Magento. Réussir son site e-commerce. Réussir son site e-commerce BLANCHARD. Préface de Sébastien L e p e r s Mickaël Mickaël BLANCHARD BLANCHARD Préface de Sébastien L e p e r s Magento Préface de Sébastien L e p e r s Magento Réussir son site e-commerce Réussir son site e-commerce Groupe Eyrolles, 2010, ISBN

Plus en détail

BTS SIO SISR3 TP 1-I Le service Web [1] Le service Web [1]

BTS SIO SISR3 TP 1-I Le service Web [1] Le service Web [1] SISR3 TP 1-I Le service Web [1] Objectifs Comprendre la configuration d'un service Web Définir les principaux paramètres d'exécution du serveur Gérer les accès aux pages distribuées Mettre à disposition

Plus en détail

1 Résolution de nom... 2 1.1 Introduction à la résolution de noms... 2. 1.2 Le système DNS... 2. 1.3 Les types de requêtes DNS...

1 Résolution de nom... 2 1.1 Introduction à la résolution de noms... 2. 1.2 Le système DNS... 2. 1.3 Les types de requêtes DNS... Table des matières 1 Résolution de nom... 2 1.1 Introduction à la résolution de noms... 2 1.2 Le système DNS... 2 1.3 Les types de requêtes DNS... 4 1.4 Configuration des clients DNS... 8 1.4.1 Résolution

Plus en détail

Préconisations Techniques & Installation de Gestimum ERP

Préconisations Techniques & Installation de Gestimum ERP 2015 Préconisations Techniques & Installation de Gestimum ERP 19/06/2015 1 / 30 Table des Matières Préambule... 4 Prérequis matériel (Recommandé)... 4 Configuration minimum requise du serveur (pour Gestimum

Plus en détail

S7 Le top 10 des raisons d utiliser PHP pour moderniser votre existant IBM i

S7 Le top 10 des raisons d utiliser PHP pour moderniser votre existant IBM i Modernisation IBM i Nouveautés 2014-2015 IBM Power Systems - IBM i 19 et 20 mai 2015 IBM Client Center, Bois-Colombes S7 Le top 10 des raisons d utiliser PHP pour moderniser votre existant IBM i Mardi

Plus en détail

Création de Sous-Formulaires

Création de Sous-Formulaires Création de Sous-Formulaires Révision 1.01 du 02/01/04 Réalisé avec : OOo 1.1.0 Plate-forme / Os : Toutes Distribué par le projet Fr.OpenOffice.org Table des Matières 1 But de ce how-to...3 2 Pré-requis...3

Plus en détail

DA MOTA Anthony - Comparaison de technologies : PhoneGap VS Cordova

DA MOTA Anthony - Comparaison de technologies : PhoneGap VS Cordova DA MOTA Anthony - Comparaison de technologies : PhoneGap VS Cordova I. Introduction Dans une période où la plasticité peut aider à réduire les coûts de développement de projets comme des applications mobile,

Plus en détail

Les GPO 2012 server R2 (appliqués à Terminal Serveur Edition)

Les GPO 2012 server R2 (appliqués à Terminal Serveur Edition) Les GPO 2012 server R2 (appliqués à Terminal Serveur Edition) Par LoiselJP Le 01/08/2014 Rev. : 01/03/2015 1 Objectifs Dès qu il s agit de placer des paramètres particuliers, on annonce «il suffit d utiliser

Plus en détail

1-Introduction 2. 2-Installation de JBPM 3. 2-JBPM en action.7

1-Introduction 2. 2-Installation de JBPM 3. 2-JBPM en action.7 Sommaire 1-Introduction 2 1-1- BPM (Business Process Management)..2 1-2 J-Boss JBPM 2 2-Installation de JBPM 3 2-1 Architecture de JOBSS JBPM 3 2-2 Installation du moteur JBoss JBPM et le serveur d application

Plus en détail

27/11/12 Nature. SDK Python et Java pour le développement de services ACCORD Module(s)

27/11/12 Nature. SDK Python et Java pour le développement de services ACCORD Module(s) Propriétés du Document Source du Document SDK_accords.odt Titre du Document SDK Python et Java pour le développement de services ACCORD Module(s) PyaccordsSDK, JaccordsSDK Responsable Prologue Auteur(s)

Plus en détail

Cyberclasse L'interface web pas à pas

Cyberclasse L'interface web pas à pas Cyberclasse L'interface web pas à pas Version 1.4.18 Janvier 2008 Remarque préliminaire : les fonctionnalités décrites dans ce guide sont celles testées dans les écoles pilotes du projet Cyberclasse; il

Plus en détail

Mise en route de PRTG Network Monitor 9 2011 Paessler AG

Mise en route de PRTG Network Monitor 9 2011 Paessler AG Mise en route de PRTG Network Monitor 9 2011 Paessler AG All rights reserved. No parts of this work may be reproduced in any form or by any means graphic, electronic, or mechanical, including photocopying,

Plus en détail

Installation d'un serveur RADIUS

Installation d'un serveur RADIUS Installation d'un serveur RADIUS Par LoiselJP Le 22/05/2013 1 Objectifs Ce document décrit le plus succinctement possible une manière, parmi d'autres, de créer un serveur Radius. L installation ici proposée

Plus en détail

TeamViewer 9 Manuel Management Console

TeamViewer 9 Manuel Management Console TeamViewer 9 Manuel Management Console Rév 9.2-07/2014 TeamViewer GmbH Jahnstraße 30 D-73037 Göppingen www.teamviewer.com Sommaire 1 A propos de la TeamViewer Management Console... 4 1.1 A propos de la

Plus en détail

WebDAV en 2 minutes. Tous ces objectifs sont complémentaires et ils sont atteints grâce au seul protocole WebDAV. Scénarii

WebDAV en 2 minutes. Tous ces objectifs sont complémentaires et ils sont atteints grâce au seul protocole WebDAV. Scénarii WebDAV en 2 minutes le but affirmé du groupe de travail WebDAV (DAV) est (pour ses concepteurs) de "définir les extensions de HTTP nécessaires pour assurer la disponibilité d'outils WEB de création collective

Plus en détail

MEDIAplus elearning. version 6.6

MEDIAplus elearning. version 6.6 MEDIAplus elearning version 6.6 L'interface d administration MEDIAplus Sommaire 1. L'interface d administration MEDIAplus... 5 2. Principes de l administration MEDIAplus... 8 2.1. Organisations et administrateurs...

Plus en détail

Méthode de Test. Pour WIKIROUTE. Rapport concernant les méthodes de tests à mettre en place pour assurer la fiabilité de notre projet annuel.

Méthode de Test. Pour WIKIROUTE. Rapport concernant les méthodes de tests à mettre en place pour assurer la fiabilité de notre projet annuel. Méthode de Test Pour WIKIROUTE Rapport concernant les méthodes de tests à mettre en place pour assurer la fiabilité de notre projet annuel. [Tapez le nom de l'auteur] 10/06/2009 Sommaire I. Introduction...

Plus en détail

L envoi d un formulaire par courriel. Configuration requise... 236 Mail Texte... 237 Mail HTML... 242 Check-list... 248

L envoi d un formulaire par courriel. Configuration requise... 236 Mail Texte... 237 Mail HTML... 242 Check-list... 248 L envoi d un formulaire par courriel Configuration requise... 236 Mail Texte... 237 Mail HTML... 242 Check-list... 248 Chapitre 9 L envoi d un formulaire par courriel L envoi par courriel d informations

Plus en détail

FileMaker Server 13. Guide de démarrage

FileMaker Server 13. Guide de démarrage FileMaker Server 13 Guide de démarrage 2007-2013 FileMaker, Inc. Tous droits réservés. FileMaker, Inc. 5201 Patrick Henry Drive Santa Clara, Californie 95054 FileMaker et Bento sont des marques commerciales

Plus en détail

Procédure d'installation complète de Click&Decide sur un serveur

Procédure d'installation complète de Click&Decide sur un serveur Procédure d'installation complète de Click&Decide sur un serveur Prérequis du serveur : Windows 2008 R2 or greater (64-bits) Windows 2012 (64-bits) - Le composant IIS (Internet Information Services) de

Plus en détail