Sie sind auf Seite 1von 200

Symfony2 - Un tutoriel pour dbuter avec le framework Symfony2

Par Alexandre Bacco (winzou)

www.siteduzero.com

Licence Creative Commons BY-NC-SA 2.0 Dernire mise jour le 3/08/2012

2/201

Sommaire
Sommaire ........................................................................................................................................... Partager .............................................................................................................................................. Symfony2 - Un tutoriel pour dbuter avec le framework Symfony2 ................................................... Partie 1 : Vue d'ensemble de Symfony2 ............................................................................................. 2 4 6 6

Symfony2, un framework PHP .......................................................................................................................................... 7


Qu'est-ce qu'un framework ? ....................................................................................................................................................................................... 7 L'objectif d'un framework ............................................................................................................................................................................................. 7 Pesons le Pour et le Contre ........................................................................................................................................................................................ 7 Alors, convaincu ? ....................................................................................................................................................................................................... 8 Qu'est-ce que Symfony2 ? .......................................................................................................................................................................................... 8 Un framework .............................................................................................................................................................................................................. 8 Un framework populaire .............................................................................................................................................................................................. 8 Un framework populaire et franais ............................................................................................................................................................................. 8 Tlcharger Symfony2 ................................................................................................................................................................................................ 8 Obtenir Symfony2 ........................................................................................................................................................................................................ 8 Vrifier votre configuration de PHP ............................................................................................................................................................................. 9 Vrifier l'installation de PHP en console .................................................................................................................................................................... 10

Vous avez dit Symfony2 ? ............................................................................................................................................... 11


L'architecture des fichiers .......................................................................................................................................................................................... 11 Liste des rpertoires .................................................................................................................................................................................................. 11 Le contrleur frontal ................................................................................................................................................................................................... 11 L'architecture conceptuelle ........................................................................................................................................................................................ 12 Architecture MVC ...................................................................................................................................................................................................... 12 Parcours d'une requte dans Symfony2 ................................................................................................................................................................... 13 Symfony2 et ses bundles .......................................................................................................................................................................................... 14 La dcoupe en bundles ....................................................................................................................................................................................... 14 La structure d'un bundle ............................................................................................................................................................................................ 15

Utilisons la console pour crer un bundle ....................................................................................................................... 15


Utilisation de la console ............................................................................................................................................................................................ 16 Sous Windows .......................................................................................................................................................................................................... 16 Sous Linux et Mac ..................................................................................................................................................................................................... 17 A quoi a sert ? .......................................................................................................................................................................................................... 17 Comment a marche ? .............................................................................................................................................................................................. 17 Crons notre bundle .................................................................................................................................................................................................. 17 Tout est bundle .......................................................................................................................................................................................................... 17 Que s'est-il pass ? ................................................................................................................................................................................................... 20 A retenir ..................................................................................................................................................................................................................... 21

Partie 2 : Les bases de Symfony2 : Hello World ......................................................................... 21


Mon premier Hello World avec Symfony2 ....................................................................................................................... 22
Crons notre route .................................................................................................................................................................................................... 22 Le routeur (ou router ) ? Une route ? ................................................................................................................................................................... 22 Crons notre contrleur ............................................................................................................................................................................................. 24 Le rle du contrleur ................................................................................................................................................................................................. 24 Crons notre contrleur ............................................................................................................................................................................................. 24 Crons notre template Twig ...................................................................................................................................................................................... 25 Les templates avec Twig ........................................................................................................................................................................................... 25 Utiliser Twig avec Symfony2 ..................................................................................................................................................................................... 25 Notre objectif : crer un blog ..................................................................................................................................................................................... 27 Le fil conducteur : un blog ......................................................................................................................................................................................... 27 Notre blog .................................................................................................................................................................................................................. 27 Un peu de nettoyage ................................................................................................................................................................................................. 27 Schma de dveloppement sous Symfony2 ............................................................................................................................................................. 27

Le routeur de Symfony2 .................................................................................................................................................. 29


Le fonctionnement ..................................................................................................................................................................................................... 29 Fonctionnement du routeur ....................................................................................................................................................................................... 29 Convention pour le nom du contrleur ...................................................................................................................................................................... 30 Les routes de base .................................................................................................................................................................................................... 30 Crer une route ......................................................................................................................................................................................................... 30 Crer une route avec des paramtres ....................................................................................................................................................................... 30 Les routes avances ................................................................................................................................................................................................. 32 Crer une route avec des paramtres et leurs contraintes ....................................................................................................................................... 32 Utiliser des paramtres facultatifs ............................................................................................................................................................................. 32 Utiliser des paramtres systme ......................................................................................................................................................................... 33 Ajouter un prfixe lors de l'import de nos routes ....................................................................................................................................................... 33 Gnrer des URL ...................................................................................................................................................................................................... 33 Pourquoi gnrer des URL ? ..................................................................................................................................................................................... 33 Comment gnrer des URL ? ................................................................................................................................................................................... 33 Application : les routes de notre blog ........................................................................................................................................................................ 34 Construction des routes ............................................................................................................................................................................................ 34 Rcapitulatif .............................................................................................................................................................................................................. 35

Les contrleurs avec Symfony2 ...................................................................................................................................... 37


Le rle du contrleur ................................................................................................................................................................................................. 37 Retourner une rponse ............................................................................................................................................................................................. 37 Manipuler l'objet Request .................................................................................................................................................................................... 37

www.siteduzero.com

Sommaire

3/201

Les paramtres de la requte ................................................................................................................................................................................... 37 Les autres mthodes de l'objet Request ............................................................................................................................................................. 39 Manipuler l'objet Response ................................................................................................................................................................................. 39 Dcomposition de la construction d'un objet Response ...................................................................................................................................... 39 Rponses et vues ..................................................................................................................................................................................................... 40 Rponse et redirection .............................................................................................................................................................................................. 40 Changer le Content-type de la rponse .............................................................................................................................................................. 41 Les diffrents services ............................................................................................................................................................................................... 41 Qu'est-ce qu'un service ? .......................................................................................................................................................................................... 41 Accder aux services ................................................................................................................................................................................................ 41 Brve liste des services ............................................................................................................................................................................................ 42 Application : le contrleur de notre blog .................................................................................................................................................................... 45 Construction du contrleur ........................................................................................................................................................................................ 45 retenir ..................................................................................................................................................................................................................... 47 Testons-le .................................................................................................................................................................................................................. 47

Le moteur de template Twig ............................................................................................................................................ 48


Les templates Twig .................................................................................................................................................................................................... 49 Intrt ........................................................................................................................................................................................................................ 49 En pratique ................................................................................................................................................................................................................ 49 savoir ..................................................................................................................................................................................................................... 50 Afficher des variables ................................................................................................................................................................................................ 50 Syntaxe de base pour afficher des variables ............................................................................................................................................................ 50 Prcisions sur la syntaxe {{ objet.attribut }} ............................................................................................................................................................... 51 Les filtres utiles ......................................................................................................................................................................................................... 51 Twig et scurit ......................................................................................................................................................................................................... 52 Les variables globales ............................................................................................................................................................................................... 52 Structures de contrle et expressions ....................................................................................................................................................................... 52 Les structures de contrle ......................................................................................................................................................................................... 52 Les tests utiles .......................................................................................................................................................................................................... 54 Hriter et inclure des templates ................................................................................................................................................................................ 54 L'hritage de template ............................................................................................................................................................................................... 54 Qu'est-ce que l'on vient de faire ? ............................................................................................................................................................................. 55 Le modle triple hritage ..................................................................................................................................................................................... 55 L'inclusion de templates ............................................................................................................................................................................................ 56 L'inclusion de contrleurs .......................................................................................................................................................................................... 57 Application : les templates de notre blog .................................................................................................................................................................. 58 Layout gnral ........................................................................................................................................................................................................... 58 Layout du bundle ....................................................................................................................................................................................................... 60 Les templates finaux ................................................................................................................................................................................................. 60 Conclusion ................................................................................................................................................................................................................ 65

Partie 3 : Grer la base de donnes avec Doctrine2 ........................................................................ 66


La couche mtier : les entits ......................................................................................................................................... 67
Notions d'ORM : fini les requtes, utilisons des objets ............................................................................................................................................. 67 Dfinition d'ORM : Object-Relational Mapper ............................................................................................................................................................ 67 Vos donnes sont des objets .................................................................................................................................................................................... 67 Crer une premire entit avec Doctrine2 ................................................................................................................................................................ 67 Une entit, c'est juste un objet .................................................................................................................................................................................. 67 Une entit, c'est juste un objet mais avec des commentaires ! ............................................................................................................................. 69 Gnrer une entit : le gnrateur la rescousse ! .................................................................................................................................................. 69 Affiner notre entit avec de la logique mtier ............................................................................................................................................................ 71 Conclusion ................................................................................................................................................................................................................ 72 Tout sur le mapping ! ................................................................................................................................................................................................. 72 L'annotation Entity ..................................................................................................................................................................................................... 72 L'annotation Table ..................................................................................................................................................................................................... 73 L'annotation Column ................................................................................................................................................................................................. 73

Manipuler ses entits avec Doctrine2 ............................................................................................................................. 75


Matrialiser les tables en base de donnes .............................................................................................................................................................. 75 Crer la table correspondante dans la base de donnes ......................................................................................................................................... 75 Modifier une entit ..................................................................................................................................................................................................... 76 A retenir ..................................................................................................................................................................................................................... 77 Enregistrer ses entits avec l'EntityManager ............................................................................................................................................................ 77 Les services Doctrine2 .............................................................................................................................................................................................. 77 Enregistrer ses entits en base de donnes ............................................................................................................................................................. 78 Rcuprer ses entits avec un EntityRepository ....................................................................................................................................................... 82

Les relations entre entits avec Doctrine2 ...................................................................................................................... 84


Prsentation .............................................................................................................................................................................................................. 84 Prsentation .............................................................................................................................................................................................................. 84 Les diffrents types de relations ................................................................................................................................................................................ 84 Notions techniques d'ORM savoir .......................................................................................................................................................................... 84 Rien n'est magique .................................................................................................................................................................................................... 84 Relation One-To-One ................................................................................................................................................................................................ 84 Prsentation .............................................................................................................................................................................................................. 84 Dfinition de la relation dans les entits .................................................................................................................................................................... 85 Exemple d'utilisation .................................................................................................................................................................................................. 87 Relation Many-To-One .............................................................................................................................................................................................. 89 Prsentation .............................................................................................................................................................................................................. 89 Dfinition de la relation dans les entits .................................................................................................................................................................... 91 Exemple d'utilisation .................................................................................................................................................................................................. 92 Relation Many-To-Many ............................................................................................................................................................................................. 93 Prsentation .............................................................................................................................................................................................................. 94 Dfinition de la relation dans les entits .................................................................................................................................................................... 95 Remplissons la base de donnes ............................................................................................................................................................................. 97

www.siteduzero.com

Sommaire

4/201

Exemples d'utilisation ................................................................................................................................................................................................ 98 Relation Many-To-Many avec attributs .................................................................................................................................................................... 100 Prsentation ............................................................................................................................................................................................................ 100 Dfinition de la relation dans les entits .................................................................................................................................................................. 101 Remplissons la base de donnes ........................................................................................................................................................................... 103 Exemple d'utilisation ................................................................................................................................................................................................ 104 Les relations bidirectionnelles ................................................................................................................................................................................. 106 Prsentation ............................................................................................................................................................................................................ 106 Dfinition de la relation dans les entits .................................................................................................................................................................. 106

Rcuprer ses entits avec Doctrine2 .......................................................................................................................... 109


Le rle des Repository ............................................................................................................................................................................................. 110 Dfinition .................................................................................................................................................................................................................. 110 Les mthodes de rcupration des entits .............................................................................................................................................................. 110 Les mthodes de rcupration de base .................................................................................................................................................................. 110 Dfinition .................................................................................................................................................................................................................. 110 Les mthodes normales .......................................................................................................................................................................................... 110 Les mthodes magiques ......................................................................................................................................................................................... 112 Les mthodes de rcupration personnelles ........................................................................................................................................................... 112 La thorie ................................................................................................................................................................................................................. 112 Utilisation du Doctrine Query Language (DQL) ....................................................................................................................................................... 117 Utiliser les jointures dans nos requtes ................................................................................................................................................................... 118 Pourquoi utiliser les jointures ? ................................................................................................................................................................................ 118 Comment faire des jointures avec le QueryBuilder ? .............................................................................................................................................. 118 Comment utiliser les jointures ? .............................................................................................................................................................................. 119 Application : les entits de notre blog ..................................................................................................................................................................... 120 Plan d'attaque ......................................................................................................................................................................................................... 120 vous de jouer ! ..................................................................................................................................................................................................... 120 Le code .................................................................................................................................................................................................................... 120

TP : Les entits de notre blog ....................................................................................................................................... 124


Cration des entits ................................................................................................................................................................................................ 125 Thorie .................................................................................................................................................................................................................... 125 Pratique ................................................................................................................................................................................................................... 125 Et bien sr... ............................................................................................................................................................................................................ 128 Adaptation du contrleur ......................................................................................................................................................................................... 128 Thorie .................................................................................................................................................................................................................... 128 Pratique ................................................................................................................................................................................................................... 128 Impossible d'utiliser le crud ? .................................................................................................................................................................................. 130 Amlioration du contrleur ...................................................................................................................................................................................... 130 L'utilisation d'un ParamConverter ............................................................................................................................................................................ 130 La pagination des articles sur la page d'accueil ...................................................................................................................................................... 131

Partie 4 : Allons plus loin avec Symfony2 ....................................................................................... 133


Crer des formulaires avec Symfony2 .......................................................................................................................... 134
Gestion des formulaires .......................................................................................................................................................................................... 134 L'enjeu des formulaires ........................................................................................................................................................................................... 134 Un formulaire Symfony2, qu'est-ce que c'est ? ....................................................................................................................................................... 134 Gestion basique d'un formulaire .............................................................................................................................................................................. 135 Gestion de la soumission d'un formulaire ............................................................................................................................................................... 137 Grer les valeurs par dfaut du formulaire .............................................................................................................................................................. 138 Personnaliser l'affichage d'un formulaire ................................................................................................................................................................. 138 Crer des types de champs personnaliss ............................................................................................................................................................. 139 Externaliser la gestion de ses formulaires .............................................................................................................................................................. 139 Externaliser la dfinition du formulaire .................................................................................................................................................................... 139 Externaliser la gestion du formulaire ....................................................................................................................................................................... 141 Les formulaires imbriqus ....................................................................................................................................................................................... 143 Intrts de l'imbrication ........................................................................................................................................................................................... 143 Un formulaire est un champ .................................................................................................................................................................................... 143 Relation simple : imbriquer un seul formulaire ........................................................................................................................................................ 144 Relation multiple : imbriquer un mme formulaire plusieurs fois ............................................................................................................................ 145 Application : les formulaires de notre blog .............................................................................................................................................................. 148 Thorie .................................................................................................................................................................................................................... 148 Pratique ................................................................................................................................................................................................................... 148

Validez vos donnes ..................................................................................................................................................... 150


Pourquoi valider des donnes ? .............................................................................................................................................................................. Never Trust User Input ............................................................................................................................................................................................ L'intrt de la validation ........................................................................................................................................................................................... La thorie de la validation ....................................................................................................................................................................................... Dfinir les rgles de validation ................................................................................................................................................................................ Les diffrentes formes de rgles ............................................................................................................................................................................. Dfinir les rgles de validation ................................................................................................................................................................................ Crer ses propres contraintes ................................................................................................................................................................................. Dclencher la validation .......................................................................................................................................................................................... Le service Validator ................................................................................................................................................................................................. La validation automatique sur les formulaires ......................................................................................................................................................... Conclusion .............................................................................................................................................................................................................. Encore plus de rgles de validation ........................................................................................................................................................................ Valider depuis un Getter .......................................................................................................................................................................................... Valider intelligemment un attribut objet ................................................................................................................................................................... Valider depuis un Callback ...................................................................................................................................................................................... Valider un champ unique ......................................................................................................................................................................................... 151 151 151 151 151 151 151 154 154 154 155 155 155 155 156 156 157

Scurit et gestion des utilisateurs ............................................................................................................................... 158


Authentification et autorisation ................................................................................................................................................................................ 159 Les notions d'authentification et d'autorisation ........................................................................................................................................................ 159

www.siteduzero.com

Partager

5/201

Exemples ................................................................................................................................................................................................................ 159 Les diffrents acteurs de la scurit Symfony2 ....................................................................................................................................................... 161 Le fichier de configuration de la scurit ................................................................................................................................................................. 161 Les acteurs de l'authentification .............................................................................................................................................................................. 161 Les acteurs de l'autorisation .................................................................................................................................................................................... 162 Installation du bundle FOSUserBundle ................................................................................................................................................................... 163 Tlcharger FOSUserBundle .................................................................................................................................................................................. 163 Activer le bundle ...................................................................................................................................................................................................... 163 Personnaliser FOSUserBundle ............................................................................................................................................................................... 163 Configuration de la scurit avec FOSUserBundle ................................................................................................................................................. 165 Configuration de la scurit .................................................................................................................................................................................... 165 Configuration du bundle FOSUserBundle ............................................................................................................................................................... 166 Tester l'authentification ............................................................................................................................................................................................ 167 Personnalisation esthtique du bundle ................................................................................................................................................................... 167 Gestion des autorisations avec les rles ................................................................................................................................................................. 169 Dfinition des rles .................................................................................................................................................................................................. 169 Tester les rles de l'utilisateur ................................................................................................................................................................................. 170 Grer les rles des utilisateurs ................................................................................................................................................................................ 171 Manipuler les utilisateurs ......................................................................................................................................................................................... 171 Manipuler les utilisateurs ......................................................................................................................................................................................... 171 Rcuprer l'utilisateur courant ................................................................................................................................................................................. 172

Les services, thorie et cration ................................................................................................................................... 173


Qu'est ce qu'un service ? ........................................................................................................................................................................................ La persistance des services .................................................................................................................................................................................... Le service container ................................................................................................................................................................................................ En pratique .............................................................................................................................................................................................................. Crer un service ...................................................................................................................................................................................................... Cration de la classe du service ............................................................................................................................................................................. Cration de la configuration du service ................................................................................................................................................................... Utilisation du service ............................................................................................................................................................................................... 173 173 173 173 174 174 176 177

Les services, utilisation pousse .................................................................................................................................. 177


Les arguments ........................................................................................................................................................................................................ 178 Les calls .................................................................................................................................................................................................................. 179 Les tags ................................................................................................................................................................................................................... 180 Extension Twig ........................................................................................................................................................................................................ 180 Les vnements du cur ....................................................................................................................................................................................... 181 Les types de champs de formulaire ........................................................................................................................................................................ 181 Les autres tags ........................................................................................................................................................................................................ 182 Les services courants de Symfony2 ........................................................................................................................................................................ 183 Les services courants de Symfony .......................................................................................................................................................................... 183

Partie 5 : Astuces et points particuliers ........................................................................................... 186


Rcuprer directement des entits Doctrine dans son contrleur ................................................................................ 186
Thorie : pourquoi un ParamConverter ? ................................................................................................................................................................ Rcuprer des objets Doctrine avant mme le contrleur ...................................................................................................................................... Les ParamConverters ............................................................................................................................................................................................. Un ParamConverter utile : DoctrineParamConverter .............................................................................................................................................. Un peu de thorie sur les ParamConverter ............................................................................................................................................................. Pratique : utilisation de DoctrineParamConverter ................................................................................................................................................... Utiliser DoctrineParamConverter ............................................................................................................................................................................ 186 186 186 186 186 186 186

Personnaliser les pages d'erreur .................................................................................................................................. 187


Thorie : remplacer les templates d'un bundle ....................................................................................................................................................... 188 Pratique : remplacer les templates Exception de TwigBundle ................................................................................................................................ 188 Le contenu d'une page d'erreur ............................................................................................................................................................................... 188

Utiliser Assetic pour grer les codes CSS et JS de votre site ...................................................................................... 190
Thorie : entre vitesse et lisibilit, pourquoi choisir ? .............................................................................................................................................. propos du nombre de requtes HTTP d'une page Web ...................................................................................................................................... Comment optimiser le front-end ? ........................................................................................................................................................................... Il est aussi possible d'amliorer le temps de chargement ! ..................................................................................................................................... Conclusion .............................................................................................................................................................................................................. Pratique : Assetic la rescousse ! .......................................................................................................................................................................... Utilisation simple ..................................................................................................................................................................................................... Appliquer des filtres ................................................................................................................................................................................................. Gestion du mode prod ....................................................................................................................................................................................... 190 190 190 190 190 190 190 192 193

Utiliser la console directement depuis le navigateur ! ................................................................................................... 195


Thorie : Le composant Console de Symfony2 ...................................................................................................................................................... 195 Les commandes sont en PHP ................................................................................................................................................................................. 195 Exemple d'une commande ...................................................................................................................................................................................... 195 Pratique : Utiliser un ConsoleBundle ...................................................................................................................................................................... 196 ConsoleBundle ? ..................................................................................................................................................................................................... 196 Installer CoreSphereConsoleBundle ....................................................................................................................................................................... 197 Utilisation de la console dans son navigateur ......................................................................................................................................................... 199 Prt pour l'hbergement mutualis ......................................................................................................................................................................... 199 Avancement du cours .............................................................................................................................................................................................. 199 En attendant ............................................................................................................................................................................................................ 199 Licences .................................................................................................................................................................................................................. 199

www.siteduzero.com

Partager

6/201

Symfony2 - Un tutoriel pour dbuter avec le framework Symfony2


Le tutoriel que vous tes en train de lire est en bta-test. Son auteur souhaite que vous lui fassiez part de vos commentaires pour l'aider l'amliorer avant sa publication officielle. Notez que le contenu n'a pas t valid par l'quipe ditoriale du Site du Zro.

Par

Alexandre Bacco (winzou)

Mise jour : 02/05/2012 Difficult : Intermdiaire

Dure d'tude : 14 jours

2 630 visites depuis 7 jours, class 21/783 V ous savez dj faire des sites Internet ? V ous matrisez votre code, mais n'tes pas totalement satisfait ? V ous avez trop souvent l'impression de rinventer la roue ? Alors ce tutoriel est fait pour vous ! Symfony2 est un puissant framework qui va vous permettre de raliser des sites complexes rapidement, mais de faon structure et avec un code clair et maintenable. En un mot : le paradis du dveloppeur ! Ce tutoriel est un tutoriel pour dbutants sur Symfony2, vous n'avez besoin d'aucune notion sur les frameworks pour l'aborder, nous allons les dcouvrir ensemble. Cependant, il est fortement conseill : d'avoir dj une bonne experience de PHP (Aller au cours Concevez votre site web avec PHP et MySQL) ; de matriser les notions de base de la POO (Aller au cours La programmation oriente objet ) ; d'avoir ventuellement des notions de namespace (Aller au cours Les espaces de nom). Si vous ne matrisez pas ces trois points, je vous invite vraiment les apprendre avant de commencer la lecture de ce cours. Symfony2 requiert ces bases, et si vous ne les avez pas, vous risquez de mettre plus de temps pour assimiler ce tutoriel. C'est comme acheter un A380 sans savoir piloter : c'est joli mais a ne sert rien.

www.siteduzero.com

Symfony2 - Un tutoriel pour dbuter avec le framework Symfony2

7/201

Partie 1 : Vue d'ensemble de Symfony2


Pour dbuter, quoi de mieux que de commencer par le commencement ! Si vous n'avez aucune exprience dans les frameworks ni dans l'architecture MVC, cette partie sera trs riche en nouvelles notions. Avanons doucement mais srement, vous tes l pour apprendre !

Symfony2, un framework PHP


Symfony2 est un framework PHP, c'est--dire une boite outils faite en PHP qui a pour but de vous simplifier la vie. Sympa, non ? Dans ce chapitre, nous allons tout d'abord voir ce qu'est vraiment un framework , car Symfony2 est avant tout un framework comme un autre. Nous verrons ensuite pourquoi Symfony2 sort du lot.

Qu'est-ce qu'un framework ? L'objectif d'un framework


L'objectif de ce chapitre n'est pas de vous fournir toutes les cls pour concevoir un framework , mais suffisamment pour pouvoir en utiliser un. On exposera rapidement les intrts, les avantages et les inconvnients de l'utilisation d'un tel outil.

Dfinition
Le mot framework provient de l'anglais frame qui veut dire cadre en franais, et work qui signifie travail . Littralement, c'est donc un cadre de travail . V ous voil avanc, hein ? Concrtement, c'est un ensemble de composants qui servent crer les fondations, l'architecture et les grandes lignes d'un logiciel. Il existe des centaines de frameworks couvrant la plupart des langages de programmation. Ils sont destins au dveloppement de sites Web ou bien la conception de logiciels. Un framework est une boite outils conue par un ou plusieurs dveloppeurs et destination d'autres dveloppeurs. Contrairement certains scripts tels que Wordpress, DotClear ou autres, un framework n'est pas utilisable tel quel. Il n'est pas fait pour tre utilis par les utilisateurs finaux. Le dveloppeur qui se sert d'un framework a encore du boulot fournir, d'o ce tutoriel !

Objectif d'un framework


L'objectif premier d'un framework est d'amliorer la productivit des dveloppeurs qui l'utilisent. Plutt sympa non ? Souvent organis en diffrents composants, un framework offre la possibilit au dveloppeur final d'utiliser tel ou tel composant pour lui faciliter son dveloppement, et lui permet ainsi de se concentrer sur le plus important. Prenons un exemple concrt. Il existe dans Symfony2 un composant qui gre les formulaires HTML : leur affichage, leur validation, etc. Le dveloppeur qui l'utilise se concentre ainsi sur l'essentiel dans son application : chaque formulaire effectue une action, et c'est cette action qui est importante, pas les formulaires. tendez ce principe tout une application ou tout un site internet, et vous comprenez l'intrt d'un framework ! Autrement dit, le framework s'occupe de la forme et permet au dveloppeur de se concentrer sur le fond.

Pesons le Pour et le Contre


Comme tout bon dveloppeur, lorsqu'on veut utiliser un nouvel outil, on doit en peser le pour et le contre pour tre sr de faire le bon choix !

Les Pour
L'avantage premier est donc, on vient de le voir, le gain en productivit. Mais il en existe bien d'autres ! On peut les classer en plusieurs catgories : le code, le travail et la communaut. Tout d'abord, un framework va vous aider raliser un bon code. Par bon code j'entends qu'il vous incite, de part sa propre architecture, bien organiser votre code. Et un code bien organis est un code facilement maintenable et volutif ! De plus, un framework offre des briques prtes tre utilises (le composant Formulaire de Symfony2 par exemple), ce qui vous vite de rinventer la roue, et surtout qui vous permet d'utiliser des briques puissantes et prouves. En effet ces briques sont dveloppes par des quipes de dveloppeurs chevronns, elles sont donc trs flexibles et trs robustes. V ous conomisez ainsi des heures de dveloppement ! Ensuite, un framework amliore la faon dont vous travaillez. En effet, dans le cas d'un site internet vous travaillez souvent avec d'autres dveloppeurs PHP et un designer. Un framework vous aide doublement dans ce travail en quipe. D'une part, un framework utilise presque toujours l'architecture MVC, on en reparlera mais c'est une faon d'organiser son code qui spare le code PHP du code HTML. Ainsi votre designer peut travailler sur des fichiers diffrents des vtres, fini les problmes d'dition simultane d'un mme fichier ! D'autre part, un framework a une structure et des conventions de codes connues. Ainsi vous pouvez facilement recruter un autre dveloppeur : s'il connait dj le framework en question, il s'intgrera trs rapidement au projet. Enfin, le dernier avantage est la communaut soutenant chaque framework . C'est elle qui fournit les tutoriaux (comme celui que vous lisez !), de l'aide sur les forums, et puis bien sr les mises jour du framework . Ces mises jour sont trs importantes : imaginez que vous codiez vous-mme tout ce qui est connexion utilisateur, session, moteur de templates, etc. Comme il est impossible de coder sans bugs, vous devriez logiquement corriger chaque bug dclar sur votre code. Maintenant imaginez que toutes ces briques de votre site, qui ne sont pas forcment votre tasse de th, soient fournies par le framework . A chaque fois que vous ou les milliers d'autres utilisateurs du framework trouverez un bug , les dveloppeurs et la communaut s'occuperont de le corriger, et vous n'aurez plus qu' suivre les mises jour. Un vrai paradis !

www.siteduzero.com

Symfony2 - Un tutoriel pour dbuter avec le framework Symfony2


Il existe plein d'autres avantages que je ne vais pas vous dtailler, mais un framework c'est aussi : Une communaut active qui utilise le framework et qui contribue en retour ; Une documentation de qualit et rgulirement mise jour ; Un code source maintenu par des dveloppeur attitrs ; Un code qui respecte les standards de programmation ; Un support long terme garanti et des mises jour qui ne cassent pas la comptabilit ; Etc.

8/201

Les Contre
V ous vous en doutez, avec autant d'avantages il y a forcment des inconvnients. Et bien figurez-vous qu'il n'y en a pas tant que a ! S'il ne fallait en citer qu'un, cela serait videmment la courbe d'apprentissage qui est plus leve. En effet, pour matriser un framework il faut un temps d'apprentissage non ngligeable. Chaque brique qui compose un framework a sa complexit propre qu'il vous faudra apprhender. Notez galement que pour les frameworks les plus rcents, tel que Symfony2 justement, il faut galement tre au courant des dernires nouveauts de PHP. Je pense notamment la POO et aux namespaces. De plus, connatre certaines bonnes pratiques telles que l'architecture MVC et autres est un plus. Bref, rien de tout cela ne doit vous effrayer, c'est ce pourquoi vous tes ici ! V oyez l'apprentissage d'un framework comme un investissement : il y a un certain effort fournir au dbut, mais les rsultats se rcoltent ensuite sur le long terme !

Alors, convaincu ?
J'espre vous avoir convaincu que, bien entendu, le Pour l'emporte largement sur le Contre. Si vous tes prt relever le dfi aujourd'hui pour tre plus productif demain, alors ce tutoriel est fait pour vous. Bonne lecture !

Qu'est-ce que Symfony2 ? Un framework


Symfony2 est donc un framework PHP. Bien sr il en existe d'autres, pour ne citer que les plus connus : Zend Framework, CodeIgniter, CakePHP, etc. Le choix d'un framework est assez personnel, et doit tre adapt au projet engag. Sans vouloir prcher pour ma paroisse, Symfony2 est l'un des plus flexibles et des plus puissants .

Un framework populaire
Symfony est trs populaire. C'est un des frameworks les plus utiliss dans le monde, notamment dans les entreprises. Il est utilis par Dailymotion par exemple ! La premire version de symfony est sortie en 2005 et est aujourd'hui toujours trs utilise. Cela lui apporte un retour d'exprience et une notorit exceptionnelle. Aujourd'hui, beaucoup d'entreprises dans le domaine de l'Internet (dont Simple IT !) recrutent des dveloppeurs capables de travailler sous ce framework . Ces dveloppeurs pourront ainsi se greffer aux projets de l'entreprise trs rapidement, car ils en connatront dj les grandes lignes. C'est un atout si vous souhaitez travailler dans ce domaine . La deuxime version du nom, que nous tudierons dans ce tutoriel, est sortie en aot 2011. Encore jeune, son dveloppement a t fulgurant grce une communaut de dveloppeur dvous. Bien que diffrent dans sa conception, cette deuxime version est plus rapide et plus souple que la premire. Il y a fort parier que trs rapidement, beaucoup d'entreprises s'arracheront les comptences des premiers dveloppeurs Symfony2. Faites-en partie !

Un framework populaire et franais


Eh oui, Symfony2, l'un des meilleurs frameworks PHP au monde, est un framework franais ! Il est dit par la socit SensioLabs, dont le crateur est Fabien Potencier. Mais Symfony2 tant un script open source, il a galement t crit par toute la communaut : beaucoup de Franais, mais aussi des dveloppeurs de tout horizon : Europe, tats-Unis, etc. C'est grce au talent de Fabien et la gnrosit de la communaut que Symfony2 a vu le jour.

Tlcharger Symfony2 Obtenir Symfony2


Il existe de nombreux moyens d'obtenir Symfony2. Nous allons voir ici la mthode la plus simple : tlcharger la distribution standard. Pour cela rien de plus simple. Rendez-vous sur le site de Symfony2, rubrique Download : http://symfony.com/download, et tlchargez la version "Symfony Standard (.zip)". Si vous utilisez dj Composer, vous pouvez tlcharger cette distribution standard en une seule commande : Code : Console php composer.phar create-project symfony/framework-standard-edition

Pour les autres, tlchargez l'archive traditionnelle avec les vendors, on parlera de Composer dans un prochain chapitre.

www.siteduzero.com

Symfony2 - Un tutoriel pour dbuter avec le framework Symfony2


Si vous tes sous Windows, vitez de tlcharger l'archive au format .tgz car des problmes ont t rencontrs avec cette archive. Une fois l'archive tlcharge, dcompressez les fichiers dans votre rpertoire web habituel, par exemple " C:\wamp\www" pour Windows ou " /var/www" pour Linux. Pour la suite du tutoriel, je considrerai que les fichiers sont accessibles l'URL http://localhost/Symfony. Je vous recommande d'avoir la mme adresse, car je ferai ce genre de liens tout au long du tutoriel .

9/201

Vrifier votre configuration de PHP


Symfony2 a quelques contraintes par rapport votre configuration PHP. Par exemple, il ne tourne que sur la version 5.3.2 ou suprieure de PHP. Pour vrifier si votre environnement est compatible, rendez-vous l'adresse suivante : http://localhost/Symfony/web/config.php.

Mon environnement de travail est compatible avec Symfony2 ! En cas d'incompatibilit (version de PHP notamment), Symfony2 vous demande de rgler les problmes avant de continuer. S'il ne vous propose que des recommandations , vous pouvez continuer sans problme. Ce sont des points que je vous conseille de rgler, mais qui sont facultatifs. Si vous tes sous Linux, vous devez bien rgler les droits sur les rpertoires app/cache et app/logs afin que Symfony2 puisse y crire. Placez-vous dans le rpertoire Symfony et videz d'abord ces rpertoires : Code : Console rm -rf app/cache/* rm -rf app/logs/*

Ensuite, si votre distribution supporte le chmod +a excutez ces commandes pour dfinir les bons droits : Code : Console sudo chmod +a "www-data allow delete,write,append,file_inherit,directory_inherit" app/cache app/logs sudo chmod +a "`whoami` allow delete,write,append,file_inherit,directory_inherit" app/cache app/logs

Si vous rencontrez une erreur avec les commandes ci-dessus, excutez les commandes ci-dessous qui n'utilisent pas le chmod +a : Code : Console sudo setfacl -R -m u:www-data:rwx m u:`whoami`:rwx app/cache app/logs sudo setfacl -dR -m u:www-data:rwx m u:`whoami`:rwx app/cache app/logs

Enfin, si vous ne pouvez pas utiliser les ACL (utiliss dans les commandes ci-dessus), dfinissez simplement les droits comme suit : Code : Console

www.siteduzero.com

Symfony2 - Un tutoriel pour dbuter avec le framework Symfony2


chmod 777 app/cache chmod 777 app/logs

10/201

V ous pouvez ds prsent excuter Symfony2, flicitations ! Rendez-vous sur la page http://localhost/Symfony/web/app_dev.php et admirez :

La page d'accueil de Symfony2

Vrifier l'installation de PHP en console


Nous aurons parfois besoin d'excuter des commandes PHP via la console, pour gnrer du code ou grer la base de donnes. Ce sont des commandes qui vont nous faire gagner du temps (toujours le mme objectif !), vrifions donc que PHP est bien disponible en console. Si vous tes sous Linux vous ne devriez pas avoir de soucis, PHP est bien disponible en console. Si vous tes sous Windows, rien n'est sr. Dans tous les cas, vrifiez-le en ouvrant l'invite de commandes pour Windows, ou le terminal pour Linux, et entrez la commande suivante : php -v . Si cette commande vous retourne bien la version de PHP et d'autres informations, c'est tout bon pour vous.

La commande vous affiche une erreur ?


Si vous tes sous Windows. V otre PHP est bien install, mais en fait Windows ne sait pas o le trouver, il faut juste le lui dire. V oici la dmarche suivre pour rgler ce problme (tir de la documentation PHP) : 1. Allez dans la paramtres systmes avancs (Dmarrer > Panneau de configuration > Systme et scurit > Systme > Paramtres systme avancs) ; 2. Cliquez sur le bouton "Variables d'environnements..." 3. Regardez dans le panneau "Variables systmes" 4. Trouvez l'entre Path (vous devriez avoir faire descendre l'ascenseur pour le trouver) 5. Double cliquez sur l'entre Path 6. Entrez votre rpertoire PHP la fin, sans oublier le point virgule (;) avant. C'est le rpertoire dans lequel se trouve le fichier " php.exe". Par exemple " ;C:\wamp\bin\php\php5.3" 7. Confirmez en cliquant sur OK V ous devez ensuite redmarrer l'invite de commande pour prendre en compte les changements. Si vous tes sous Linux. Vrifiez alors votre installation de PHP, vous devez notamment avoir le paquet php5-cli, qui est la version console de PHP. Dans les deux cas, vrifiez aprs vos manipulations que le problme est bien rsolu. Pour cela, excutez nouveau la commande php -v . Elle devrait alors vous afficher la version de PHP . Et voil, votre poste de travail est maintenant oprationnel pour dvelopper avec Symfony2 ! V otre environnement est prt, mais vous devez maintenant apprendre vous servir de Symfony2. Rendez-vous donc au prochain chapitre pour dcouvrir l'architecture utilise par Symfony2 !

www.siteduzero.com

Symfony2 - Un tutoriel pour dbuter avec le framework Symfony2

11/201

Vous avez dit Symfony2 ?


Dans ce chapitre, nous allons voir comment est organis Symfony2 l'intrieur. Nous n'entrerons pas dans les dtails, c'est trop tt, le but tant juste d'avoir une vision globale du processus d'excution d'une page sous Symfony2. Ainsi, vous pourrez comprendre ce que vous faites. C'est mieux, non ?

L'architecture des fichiers


On vient d'extraire beaucoup de fichiers, mais sans savoir encore quoi ils servent. C'est le moment d'claircir tout cela !

Liste des rpertoires


Ouvrez donc le rpertoire dans lequel vous avez extrait les fichiers. V ous pouvez voir qu'il n'y a pas beaucoup de fichier ici, seulement des rpertoires. En effet tout est bien rang dans chaque rpertoire, il nous faut donc lucider ces rpertoires. En voici la liste : Code : Autre app src vendor web

Le rpertoire app/
Ce rpertoire contient tout ce qui concerne votre site internet sauf... son code source. Assez trange me direz vous . En fait c'est simplement pour sparer le code source qui fait la logique de votre site du reste. Le reste, c'est ce rpertoire app. Et ce reste c'est : la configuration, le cache, les fichiers logs, etc. Ce sont des fichiers qui concernent l'entiret de votre site, contrairement aux fichiers de code source qui seront dcoups par fonctionnalits de votre site. Dans Symfony2, un projet de site internet est une application, simple question de vocabulaire. Le rpertoire app/ est donc le raccourci pour application.

Le rpertoire src/
V oil enfin le rpertoire dans lequel on mettra le code source ! C'est ici que l'on passera le plus clair de notre temps. Dans ce rpertoire, nous organiserons notre code en " bundles", des briques de notre application, dont nous verrons la dfinition plus loin. V ous pouvez voir qu'il n'est pas vide : il contient en effet quelques fichiers exemples, fournis par Symfony2. Nous les supprimerons plus tard dans ce tutoriel.

Le rpertoire vendor/
Ce rpertoire contient toutes les librairies externes notre application. Dans ces librairies externes, j'inclus Symfony2 ! V ous pouvez parcourir ce rpertoire, vous y trouverez des librairies comme Doctrine, Twig, SwiftMailer, etc. Et une librairie, c'est quoi exactement ?

Une librairie est une sorte de boite noire qui remplit une fonction bien prcise, et dont on peut se servir dans notre code. Par exemple, la librairie SwiftMailer permet d'envoyer des emails. On ne sait pas comment elle fonctionne (principe de la boite noire), mais on sait comment s'en servir : on pourra donc envoyer des emails trs facilement, juste en apprenant rapidement utiliser la librairie.

Le rpertoire web/
Ce rpertoire contient tous les fichiers destins vos visiteurs : images, fichiers CSS et JavaScript, etc. Il contient galement le contrleur frontal (app.php), dont nous parlerons juste aprs. En fait c'est le seul rpertoire qui devrait tre accessible vos visiteurs. Les autres rpertoires ne sont pas censs tre accessibles (ce sont vos classes, elles vous regardent vous, pas vos visiteurs), c'est pourquoi vous y trouverez des fichiers .htaccess interdisant l'accs depuis l'extrieur. On utilisera donc toujours des URL du type http://localhost/Symfony/web/... au lieu de simplement http://localhost/Symfony/.... Si vous le souhaitez, vous pouvez configurer votre Apache pour que l'URL http://localhost/Symfony pointe directement sur le rpertoire web/. C'est en tout cas ce que vous devrez faire lorsque vous mettrez votre site en ligne (les hbergeurs le permettent). Pour cela, vous pouvez lire ce tutoriel qui explique comment configurer Apache.

A retenir
Retenez donc que nous passerons la plupart de notre temps dans le rpertoire src/, travailler sur nos bundles. On touchera galement pas mal au rpertoire app/ pour configurer notre application. Et lorsque nous installerons des bundles tlchargs, nous le ferons dans le rpertoire vendor/.

Le contrleur frontal
Dfinition
Le contrleur frontal (front controller, en anglais) est le point d'entre de votre application. C'est le fichier par lequel passent toutes vos pages. V ous devez surement connaitre le principe du index.php et des pseudo-frames (avec des URL du type

www.siteduzero.com

Symfony2 - Un tutoriel pour dbuter avec le framework Symfony2


index.php?page=blog), et bien ce index.php est un contrleur frontal. Dans Symfony2, le contrleur frontal se situe dans le rpertoire web/, il s'agit de app.php ou app_dev.php. Pourquoi y a-t-il deux contrleurs frontaux ? Normalement c'est un fichier unique qui gre toutes les pages non ?

12/201

V ous avez parfaitement raison... pour un code classique ! Nous travaillons maintenant avec Symfony2, et son objectif est de nous faciliter le dveloppement. C'est pourquoi Symfony2 propose un contrleur frontal pour nos visiteurs, app.php, et un contrleur frontal lorsque nous dveloppons, app_dev.php. Ces deux contrleurs frontaux, fournis par Symfony2 et prts l'emploi, dfinissent en fait deux environnements de travail.

Deux environnements de travail


L'objectif est de rpondre au mieux suivant la personne qui visite le site : Un dveloppeur a besoin d'informations sur la page afin de l'aider dvelopper. En cas d'erreur, il veut tous les dtails pour pouvoir dboguer facilement. Il n'a pas besoin de rapidit. Un visiteur normal n'a pas besoin d'informations particulires sur la page. En cas d'erreur, l'origine de celle-ci ne l'intresse pas du tout, il veut juste retourner d'o il vient. Par contre, il veut que le site soit le plus rapide possible charger. V ous voyez la diffrence ? A chacun ses besoins, et Symfony2 compte bien tous les remplir. C'est pourquoi il offre plusieurs environnements de travail : L'environnement de dveloppement, appel dev , accessible en utilisant le contrleur frontal app_dev.php. C'est l'environnement que l'on utilisera toujours pour dvelopper. L'environnement de production, appel prod , accessible en utilisant le contrleur frontal app.php. Essayez-les ! Allez sur http://localhost/Symfony/web/app_dev.php et vous verrez une barre doutils en bas de votre cran, contenant nombre dinformations utiles au dveloppement. Allez sur http://localhost/Symfony/web/app.php et vous obtiendrez... une erreur 404 . En effet, aucune page n'est dfinie par dfaut pour le mode prod . Nous les dfinirons plus tard, mais notez que c'est une belle erreur 404, aucun terme barbare n'est employ pour la justifier . Pour voir le comportement du mode dev en cas d'erreur, essayez aussi d'aller sur une page qui n'existe pas. V ous avez vu ce que donne une page introuvable en mode prod , mais allez maintenant sur http://localhost/Symfony/web/app_dev.p [...] quinexistepas. La diffrence est claire : le mode prod nous dit juste page introuvable alors que le mode dev nous donne plein d'informations sur l'origine de l'erreur, indispensables pour la corriger. C'est pourquoi, dans la suite du tutoriel, nous utiliserons toujours le mode dev en passant donc par app_dev.php. Bien sr, lorsque votre site sera oprationnel et que des internautes pourront le visiter, il faudra leur faire utiliser le mode prod . Mais nous n'en sommes pas encore l. Et comment savoir quelles erreurs surviennent en mode production si elles ne s'affichent pas ? C'est une bonne question, en effet si par malheur une erreur intervient pour l'un de vos visiteurs, il ne verra aucun message et vous non plus, une vraie galre pour dboguer ! En ralit si les erreurs ne sont pas affiches, elles sont bien stockes dans un fichier. Allez jeter un oeil au fichier app/logs/prod.log qui contient plein d'informations sur les requtes effectues en mode production, dont les erreurs .

Concrtement, qu'est-ce que contrle le contrleur frontal ?


Trs bonne question. Pour cela, rien de tel que... d'ouvrir le fichier app.php. Ouvrez-le et vous constaterez qu'il ne fait pas grand chose. En effet, le but du contrleur frontal n'est pas de faire quelque chose, mais d'tre un point d'entre de notre application. Il se limite donc appeler le noyau (Kernel) de Symfony2 en disant " On vient de recevoir une requte, transforme la en rponse s'il-te-plait ". Ici, voyez le contrleur frontal comme un fichier nous (il est dans notre rpertoire web/), et le Kernel comme un composant Symfony2, une boite noire (il est dans le rpertoire vendors/). V ous voyez comment on a utilis notre premier composant Symfony2 : on a dlgu la gestion de la requte au Kernel. Bien sr ce Kernel aura besoin de nous pour savoir quoi excuter comme code, mais il gre dj plusieurs choses que nous avons vues : la gestion des erreurs, l'ajout de la toolbar en bas de l'cran, etc. On n'a encore rien fait, et pourtant on a dj gagn du temps !

L'architecture conceptuelle
On vient de voir comment sont organiss les fichiers de Symfony2. Maintenant il s'agit de comprendre comment s'organise l'excution du code au sein de Symfony2.

Architecture MVC
Architecture MVC... V ous avez certainement dj entendu parler de ce concept. Sachez que Symfony2 respecte bien entendu cette architecture MVC. Je ne vais pas rentrer dans ses dtails car il y a dj un super tutoriel sur ce mme site, mais en voici les grandes lignes. MVC signifie Modle / Vue / Contrleur. C'est un dcoupage trs rpandu pour dvelopper les sites Internet, car il spare les couches selon leur logique propre : le Contrleur (ou Controller) : son rle est de gnrer la rponse la requte HTTP demande par notre visiteur. Il est la couche qui se charge d'analyser et de traiter la requte de l'utilisateur. Le contrleur contient la logique de notre site Internet et va se contenter d'utiliser les autres composants : les modles et les vues. Concrtement un contrleur va rcuprer, par exemple, les informations sur l'utilisateur courant, vrifier qu'il a le droit de modifier tel article, rcuprer cet article et demander la page du formulaire d'dition de l'article. C'est tout bte, avec quelques if(), on s'en sort trs bien ;

www.siteduzero.com

Symfony2 - Un tutoriel pour dbuter avec le framework Symfony2


le Modle (ou Model ) : son rle est de grer vos donnes et votre contenu. Reprenons l'exemple de l'article. Lorsque je dis le contrleur rcupre l'article , il va en fait faire appel au modle article et lui dire : donne-moi l'article portant l'id 5 . C'est le modle qui sait comment rcuprer cet article, gnralement via une requte au serveur SQL, mais a pourrait tre depuis un fichier texte ou ce que vous voulez. Au final, il permet au contrleur de manipuler les articles, mais sans savoir comment les articles sont stocks, grs, etc. C'est une couche d'abstraction ; la Vue (ou View ) : son rle est d'afficher les pages. Reprenons encore l'exemple de l'article. Ce n'est pas le contrleur qui affiche le formulaire, il ne fait qu'appeler la bonne vue. Si nous avons une vue formulaire , les balises HTML du formulaire d'dition de l'article y seront et au final, le contrleur ne fera qu'afficher cette vue sans savoir vraiment ce qu'il y a dedans. En pratique, c'est le designer d'un projet qui travaille sur les vues. Sparer vues et contrleurs permet aux designers et dveloppeurs PHP de travailler ensemble sans se marcher dessus. Au final, si vous avez bien compris, le contrleur ne contient que du code trs simple, car il se contente d'utiliser des modles et des vues en leur attribuant des tches prcises. Il agit un peu comme un chef d'orchestre, qui n'agite qu'une baguette alors que ses musiciens jouent des instruments complexes.

13/201

Parcours d'une requte dans Symfony2


Afin de bien visualiser tous les acteurs que nous avons vu jusqu' prsent, je vous propose un schma du parcours complet d'une requte dans Symfony2 :

www.siteduzero.com

Symfony2 - Un tutoriel pour dbuter avec le framework Symfony2


En le parcourant avec des mots, voici ce que cela donne : 1. Le visiteur demande la page /blog ; 2. Le contrleur frontal reoit la requte, charge le Kernel et la lui transmet ; 3. Le Kernel demande au Routeur quel contrleur excuter pour l'URL /blog. Ce Routeur est un composant Symfony2 qui fait la correspondance entre URLs et contrleurs, nous l'tudierons bien sr dans un prochain chapitre. Le Routeur fait donc son travail, et dit au Kernel qu'il faut excuter le contrleur SdzBlog ; 4. Le Kernel excute donc ce contrleur. Le contrleur demande au modle Article la liste des articles, puis la donne la vue ListeArticles pour qu'elle construise la page HTML et la lui retourne. Une fois fini, le contrleur envoie au visiteur la page HTML complte. J'ai mis des couleurs pour distinguer les points o on intervient des autres. En vert, donc le contrleur, modle et vue, c'est ce qu'on devra dvelopper nous-mmes. En orange, donc le Kernel et le Routeur, c'est ce qu'on devra configurer. On ne touchera pas au contrleur frontal, en gris. Maintenant, il ne nous reste plus qu' voir comment organiser concrtement notre code nous et sa configuration.

14/201

Symfony2 et ses bundles La dcoupe en bundles


Le concept
V ous avez dj crois ce terme de bundle quelquefois depuis le dbut du tutoriel, mais qu'est-ce qui se cache derrire ce terme ? Pour faire simple, un bundle est une brique de votre application. Evident non ? Plus srieusement, il s'agit vraiment de a. Symfony2 utilise ce concept innovant qui consiste regrouper dans un mme endroit, le bundle, tout ce qui concerne une mme fonctionnalit. Par exemple, on peut imaginer un bundle Blog dans notre site, qui regrouperait les contrleurs, les modles, les vues, les fichiers CSS et Javascript, etc. Tout ce qui concerne directement la fonctionnalit blog de notre site. Cette organisation permet de dcouper naturellement nos fonctionnalits, et ainsi de ranger chaque fichier sa place. Un fichier javascript n'est utilis que sur le bundle blog ? Mettez le dans le bundle blog ! Bien videmment, au sein d'un bundle, il faut retrouver galement une architecture bien dfinie, nous l'tudions juste aprs.

Des exemples
Pour mieux visualiser, je vous propose quelques bons exemples de bundles possibles : Un bundle Utilisateur, qui va grer les utilisateurs ainsi que les groupes, intgrer des pages d'administration de ces utilisateurs, et des pages classiques comme le formulaire d'inscription, de rcupration de mot de passe, etc. Un bundle Blog, qui va fournir une interface pour grer un blog sur le site. Ce bundle peut utiliser le bundle Membre pour faire un lien vers les profils des auteurs des articles et des commentaires. Un bundle Boutique, qui va fournir des outils pour grer des produits et des commandes. Un bundle Admin, qui va fournir uniquement une interface vers les outils d'administration des diffrents bundles utiliss (membre, blog, etc.). Attention il ne doit pas y avoir beaucoup de code dans ce bundle, ce n'est qu'un raccourci vers les fonctionnalits d'administration des autres bundles. La partie admin pour ajouter un article au blog doit se trouver dans le bundle blog. Et ces bundles, parce qu'ils respectent des rgles communes, vont fonctionner ensemble. Par exemple, un bundle Forum et un bundle Utilisateur devront s'entendre : dans un forum, ce sont des utilisateurs qui interagissent.

L'intrt
Une question toujours se poser : quel est l'intrt de ce que l'on est en train de faire ? En plus d'organiser votre code par fonctionnalits, la dcoupe en bundles permet... l'change de bundles entre applications ! Cela signifie que vous pouvez dvelopper une fonctionnalit, puis la partager avec d'autres dveloppeurs ou encore la rutiliser dans un de vos autres projets. Et bien entendu, cela marche dans l'autre sens : vous pouvez installer dans votre projet des bundles qui ont t dvelopps par d'autres ! Le principe mme des bundles offre donc des possibilits infinies ! Imaginez le nombre de fonctionnalits classiques sur un site internet, que vous n'aurez plus dvelopper vous-mmes. V ous avez besoin d'un livre d'or ? Il existe srement un bundle. V ous avez besoin d'un blog ? Il existe srement un bundle. Etc.

Les bundles de la communaut


Presque tous les bundles de la communaut Symfony2 sont regroups sur un mme site : http://knpbundles.com/. Il en existe beaucoup, et pour n'en citer que quelques uns : FOSUserBundle : c'est un bundle destin grer les utilisateurs de votre site. Concrtement, il fournit le modle utilisateur ainsi que le contrleur pour accomplir les actions de base (connexion, inscription, dconnexion, dition d'un utilisateur, etc.) et fournit aussi les vues qui vont avec. Bref, il suffit d'installer le bundle et de le personnaliser un peu pour obtenir un espace membre ! FOSCommentBundle : c'est un bundle destin grer des commentaires. Concrtement, il fourni le modle commentaire (ainsi que son contrleur) pour ajouter, modifier et supprimer les commentaires. Les vues sont fournies avec, videmment. Bref, en installant ce bundle, vous pourrez ajouter un fil de commentaires n'importe quelle page de votre site ! GravatarBundle : c'est un bundle destin grer les avatars depuis le service web Gravatar. Concrtement, il fournit une extension Twig pour pouvoir afficher un avatar issu de Gravatar via une simple fonction qui s'avre tre trs pratique ; etc. Je vous conseille vivement de passer sur http://knpbundles.com/ avant de commencer dvelopper un bundle. S'il en existe dj

www.siteduzero.com

Symfony2 - Un tutoriel pour dbuter avec le framework Symfony2


un et qu'il vous convient, il serait trop bte de rinventer la roue externe, patience ! . Bien sr, il faut d'abord apprendre installer un bundl e

15/201

La structure d'un bundle


Un bundle contient tout : routes, contrleurs, vues, modles, classes personnelles, etc. Bref, tout ce qu'il faut pour remplir la fonction du bundle. videmment, tout cela est organis en dossiers afin que tout le monde s'y retrouve. V oici la structure d'un bundle partir de son rpertoire de base, vous pouvez en voir l'illustration grce au bundle exemple fourni par dfaut dans src/Acme/DemoBundle/ : Code : Autre Controller/ DependencyInjection/ Entity/ Form/ Resources/ -- config/ -- public/ -- views/ Tests/ | | | | Contient Contient Contient Contient vos des vos vos

contrleurs. informations sur votre bundle (chargement automatique de la configuration pa modles. ventuels formulaires.

| Contient les fichiers de configuration de votre bundle (nous placerons les routes ici, | Contient les fichiers publics de votre bundle : fichiers CSS et JavaScript, images, etc | Contient les vues de notre bundle, les templates Twig. | Contient vos ventuels tests unitaires et fonctionnels. Nous dvelopperons sans faire de

La structure est assez simple au final, retenez la bien. Sachez qu'elle n'est pas du tout fixe, vous pouvez crer tous les dossiers que vous voulez pour mieux organiser votre code. Mais cette structure conventionnelle permet d'autres dveloppeurs de comprendre rapidement votre bundle. Bien entendu je vous guiderai pour chaque cration de fichier . V ous connaissez maintenant les grands principes de Symfony2, et c'est dj beaucoup ! Dans le prochain chapitre, nous allons crer notre premire page. Cela nous permettra de manipuler bundles, routes, contrleurs et vues. Bref, nous mettrons en pratique tout ce que nous venons de voir en thorie. Rendez-vous au prochain chapitre.

www.siteduzero.com

Symfony2 - Un tutoriel pour dbuter avec le framework Symfony2

16/201

Utilisons la console pour crer un bundle


Dans ce chapitre nous allons crer notre premier bundle, juste histoire d'avoir la structure de base de notre code futur. Mais nous ne le ferons pas n'importe comment : nous allons gnrer le bundle en utilisant une commande Symfony2 en console ! L'objectif est de dcouvrir la console utilement.

Utilisation de la console
Tout d'abord, vous devez savoir une chose : Symfony2 intgre des commandes disponibles non pas via le navigateur, mais via l'invite de commandes (sous Windows) ou le terminal (sous Linux). Il existe pas mal de commandes qui vont nous servir assez souvent lors du dveloppement, apprenons donc ds maintenant utiliser cette console ! Les outils disponibles en ligne de commandes ont pour objectif de nous faciliter la vie. Ce n'est pas un obscur programme pour les geek amoureux de la console ! V ous pourrez partir de l gnrer une base de code source pour certains fichiers rcurrent, vider le cache, ajouter des utilisateurs par la suite, etc. N'ayez pas peur de cette console .

Sous Windows
Lancez l'invite de commandes : Menu Dmarrer > Programmes > Accessoires > Invite de commandes.

Puis placez vous dans le rpertoire o vous avez mis Symfony2, en utilisant la commande Windows cd (je vous laisse adapter la commande) : Code : Console Microsoft Windows [version 6.1.7601] Copyright (c) 2009 Microsoft Corporation. Tous droits rservs. C:\Users\winzou>cd ../../wamp/www/Symfony C:\wamp\www\Symfony>_

On va excuter des fichiers PHP depuis cette invite de commandes. En l'occurence c'est le fichier app/console (ouvrez-le, c'est bien du PHP) que nous allons excuter. Pour cela, il faut lancer la commande PHP avec le nom du fichier en argument : php app/console . C'est parti : Code : Console C:\wamp\www\Symfony>php app/console Symfony version 2.1.0-DEV - app/dev/debug Usage: [options] command [arguments] Options: --help --quiet --verbose --version -h -q -v -V Display this help message. Do not output any message. Increase verbosity of messages. Display this application version.

www.siteduzero.com

Symfony2 - Un tutoriel pour dbuter avec le framework Symfony2


--ansi Force ANSI output. --no-ansi Disable ANSI output. --no-interaction -n Do not ask any interactive question. --shell -s Launch the shell. --processisolation Launch commands from shell as a separate processes. --env -e The Environment name. --no-debug Switches off debug mode.

17/201

Et voila, vous venez d'excuter une commande Symfony ! Celle-ci ne fait pas grand chose, c'tait juste un entrainement. La commande ne fonctionne pas ? Il vous dit que PHP n'est pas un excutable ? V ous avez d oublier d'ajouter PHP dans votre variable PATH, on l'a fait la fin du premier chapitre, jetez y un oeil.

Sous Linux et Mac


Ouvrez le terminal. Placez vous dans le rpertoire o vous avez mis Symfony2, certainement /var/www pour Linux ou /user/sites pour Mac. Le fichier que nous allons excuter est app/console, il faut donc lancer la commande php app/console . Je ne vous fais pas de capture d'cran, mais j'imagine que vous savez le faire !

A quoi a sert ?
Une trs bonne question, qu'il faut toujours se poser. La rponse est trs simple : nous simplifier la vie ! Depuis cette console, on pourra par exemple crer une base de donnes, vider le cache, ajouter ou modifier des utilisateurs (sans passer par phpMyAdmin !), etc. Mais ce qui nous intresse dans ce chapitre, c'est la gnration de code. En effet, pour crer un bundle, un modle ou un formulaire, le code de dpart est toujours le mme. C'est ce code l que le gnrateur va crire pour nous. Du temps de gagn !

Comment a marche ?
Comment Symfony2, un framework pourtant crit en PHP, peut-il avoir des commandes en console ?

V ous devez savoir que PHP peut s'excuter depuis le navigateur, mais galement depuis la console. En fait ct Symfony2, tout est toujours crit en PHP, il n'y a rien d'autre. Pour en tre sr, ouvrez le fichier app/console : Code : PHP <?php require_once __DIR__.'/bootstrap.php.cache'; require_once __DIR__.'/AppKernel.php'; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Input\ArgvInput; $input = new ArgvInput(); $env = $input->getParameterOption(array('--env', '-e'), getenv('SYMFONY_ENV') ?: 'dev'); $debug = !$input->hasParameterOption(array('--no-debug', '')); $kernel = new AppKernel($env, $debug); $application = new Application($kernel); $application->run();

V ous ne remarquez rien ? Il ressemble beaucoup au contrleur frontal app.php ! En fait il fait presque la mme chose, il inclut les mmes fichiers, et charge galement le Kernel. Mais il dfinit la requte comme venant de la console, ce qui excute du code diffrent par la suite. On pourra nous aussi crire du code qui sera excut non pas depuis le navigateur (comme les contrleurs habituels), mais depuis la console. Rien ne change pour le code, si ce n'est que l'affichage ne peut pas tre en HTML bien videmment.

Crons notre bundle Tout est bundle


Rappelez-vous : dans Symfony2, chaque partie de votre site est un bundle. Pour crer notre premire page, il faut donc d'abord crer notre premier bundle. Rassurez-vous, crer un bundle est extrmement simple avec le gnrateur. Dmonstration !

Excuter la bonne commande


Comme on vient de l'apprendre, excutez la commande php app/console generate:bundle .

1. Choisir le namespace
Symfony2 vous demande le namespace de votre bundle :

www.siteduzero.com

Symfony2 - Un tutoriel pour dbuter avec le framework Symfony2


Code : Console C:\wamp\www\Symfony>php app/console generate:bundle Welcome to the Symfony2 bundle generator

18/201

Your application code must be written in bundles. This command helps you generate them easily. Each bundle is hosted under a namespace (like Acme/Bundle/BlogBundle). The namespace should begin with a "vendor" name like your company name, your project name, or your client name, followed by one or more optional category sub-namespaces, and it should end with the bundle name itself (which must have Bundle as a suffix). See http://symfony.com/doc/current/cookbook/bundles/best_practices.html#index1 for more details on bundle naming conventions. Use / instead of \ for the namespace delimiter to avoid any problem. Bundle namespace:_

V ous pouvez nommer votre namespace comme bon vous semble, il faut juste qu'il se termine par le suffixe "Bundle". Par convention, on le compose de trois parties. Nous allons nommer notre namespace Sdz\BlogBundle , explications : 1. Sdz est le namespace racine : il vous reprsente. V ous pouvez mettre votre pseudo, le nom de votre site ou ce que vous voulez ; 2. Blog est le nom du bundle en lui-mme : il dfinit ce que fait le bundle. Ici, nous crons un blog, nous l'avons donc simplement appel Blog ; 3. Bundle est le suffixe obligatoire. Rentrez donc dans la console Sdz/BlogBundle Avec des slashes juste pour cette fois pour les besoins de la console, mais un namespace comprend bien des anti-slashes.

2. Choisir le nom
Symfony2 vous demande le nom de votre bundle : Code : Console Bundle namespace: Sdz/BlogBundle In your code, a bundle is often referenced by its name. It can be the concatenation of all namespace parts but it's really up to you to come up with a unique name (a good practice is to start with the vendor name). Based on the namespace, we suggest SdzBlogBundle. Bundle name [SdzBlogBundle]:_

Par convention, on nomme le bundle de la mme manire que le namespace, sans les slashes. On a donc : SdzBlogBundle. C'est ce que Symfony2 vous propose par dfaut (la valeur entre les crochets), appuyez donc simplement sur Entre. Retenez ce nom, par la suite quand on parlera du nom du bundle, cela voudra dire ce nom l : SdzBlogBundle.

3. Choisir la destination
Symfony2 vous demande l'endroit o vous voulez que les fichiers du bundle soient gnrs : Code : Console The bundle can be generated anywhere. The suggested default directory uses the standard conventions. Target directory [C:/wamp/www/src]:_

Par convention, comme on l'a vu, on place nos bundles dans le rpertoire src/. C'est ce que Symfony2 vous propose, appuyez donc sur Entre.

4. Choisir le format de configuration


Symfony2 vous demande sous quelle forme vous voulez configurer votre bundle. Il s'agit simplement du format de la configuration, configuration que nous ferons plus tard. Il existe plusieurs moyens comme vous pouvez le voir : Yaml, XML, PHP

www.siteduzero.com

Symfony2 - Un tutoriel pour dbuter avec le framework Symfony2


ou Annotations. Code : Console Target directory [C:/wamp/www/src]: Determine the format to use for the generated configuration. Configuration format (yml, xml, php, or annotation) [annotation]:

19/201

Chacune a ses avantages et inconvnients. Nous allons utiliser le Yaml (yml) ici, car est il bien adapt pour un bundle. Mais sachez que nous utiliseront les annotations pour nos futures entits par exemple. Entrez donc yml .

5. Choisir quelle structure gnrer


Symfony2 vous demande si vous voulez gnrer juste le minimum ou une structure plus complte pour le bundle : Code : Console Configuration format (yml, xml, php, or annotation) [annotation]: yml To help you get started faster, the command can generate some code snippets for you. Do you want to generate the whole directory structure [no]?

Faisons simple et demandons Symfony2 de tout nous gnrer. Entrez donc yes .

6. Confirmez, et c'est jou !


Pour toutes les questions suivantes, confirmez en appuyant sur Entre chaque fois. Et voil, votre bundle est gnr : Code : Console Do you want to generate the whole directory structure [no]? yes Summary before generation You are going to generate a "Sdz\BlogBundle\SdzBlogBundle" bundle in "C:/wamp/www/Symfony/src/" using the "yml" format. Do you confirm generation [yes]? Bundle generation Generating the bundle code: OK Checking that the bundle is autoloaded: OK Confirm automatic update of your Kernel [yes]? Enabling the bundle inside the Kernel: OK Confirm automatic update of the Routing [yes]? Importing the bundle routing resource: OK You can now start using the generated code!

C:\wamp\www\Symfony>_

Tout d'abord, je vous rserve une petite surprise : allez voir sur http://localhost/Symfony/web/app_dev.php/hello/winzou ! Le bundle est dj oprationnel ! Le code-exemple gnr affiche le texte pass dans l'URL, vous pouvez donc galement essayer ceci : http://localhost/Symfony/web/app_dev.php/hello/World. Mais, pourquoi il n'y a pas la toolbar en bas de la page ?

C'est normal, c'est juste un petit truc savoir pour viter de s'arracher les cheveux inutilement . La toolbar est un petit bout de code HTML que rajoute Symfony2 chaque page... contenant la balise </body>. Or sur cette page, vous pouvez afficher la source depuis votre navigateur, il n'y a aucune balise HTML en fait, donc Symfony2 n'ajoute pas la toolbar. Pour l'activer rien de plus simple, il nous faut rajouter une toute petite structure HTML. Pour cela ouvrez le fichier src/Sdz/BlogBundle/Resources/views/Default/index.html.twig, c'est la vue utilise pour cette page.

www.siteduzero.com

Symfony2 - Un tutoriel pour dbuter avec le framework Symfony2


L'extension " .twig" signifie qu'on utilise le moteur de templates Twig pour grer nos vues, on en reparlera bien sr. Le fichier est plutt simple, et je vous propose de le changer ainsi : Code : HTML & Django {# src/Sdz/BlogBundle/Resources/views/Default/index.html.twig #} <html> <body> Hello {{ name }}! </body> </html>

20/201

Actualisez la page, et voil une magnifique toolbar apparait en bas de la page ! Seule la balise </body> suffisait, mais quitte changer autant avoir une structure HTML valide .

Que s'est-il pass ?


Dans les coulisses, Symfony2 a fait pas mal de choses, revoyons tout a notre rythme.

Symfony2 a gnr la structure du bundle


Allez dans le rpertoire src/Sdz/BlogBundle, vous pouvez voir tout ce que Symfony2 a gnr pour nous. Rappelez-vous la structure d'un bundle que nous avons vu au chapitre prcdent : Symfony2 en a gnr la majorit ! A savoir : le seul fichier obligatoire pour un bundle est en fait la classe SdzBlogBundle.php la racine du rpertoire. V ous pouvez l'ouvrir et voir ce qu'il contient : pas trs intressant en soi ; tant mieux que Symfony l'ait gnr tout seul . Sachez-le ds maintenant : nous ne modifierons presque jamais ce fichier, vous pouvez passer votre chemin.

Symfony2 a enregistr notre bundle auprs du Kernel


Le bundle est cr, mais il faut dire Symfony2 de le charger. Pour cela il faut configurer le noyau (le Kernel) pour qu'il le charge. Rappelez-vous, la configuration de l'application se trouve dans le rpertoire app/. En loccurrence, la configuration du noyau se fait dans le fichier app/AppKernel.php : Code : PHP <?php use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Config\Loader\LoaderInterface; class AppKernel extends Kernel { public function registerBundles() { $bundles = array( new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new Symfony\Bundle\SecurityBundle\SecurityBundle(), new Symfony\Bundle\TwigBundle\TwigBundle(), new Symfony\Bundle\MonologBundle\MonologBundle(), new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(), new Symfony\Bundle\AsseticBundle\AsseticBundle(), new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(), new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), new JMS\AopBundle\JMSAopBundle(), new JMS\DiExtraBundle\JMSDiExtraBundle($this), new JMS\SecurityExtraBundle\JMSSecurityExtraBundle(), new Sdz\BlogBundle\SdzBlogBundle(), ); { if (in_array($this->getEnvironment(), array('dev', 'test')))

$bundles[] = new Acme\DemoBundle\AcmeDemoBundle(); $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle(); $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle(); } } } return $bundles;

// ...

www.siteduzero.com

Symfony2 - Un tutoriel pour dbuter avec le framework Symfony2


Cette classe permet donc uniquement de dfinir quels bundles charger pour l'application. V ous pouvez le voir, ils sont instancis dans un simple tableau. Les lignes 11 22 dfinissent les bundles charger pour l'environnement de production. Les lignes 26 29 dfinissent les bundles charger en plus pour l'environnement de dveloppement. Comme vous pouvez le voir, le gnrateur du bundle a modifi lui-mme ce fichier pour y rajouter la ligne 22, surligne en jaune. C'est ce que l'on appelle "enregistrer le bundle dans l'application". V ous pouvez voir galement qu'il en existe plein d'autres, ce sont tous les bundles par dfaut qui apportent des fonctionnalits de base au framework Symfony2. En fait, quand on parle de Symfony2, on parle la fois de ses composants (Kernel, Routeur, etc.) et de ses bundles.

21/201

Symfony2 a enregistr nos routes auprs du Routeur


Les routes ? Le Routeur ?

Pas de panique nous verrons tout cela dans les prochains chapitres. Sachez juste pour l'instant que le rle du Routeur, que nous avons brivement vu sur le schma du chapitre prcdent, est de dterminer quel contrleur excuter en fonction de l'URL appele. Pour cela, il utilise les routes. Chaque bundle dispose de ses propres routes. Pour notre bundle frachement cr, vous pouvez les voir dans le fichier src/Sdz/BlogBundle/Resources/config/routing.yml. En l'occurrence il n'y en a qu'une seule : Code : YAML # src/Sdz/BlogBundle/Resources/config/routing.yml SdzBlogBundle_homepage: pattern: /hello/{name} defaults: { _controller: SdzBlogBundle:Default:index }

Or ces routes ne sont pas charges automatiquement, il faut dire au Routeur " Bonjour, mon bundle SdzBlogBundle contient des routes qu'il faut que tu viennes chercher". Cela se fait, vous l'aurez devin, dans la configuration de l'application. Cette configuration se trouve toujours dans le rpertoire app/, en l'occurrence pour les routes il s'agit du fichier app/config/routing.yml : Code : YAML # app/config/routing.yml SdzBlogBundle: resource: "@SdzBlogBundle/Resources/config/routing.yml" prefix: /

Ce sont ces lignes qui importent le fichier de routes situ dans notre bundle. Ces lignes ont dj t gnres par le gnrateur de bundle, vraiment pratique lui !

A retenir
Ce qu'il faut retenir de tout cela, c'est que pour qu'un bundle soit oprationnel il faut : Son code source, situ dans src/Application/Bundle, et dont le seul fichier obligatoire est la classe la racine SdzBlogBundle.php Enregistrer le bundle dans le noyau pour qu'il soit charg, en modifiant le fichier app/AppKernel.php Enregistrer les routes (si le bundle en contient) dans le Routeur pour qu'elles soient charges, en modifiant le fichier app/config/routing.yml Ces trois points sont bien sr effectus automatiquement lorsqu'on utilise le gnrateur. Mais vous pouvez tout fait crer un bundle sans l'utiliser, et il faudra alors remplir cette petite checklist . Par la suite, tout notre code source sera situ dans des bundles. Un moyen trs propre de bien structurer son application. V oil votre bundle est maintenant prt ! A partir de l nous pourrons dvelopper notre propre code et enfin faire notre premire page. Tout ceci sera dtaill dans la prochaine partie. Cette premire partie vous aura montr les possibilits immenses qu'offre Symfony2... sans pour l'instant crire une seule ligne de code ! Retenez bien galement l'utilisation de la console. Nous nous en servirons de temps en temps, chaque fois que Symfony2 propose des commandes pour amliorer notre vie de dveloppeur . Tous les chapitres de cette premire partie ont t intelligemment relus et corrigs par narzil, un grand merci !

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World

22/201

Partie 2 : Les bases de Symfony2 : Hello World


Cette partie a pour objectif de crer une premire page, et d'en matriser les composantes. Ce sont ici les notions les plus importantes de Symfony2, prenez-donc votre temps pour bien comprendre tout ce qui est abord.

Mon premier Hello World avec Symfony2


L'objectif de ce chapitre est de crer de toute pice notre premire page avec Symfony2 : une simple page blanche comprenant un Hello World! . Nous allons donc crer tous les lments indispensables pour concevoir une telle page. Nous allons donc avoir une vue d'ensemble de tous les acteurs qui interviennent dans la cration d'une page : routeur, contrleur et template. Pour cela, tous les dtails ne seront pas expliqus afin de se concentrer sur l'essentiel : la faon dont ils se coordonnent. V ous devrez attendre les prochains chapitre pour tudier un un ces trois acteurs, patience donc ! Ne bloquez donc pas sur un point si vous ne comprenez pas tout, forcez-vous juste comprendre l'ensemble. la fin du chapitre, vous aurez une vision globale de la cration d'une page et l'objectif sera atteint. Bonne lecture !

Crons notre route


Nous travaillons dans notre bundle SdzBlogBundle, placez vous donc dans son rpertoire : src/Sdz/BlogBundle. Pour crer une page, il faut d'abord dfinir l'URL laquelle elle sera accessible. Pour cela, il faut crer la route de cette page.

Le routeur (ou router ) ? Une route ?


Objectif
L'objectif du routeur est de dire Symfony2 ce qu'il doit faire lorsque que l'on appelle l'URL /hello-world (par exemple). Nous devons donc crer une route qui va dire : lorsque l'on est sur l'URL /hello-world , alors on appelle le contrleur Blog qui va afficher un Hello World. . V oici un schma de l'interaction Noyau - Routeur - Route :

Comme je l'ai dit, nous ne toucherons ni au noyau, ni au routeur : nous nous occuperons juste des routes. Ouf, voil du travail en moins.

1. Crons notre fichier de routes


Les routes se dfinissent dans un simple fichier texte, que Symfony2 a dj gnr pour notre SdzBlogBundle. Usuellement, on nomme ce fichier Resources/config/routing.yml dans le rpertoire du bundle. Ouvrez le fichier, et rajoutez cette route la suite de celle qui existe dj : Code : YAML # src/Sdz/BlogBundle/Resources/config/routing.yml HelloTheWorld: pattern: /hello-world defaults: { _controller: SdzBlogBundle:Blog:index }

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World

23/201

V ous venez de crer votre premire route ! Contrairement ce que vous pourriez penser, l'indentation se fait avec 4 espaces par niveau, pas avec des tabulations ! Je le prcise en grand ici parce qu'un jour vous ferez l'erreur (l'invitable ne peut tre vit), et vous me remercierez de vous avoir mis sur la voie Et ceci est valable pour tous vos fichiers .yml Attention galement, il semble y avoir des erreurs lors des copier-coller depuis le tutoriel vers les fichiers .yml. Si vous rencontrez une obscure erreur, pensez bien dfinir l'encodage du fichier en "UTF-8 sans BOM" et supprimer les ventuels caractres non dsirs. C'est un bogue trange qui provient du site, mais dont on ne connait pas l'origine. L'esquive est de toujours recopier les exemples Yaml que je vous donne, et de ne pas les copier-coller.

Essayons de comprendre rapidement cette route : Le " HelloTheWorld" est le nom de la route. Il est assez arbitraire, et vous permet juste de vous y retrouver par la suite. La seule contrainte est qu'il soit unique ; Le " pattern " correspond l' URL laquelle nous souhaitons que notre hello world soit accessible. C'est ce qui permet la route de dire : cette URL est pour moi, je prends ; Le " defaults" correspond aux paramtres de la routes, dont : Le " _controller", qui correspond l'action (ici, "index") que l'on veut excuter et au contrleur (ici, "Blog") que l'on va appeler (un contrleur peut contenir plusieurs actions, c'est dire plusieurs pages). Ne vous inquitez pas, un chapitre complet est ddi au routeur et vous permettra de jouer avec. Pour l'instant ce fichier nous permet juste d'avancer. Mais avant d'aller plus loin, penchons-nous sur la valeur que l'on a donne _controller : SdzBlogBundle:Blog:index . Cette valeur se dcoupe en suivant les pointills les deux-points ( : ) : SdzBlogBundle est le nom de notre bundle, celui dans lequel Symfony2 ira chercher le contrleur ; Blog est le nom du contrleur ouvrir. En terme de fichier, cela correspond controller/BlogController.php dans le rpertoire du bundle. Dans notre cas, nous avons src/Sdz/BlogBundle/controller/BlogController.php comme chemin absolu. index est le nom de la mthode excuter au sein du contrleur.

2. Informons Symfony2 que nous avons des routes pour lui


On l'a vu prcdemment, grce au bon travail du gnrateur, Symfony2 est dj au courant du fichier de routes de notre bundle. Mais sachez que ce n'est pas par magie ! Il faut que vous sachiez comment tout cela s'imbrique. Ouvrez le fichier de configuration globale de notre application : app/config/config.yml. Dans ce fichier, il y a plein de valeurs, mais la section qui nous intresse est la section "router", la ligne 9 que je vous remets ici : Code : YAML # app/config/config.yml router: resource: "%kernel.root_dir%/config/routing.yml" strict_parameters: %kernel.debug%

Cette section indique au routeur qu'il doit chercher les routes dans le fichier app/config/routing.yml (" %kernel.root_dir%" est un paramtre qui vaut "app" dans notre cas). Le routeur va donc se contenter d'ouvrir ce fichier. Ouvrez-le galement : Code : YAML # app/config/routing.yml SdzBlogBundle: resource: "@SdzBlogBundle/Resources/config/routing.yml" prefix: /

Outre les commentaires, vous voyez que le gnrateur a insr une route spciale (qui n'a pas de "pattern", mais une "resource") qui va importer le fichier de routes de notre bundle. C'est parce que ce fichier routing.yml tait vide (avant la gnration du bundle) que l'on avait une erreur page introuvable en mode prod : comme il n'y a aucune route dfinie, Symfony2 nous informe juste titre qu'aucune page n'existe. Pour information, si le mode dev ne nous donnait pas d'erreur, c'est parce que le mode dev charge le fichier routing_dev.yml et non routing.yml. Et dans ce fichier, allez voir, il y a bien quelques routes dfinies. Et il y a aussi la ligne qui importe le fichier routing.yml, afin d'avoir les routes du mode prod dans le mode dev (l'inverse tant bien sr faux).

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


Bref, vous n'avez rien modifier ici, c'tait juste pour que vous sachiez que l'import du fichier de routes d'un bundle n'est pas automatique, il se dfinit dans le fichier de routes global. Revenons nos moutons. En fait, on aurait pu ajouter notre route HelloTheWorld directement dans ce fichier routing.yml. Cela aurait fonctionn et cela aurait t plutt rapide. Mais c'est oublier notre dcoupage en bundles ! En effet, cette route concerne le bundle du blog, elle doit donc se trouver dans notre bundle et pas ailleurs. N'oubliez jamais ce principe. Cela permet notre bundle d'tre indpendant : si plus tard nous ajoutons, modifions ou supprimons des routes dans notre bundle, nous ne toucherons qu'au fichier src/Sdz/BlogBundle/Resources/config/routing.yml au lieu de app/config/routing.yml. Et voil, il n'y a plus qu' crer le fameux contrleur Blog ainsi que sa mthode index !

24/201

Crons notre contrleur Le rle du contrleur


Rappelez-vous ce que nous avons dit sur le MVC : le contrleur est la glu de notre site ; il utilise tous les autres composants (base de donnes, formulaires, templates, etc.) pour gnrer la rponse suite notre requte ; c'est ici que rsidera toute la logique de notre site : si l'utilisateur est connect et qu'il a le droit de modifier cet article, alors j'affiche le formulaire d'dition des articles de mon blog.

Crons notre contrleur


1. Le fichier de notre contrleur Blog
Dans un bundle, les contrleurs se trouvent dans le rpertoire Controller du bundle. Rappelez-vous : dans la route, on a dit qu'il fallait faire appel au contrleur nomm Blog . Le nom des fichiers des contrleurs doit respecter une convention trs simple : il doit commencer par le nom du contrleur, ici Blog , suivi du suffixe Controller . Au final, on doit donc crer le fichier src/Sdz/BlogBundle/Controller/BlogController.php. Mme si Symfony2 a dj cr un contrleur DefaultController pour nous, ce n'est qu'un exemple, on va utiliser le notre. Ouvrez donc notre BlogController.php et collez-y le code suivant : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class BlogController extends Controller { public function indexAction() { return new Response("Hello World !"); } }

Surprise : allez voir sur http://localhost/Symfony/web/app_dev.php/hello-world ! Mme bundle mais contrleur diffrent, on en fait des choses ! Maintenant, essayons de comprendre rapidement ce fichier : ligne 5 : on se place dans le namespace des contrleurs de notre bundle. Rien de bien compliqu, suivez la structure des rpertoires dans lequel se trouve le contrleur ; ligne 7 : notre contrleur hrite de ce contrleur de base. Il faut donc le charger grce au use ; ligne 8 : notre contrleur va utiliser l'objet Response, il faut donc le charger grce au use ; ligne 10 : le nom de notre contrleur respecte le nom du fichier pour que l'autoload fonctionne ; ligne 12 : on dfinit la mthode indexAction(). N'oubliez pas de mettre le suffixe Action derrire le nom de la mthode ; ligne 14 : on cre une rponse toute simple. L'argument de l'objet Response est le contenu de la page que vous envoyez au visiteur, ici Hello World ! . Puis on retourne cet objet. Bon : certes, le rendu n'est pas trs joli, mais au moins, nous avons atteint l'objectif d'afficher nous-mmes un Hello World. Pourquoi indexAction() ? J'ai pas suivi l.

En effet il faut savoir que le nom des mthodes des contrleurs doivent respecter une convention. Lorsque dans la route on parle de l'action "index", dans le contrleur on doit dfinir la mthode indexAction(), c'est--dire le nom de l'action suivi du suffixe "Action", tout simplement. Il n'y a pas tellement rflchir, c'est une simple convention pour distinguer les mthodes qui vont tre appeles par le noyau (les xxxAction()) des autres mthodes que vous pourriez crer au sein de votre contrleur. Mais crire le contenu de sa page de cette manire dans le contrleur, a n'est pas trs pratique, et en plus de cela, on ne

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


respecte pas le modle MVC. Utilisons donc les templates !

25/201

Crons notre template Twig Les templates avec Twig


Savez-vous ce qu'est un moteur de template ? C'est un script qui permet d'utiliser des templates, c'est--dire des fichiers qui ont pour but d'afficher le contenu de votre page HTML de faon dynamique, mais sans PHP. Comment ? Avec leur langage eux. Chaque moteur a son propre langage. Avec Symfony2, nous allons employer le moteur Twig. V oici un exemple de comparaison entre un template simple en PHP et un template en langage Twig . PHP : Code : PHP <!DOCTYPE html> <html> <head> <title>Welcome to Symfony!</title> </head> <body> <h1><?php echo $page_title ?></h1> <ul id="navigation"> <?php foreach ($navigation as $item): ?> <li> <a href="<?php echo $item->getHref() ?>"><?php echo $item->getCaption() ?></a> </li> <?php endforeach; ?> </ul> </body> </html>

Et ce mme template, mais avec Twig : Code : HTML & Django <!DOCTYPE html> <html> <head> <title>Welcome to Symfony!</title> </head> <body> <h1>{{ page_title }}</h1> <ul id="navigation"> {% for item in navigation %} <li><a href="{{ item.href }}">{{ item.caption }}</a></li> {% endfor %} </ul> </body> </html>

Ils se ressemblent, soyons d'accord. Mais celui ralis avec Twig est bien plus facile lire ! Pour afficher une variable, vous faites juste {{ ma_var }} au lieu de <?php echo $ma_var ?>. Le but en fait est de faciliter le travail de votre designer. Un designer ne connat pas forcment le PHP, ni forcment Twig d'ailleurs. Mais Twig est trs rapide prendre en main, plus rapide crire et lire, et il dispose aussi de fonctionnalits trs intressantes. Par exemple, imaginons que votre designer veuille mettre les titres en lettres majuscules (COMME CECI). Il lui suffit de faire : {{ titre|upper }}, o titre est la variable qui contient le titre d'un article de blog par exemple. C'est plus joli que <?php echo strtoupper($titre) ?>, vous tes d'accord ? Nous verrons dans le chapitre ddi Twig les nombreuses fonctionnalits que le moteur vous propose et qui vont vous faciliter la vie. En attendant, nous devons avancer sur notre Hello World ! .

Utiliser Twig avec Symfony2


Comment utiliser un template Twig depuis notre contrleur, au lieu d'afficher notre texte tout simple ?

1. Crons le fichier du template


Le rpertoire des templates (ou vues) d'un bundle se trouve dans le dossier " Resources/views". Ici encore on ne va pas utiliser le template situ dans le rpertoire " Default" gnr par Symfony2. Crons notre propre rpertoire " Blog" et crons notre template " index.html.twig" dans ce rpertoire. Nous avons donc le fichier " src/Sdz/BlogBundle/Resources/views/Blog/index.html.twig". Blog/index.html.twig ? Dcoupons ce nom : Blog/ est le nom du rpertoire. Nous l'avons appel comme notre contrleur afin de nous y retrouver (ce n'est

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


pas une obligation, mais c'est fortement recommand) ; index est le nom de notre template qui est aussi le nom de la mthode de notre contrleur (idem, pas obligatoire, mais recommand) ; html correspond au format du contenu de notre template. Ici, nous allons y mettre du code HTML, mais vous serez amen vouloir y mettre du XML ou autre : vous changerez donc cette extension. Cela permet de mieux s'y retrouver ; twig est le format de notre template. Ici, nous utilisons Twig comme moteur de template, mais il est toujours possible d'utiliser des templates PHP. Revenez notre template et copiez-collez ce code l'intrieur : Code : HTML & Django {# src/Sdz/BlogBundle/Resources/views/Blog/index.html.twig #} <!DOCTYPE html> <html> <head> <title>Bienvenue sur ma premire page avec le Site du Zro !</title> </head> <body> <h1>Hello World !</h1> <p> Le Hello World est un grand classique en programmation. Il signifie normment, car cela veut dire que vous avez russi excuter le programme pour accomplir une tche afficher ce hello world !

26/201

simple : </p> </body> </html>

Dans ce template, nous n'avons utilis ni variable, ni structure Twig. En fait, c'est un simple fichier contenant du code HTML pur !

2. Appelons ce template depuis le contrleur


Il ne reste plus qu' appeler ce template. C'est le rle du contrleur, c'est donc au sein de la mthode indexAction() que nous allons appeler le template. Cela se fait trs simplement avec la mthode <?php $this->render(). Cette mthode prend en paramtre le nom du template et retourne un objet de type Response avec pour contenu le contenu de notre template. V oici le contrleur modifi en consquence : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class BlogController extends Controller { public function indexAction() { return $this->render('SdzBlogBundle:Blog:index.html.twig'); } }

Nous n'avons modifi que la ligne 14. La convention pour le nom du template est la mme que pour le nom du contrleur, souvenez-vous : NomDuBundle:NomDuContrleur:NomDeLAction. Maintenant, retournez sur la page http://localhost/Symfony/web/app_dev.php/hello-world et profitez ! V ous avez des problmes d'accent ? Faites attention bien dfinir l'encodage de vos templates en UTF8 Sans BOM.

Notez galement l'apparition de la toolbar en bas de la page. Je vous l'avais dj dit, Symfony2 l'ajoute automatiquement lorsqu'il dtecte la balise fermante </body>. C'est pour cela que nous ne l'avions pas tout l'heure avec notre Hello World tout simple. V oulez-vous vous amuser un peu avec les variables Twig ? Modifiez la ligne 14 du contrleur pour rajouter un 2e argument la mthode render() : Code : PHP

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


<?php return $this->render('SdzBlogBundle:Blog:index.html.twig', array('nom' => 'winzou')); Puis modifiez votre template en remplaant la balise h1 par la suivante : Code : HTML <h1>Hello {{ nom }} !</h1> C'est tout ! Rechargez la page. Bonjour vous galement. chapitre ddi Twig bien videmment. On verra plus en dtails le passage de variables dans le

27/201

Notre objectif : crer un blog Le fil conducteur : un blog


Tout au long de ce cours, nous construirons un blog. Cela me permet d'utiliser des exemples cohrents entre eux et de vous montrer comment construire un blog de toutes pices. Bien sr, libre vous d'adapter les exemples au projet que vous souhaitez mener, je vous y encourage, mme ! Le choix du blog n'est pas trs original, mais il permet que l'on se comprenne bien : vous savez dj ce qu'est un blog, vous comprendrez donc, en thorie, tous les exemples.

Notre blog
Le blog que nous allons crer est trs simple. En voici les grandes lignes : nous aurons des articles auxquels nous attacherons des tags ; nous pourrons lire, crire, diter et rechercher des articles ; nous pourrons crer, modifier et supprimer des tags ; au dbut, nous n'aurons pas de systme de gestion des utilisateurs : nous devrons saisir notre nom lorsque nous rdigerons un article. Puis nous rajouterons la couche utilisateur ; au dbut, il n'y aura pas de systme de commentaires. Puis nous ajouterons cette couche commentaire.

Un peu de nettoyage
Avec tous les lments gnrs par Symfony2 et les ntres, il y a un peu de redondance. V ous pouvez donc supprimer joyeusement : Le contrleur Controller/DefaultController.php; Les vues dans le rpertoire Resources/views/Default; La route SdzBlogBundle_homepage dans Resources/config/routing.yml. Ainsi que tout ce qui concerne le bundle AcmeDemoBundle, un bundle de dmonstration intgr dans la distribution standard de Symfony2 et dont nous ne nous servirons pas. Supprimez donc : Le rpertoire src/Acme ; La ligne 26 du fichier app/AppKernel.php, celle qui active le bundle AcmeDemoBundle : Code : PHP <?php $bundles[] = new Acme\DemoBundle\AcmeDemoBundle(); Les 3 premires routes dans le fichier app/config/routing_dev.php : Code : YAML _welcome: pattern: / defaults: { _controller: AcmeDemoBundle:Welcome:index } _demo_secured: resource: "@AcmeDemoBundle/Controller/SecuredController.php" type: annotation _demo: resource: "@AcmeDemoBundle/Controller/DemoController.php" type: annotation prefix: /demo

Schma de dveloppement sous Symfony2


Si vous rafrachissez la page pour vrifier que tout est bon, il est possible que vous ayez une erreur ! En effet il faut prendre ds maintenant un rflexe Symfony2 : vider le cache. Car Symfony, pour nous offrir autant de fonctionnalits et tre si rapide, utilise beaucoup son cache (des calculs qu'il ne fait qu'une fois puis qu'il stocke). Or aprs certaines modifications, le cache n'est plus jour et il se peut que cela gnre des erreurs. Deux cas de figure :

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


En mode prod, c'est simple, Symfony2 ne vide jamais le cache. Ca lui permet de ne faire aucune vrification sur la validit du cache (ce qui prend du temps), et de servir les pages trs rapidement vos visiteurs. La solution : vider le cache la main chaque fois que vous faites des changements. Cela se fait grce la commande php app/console cache:clear --env=prod . En mode dev, c'est plus simple et plus compliqu. Lorsque vous modifiez votre code, Symfony reconstruit une bonne partie du cache la prochaine page que vous chargez. Donc pas forcment besoin de vider le cache. Seulement, comme il ne reconstruit pas tout, il peut apparatre des bugs parfois. Dans ce cas, un petit php app/console cache:clear rsout le problme en 3 secondes ! Parfois, il se peut que la commande cache:clear gnre des erreurs lors de son excution. Dans ce cas, essayez de relancer la commande, des fois une deuxime passe peut rsoudre les problmes. Dans le cas contraire, supprimez le cache la main en supprimant simplement le rpertoire app/cache/dev (ou app/cache/prod suivant lenvironnement). Typiquement, un schma classique de dveloppement est le suivant : Je fais des changements, je teste ; Je fais des changements, je teste ; Je fais des changements, je teste a ne marche pas, je vide le cache a marche ; Je fais des changements, je teste ; Je fais des changements, je teste ; Je fais des changements, je teste a ne marche pas, je vide le cache a marche ; ... En fin de journe, j'envoie tout sur le serveur de production, je vide obligatoirement le cache, je teste, a marche. videmment, quand je dis "je teste a ne marche pas", j'entends "a devrait marcher et l'erreur rencontre est trange". Si vous faites une erreur dans votre propre code, c'est pas un cache:clear qui va la rsoudre !

28/201

Et maintenant, c'est parti ! Et voil, nous avons cr une page de A Z ! V oici plusieurs remarques sur ce chapitre. D'abord, ne vous affolez pas si vous n'avez pas tout compris. Le but de ce chapitre tait de vous donner une vision globale d'une page Symfony2. V ous avez des notions de bundles, de routes, de contrleurs et de templates : vous savez presque tout ! Il ne reste plus qu' approfondir chacune de ces notions, ce que nous ferons ds le prochain chapitre. Ensuite, sachez que tout n'est pas refaire lorsque vous crez une deuxime page. Je vous invite l, maintenant, crer une page /byebye-world et voyez si vous y arrivez. Dans le cas contraire relisez ce chapitre, puis si vous ne trouvez pas votre erreur, n'hsitez pas poser votre question sur le forum PHP, d'autres Zros qui sont passs par l seront ravis de vous aider. Sur le forum, pensez mettre le tag [Symfony2] dans le titre de votre sujet, afin de s'y retrouver.

Enfin, prparez-vous pour la suite, les choses srieuses commencent !

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World

29/201

Le routeur de Symfony2
Comme nous avons pu l'apercevoir, le rle du routeur est, partir d'une URL, de dterminer quel contrleur appeler et avec quels arguments. Cela permet de configurer son application pour avoir de trs belles URL, ce qui est important pour le rfrencement et mme pour le confort des visiteurs. Soyons d'accord, l'URL /article/le-systeme-de-route est bien plus sexy que index.php?controlleur=article&methode=voir&id=5 ! V ous avez peut-tre dj entendu parler d'URL Rewriting ? Le routeur, bien que diffrent, permet effectivement de faire l'quivalent de l'URL Rewriting , mais il le fait ct PHP, et donc est bien mieux intgr notre code.

Le fonctionnement
L'objectif de ce chapitre est de vous transmettre toutes les connaissances pour pouvoir crer ce que l'on appelle un fichier de mapping des routes (un fichier de correspondances en franais). Ce fichier, gnralement situ dans votreBundle/Resources/config/routing.yml, contient la dfinition des routes. Chaque route fait la correspondance entre une URL et le contrleur appeler. Je vous invite copier-coller ds maintenant ces routes dans le fichier, nous allons travailler dessus dans ce chapitre : Code : YAML # src/sdz/BlogBundle/Resources/config/routing.yml sdzblog_accueil: pattern: /blog defaults: { _controller: SdzBlogBundle:Blog:index } sdzblog_voir: pattern: defaults: /blog/article/{id} { _controller: SdzBlogBundle:Blog:voir }

sdzblog_ajouter: pattern: /blog/ajouter defaults: { _controller: SdzBlogBundle:Blog:ajouter }

Petit rappel au cas o : l'indentation se fait avec 4 espaces par niveau, et non avec des tabulations

V ous pouvez supprimer la route " helloTheWorld" que nous avons cre au chapitre prcdent, elle ne nous resservira plus. V ous pouvez voir qu' la place nous avons maintenant une route " sdzblog_accueil", qui pointe vers la mme action du contrleur.

Fonctionnement du routeur
Dans l'exemple ci-dessus, vous pouvez distinguer trois blocs. Chaque bloc correspond une route. Nous les verrons en dtail plus loin, mais vous pouvez constater que chaque route prend : une entre (ligne pattern ) : c'est l'URL capturer ; une sortie (ligne defaults) : ce sont les paramtres de la route, notamment celui qui dit quel est le contrleur appeler. Le but du routeur est donc, partir d'une URL, de trouver la route correspondante et de retourner le contrleur que veut cette route. Pour trouver la bonne route, le routeur va les parcourir une par une, dans l'ordre du fichier, et s'arrter la premire route qui fonctionne. V oici le schma quivalent au chapitre prcdent, mais actualis pour notre fichier de route juste au-dessus :

Et voici en texte le fonctionnement, pas pas : 1. On appelle l'URL " /blog/article/5" ;

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


2. Le routeur essaie de faire correspondre cette URL avec le pattern de la premire route. Ici, " /blog/article/5" ne correspond pas du tout " /blog" (ligne pattern de la premire route) ; 3. Le routeur passe donc la route suivante. Il essaie de faire correspondre " /blog/article/5" avec " /blog/article/{id}". Nous le verrons plus loin, mais {id} est un paramtre, une sorte de joker je prends tout . Cette route correspond, car nous avons bien : " /blog/article" (URL) = " /blog/article" (route), " 5" (URL) = " {id}" (route) ; 4. Le routeur s'arrte donc, il a trouv sa route ; 5. Il demande la route : Quel contrleur souhaites-tu appeler, et avec quels paramtres ? , la route rpond Je veux le contrleur SdzBlogBundle:Blog:voir, avec le paramtre $id = 5. ; 6. Le routeur renvoie donc ces informations au kernel (le noyau de Symfony2) ; 7. Le noyau va excuter le bon contrleur ! Dans le cas o le routeur ne trouve aucune route correspondante, le noyau de Symfony2 va dclencher une erreur 404. Pour chaque page, il est possible de visualiser toutes les routes que le routeur essaie une une, et celle qu'il utilise finalement. C'est le Profiler qui s'occupe de tracer cela, accessible depuis la barre d'outil : cliquez sur le nom de la route dans la barre d'outils, "sdzblog_accueil" si vous tes sur la page /blog. Ce lien vous amne dans l'onglet "Request" du Profiler, mais allez dans l'onglet "Routing" qui nous intresse :

30/201

Cliquez pour afficher en grand

V ous pouvez voir qu'il y a dj pas mal de routes dfinies alors que nous n'avons rien fait. Ces routes qui commencent par /_profiler/ sont les routes ncessaires au Profiler, dans lequel vous tes. Eh oui, c'est un bundle galement, le bundle WebProfilerBundle !

Convention pour le nom du contrleur


V ous l'avez vu, lorsque l'on dfinit le contrleur appeler dans la route, il y a une convention respecter : la mme que pour appeler un template (nous l'avons vu au chapitre prcdent). Un rappel ne fait pas de mal : lorsque vous crivez SdzBlogBundle:Blog:voir , vous avez trois informations : SdzBlogBundle est le nom du bundle dans lequel aller chercher le contrleur. En terme de fichier, cela signifie pour Symfony2 : Va voir dans le rpertoire de ce bundle. . Dans notre cas, Symfony2 ira voir dans src/Sdz/BlogBundle ; Blog est le nom du contrleur ouvrir. En terme de fichier, cela correspond controller/BlogController.php dans le rpertoire du bundle. Dans dans notre cas, nous avons comme chemin absolu src/Sdz/BlogBundle/controller/BlogController.php ; voir est le nom de l'action excuter au sein du contrleur. Attention lorsque vous dfinissez cette mthode dans le contrleur, vous devez la faire suivre du suffixe Action , comme ceci : <?php public function voirAction().

Les routes de base Crer une route


tudions la premire route plus en dtail : Code : YAML # src/Sdz/BlogBundle/config/Resources/routing.yml sdzblog_accueil: pattern: /blog defaults: { _controller: SdzBlogBundle:Blog:index }

Ce bloc reprsente ce que l'on nomme une route . Elle est constitue au minimum de trois lments : sdzblog_accueil est le nom de la route. Il n'a aucune importance dans le travail du routeur, mais il interviendra lorsque l'on voudra gnrer des URL : eh oui, on n'crira pas l'URL la main mais on fera appel au routeur pour qu'il fasse le travail notre place ! Retenez donc pour l'instant qu'il faut qu'un nom soit unique et clair. On a donc prfix les routes de "sdzblog" pour l'unicit entre bundles ; pattern: /blog est l'URL sur laquelle la route s'applique. Ici, /blog correspond une URL absolue du type http://www.monsite.com/blog ; defaults: { _controller: SdzBlogBundle:Blog:index } correspond au contrleur appeler. V ous avez maintenant les bases pour crer une route simple !

Crer une route avec des paramtres


Reprenons la deuxime route de notre exemple : Code : YAML # src/Sdz/BlogBundle/Resources/config/routing.yml

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


sdzblog_voir: pattern: defaults:

31/201

/blog/article/{id} { _controller: SdzBlogBundle:Blog:voir }

Grce au paramtre {id} dans le pattern de notre route, toutes les URL du type " /blog/article/*" seront gres par cette route, par exemple : " /blog/article/5" ou " /blog/article/654", ou mme " /blog/article/sodfihsodfih" (on n'a pas encore dit que {id} devait tre un nombre, patience !). Par contre, l'URL " /blog/article" ne sera pas intercepte car le paramtre {id} n'est pas renseign. En effet, les paramtres sont par dfaut obligatoires, nous verrons quand et comment les rendre facultatifs plus loin dans ce chapitre. Mais si le routeur sarrtait l, il n'aurait aucun intrt. Toute sa puissance rside dans le fait que ce paramtre {id} est accessible depuis votre contrleur ! Si vous appelez l'URL " /blog/article/5", alors depuis votre contrleur, vous aurez la variable $id (du nom du paramtre) qui aura pour valeur 5 . Je vous invite crer la mthode correspondante dans le contrleur : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class BlogController extends Controller { // ... ici la mthode indexAction() que l'on a dj cre // La route fait appel SdzBlogBundle:Blog:voir, on doit donc dfinir la mthode "voirAction". // On donne cette mthode l'argument $id, pour correspondre au paramtre {id} de la route. public function voirAction($id) { // $id vaut 5 si l'on a appel l'URL /blog/article/5. // Ici, on rcuprera depuis la base de donnes l'article correspondant l'id $id. // Puis on passera l'article la vue pour qu'elle puisse l'afficher. return new Response("Affichage de l'article d'id : ".$id."."); } }

N'oubliez pas de tester votre code l'adresse suivante : http://localhost/Symfony/web/app_dev.php/blog/article/5, et amusezvous changer la valeur du paramtre. V ous pouvez bien sr multiplier les paramtres au sein d'une mme route. Rajoutez cette route juste aprs la route "sdzblog_voir", pour l'exemple : Code : YAML # src/Sdz/BlogBundle/Resources/config/routing.yml sdzblog_voir_slug: pattern: /blog/{annee}/{slug}.{format} defaults: { _controller: SdzBlogBundle:Blog:voirSlug }

Cette route permet d'intercepter les URL suivantes : " /blog/2011/mon-weekend.html" ou " /blog/2012/symfony.xml", etc. Et voici la mthode correspondante qu'on aurait ct contrleur : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class BlogController extends Controller { // ... ici les mthodes indexAction() et voirAction() que l'on a dj cre // On rcupre tous les paramtres en argument de la mthode.

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


public function voirSlugAction($slug, $annee, $format) { // Ici le contenu de la mthode. return new Response("On pourrait afficher l'article correspondant au slug '".$slug."', cr en ".$annee." et au format ".$format."."); } }

32/201

Notez que l'ordre des arguments dans la dfinition de la mthode voirSlugAction() n'a pas d'importance. La route fait la correspondance partir du nom des variables utilises, non partir de leur ordre. C'est toujours bon savoir !

Revenez notre route et notez galement le point entre les paramtres {slug} et {format} : vous pouvez en effet sparer vos paramtres soit avec le slash ( / ), soit avec le point ( . ). Veillez donc ne pas utiliser de point dans le contenu de vos paramtres. Par exemple, pour notre paramtre {slug} : une URL " /blog/2011/mon-weekend.etait.bien.html" ne va pas correspondre cette route car : {annee} = 2011 ; {slug} = mon-weekend ; {format} = etait ; ? = bien ; ? = html ; La route attend des paramtres mettre en face de ces dernires valeurs, et comme il n'y en a pas, cette route dit : Cette URL ne me correspond pas, passez la route suivante. . Attention donc ce petit dtail .

Les routes avances Crer une route avec des paramtres et leurs contraintes
Nous avons cr une route avec des paramtres, trs bien. Mais si quelqu'un essaie d'atteindre l'URL /blog/oaisd/aouish.oasidh, eh bien rien ne l'en empche ! Et pourtant, oaisd n'est pas tellement une anne valide ! La solution ? Les contraintes sur les paramtres. Reprenons notre dernire route sdzblog_voir_slug : Code : YAML # src/Sdz/BlogBundle/Resources/config/routing.yml sdzblog_voir_slug: pattern: /blog/{annee}/{slug}.{format} defaults: { _controller: SdzBlogBundle:Blog:voirSlug }

Nous voulons ne rcuprer que les bonnes URL o l'anne vaut 2010 et non oshidf , par exemple. Cette dernire devrait retourner une erreur 404 (page introuvable). Pour cela, il nous suffit qu'aucune route ne l'intercepte ; ainsi, le routeur arrivera la fin du fichier sans aucune route correspondante et il dclenchera tout seul une erreur 404. Comment faire pour que notre paramtre {annee} n'intercepte pas oshidf ? C'est trs simple : Code : YAML # src/Sdz/BlogBundle/Resources/config/routing.yml sdzblog_voir_slug: pattern: /blog/{annee}/{slug}.{format} defaults: { _controller: SdzBlogBundle:Blog:voirSlug } requirements: annee: \d{4} format: html|xml

Nous avons ajout la section requirements . Comme vous pouvez le voir, on utilise les expressions rgulires pour dterminer les contraintes que doivent respecter les paramtres. Ici : " \d{4}" veut dire quatre chiffres la suite . L'URL /blog/sdff/mon-weekend.html ne sera donc pas intercepte. V ous l'avez reconnu, c'est une expression rgulire. V ous pouvez utiliser n'importe laquelle, je vous invite lire le cours correspondant de M@teo ; " html|xml" signifie soit html, soit xml . L'URL /blog/2011/mon-weekend.rss ne sera donc pas intercepte. N'hsitez surtout pas faire les tests ! Cette route est oprationnelle, nous avons cr l'action correspondante dans le contrleur. Essayez donc de bien comprendre quels paramtres sont valides, lesquels ne le sont pas. V ous pouvez galement changer la section "requirements". Maintenant, nous souhaitons aller plus loin. En effet, si le .xml est utile pour rcuprer l'article au format XML (pourquoi pas ?), le .html semble inutile : par dfaut, le visiteur veut toujours du HTML. Il faut donc rendre le paramtre {format} facultatif.

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World

33/201

Utiliser des paramtres facultatifs


Reprenons notre route et ajoutons-y la possibilit {format} de ne pas tre renseign : Code : YAML # src/Sdz/BlogBundle/Resources/config/routing.yml sdzblog_voir_slug: pattern: /blog/{annee}/{slug}.{format} defaults: { _controller: SdzBlogBundle:Blog:voirSlug, format: html } requirements: annee: \d{4} format: html|xml

Nous avons juste rajout une valeur par dfaut dans le tableau defaults : " format: html". C'est aussi simple que cela ! Ainsi, l'URL /blog/2011/mon-weekend sera bien intercepte et le paramtre format sera mis sa valeur par dfaut, savoir html . Au niveau du contrleur, rien ne change : vous gardez l'argument $format comme avant et celui-ci vaudra html , la valeur par dfaut.

Utiliser des paramtres systme


Prenons l'exemple de notre paramtre {format} : lorsqu'il vaut xml , vous allez afficher du XML et devrez donc envoyer le header avec le bon Content-type . Les dveloppeurs de Symfony2 ont pens nous et prvu des paramtres systme . Ils s'utilisent exactement comme des paramtres classiques, mais effectuent automatiquement des actions supplmentaires : le paramtre {_format} : lorsqu'il est utilis (comme notre paramtre {format}, rajoutez juste un underscore), alors un header avec le Content-type correspondant est envoy. Exemple : vous appelez /blog/2011/monweekend.xml et le routeur va dire l'objet Request que l'utilisateur demande du XML. Ainsi, l'objet Response enverra un header Content-type: application/xml . V ous n'avez plus vous en soucier ! Depuis le contrleur, vous pouvez rcuprer ce format soit avec l'argument $_format comme n'importe quel autre argument, soit via la mthode getRequestFormat() de l'objet Request. Par exemple : <?php $this>get('request')->getRequestFormat() ; le paramtre {_locale} : lorsqu'il est utilis, il va dfinir la langue dans laquelle l'utilisateur souhaite obtenir la page. Ainsi, si vous avez dfini des fichiers de traduction ou si vous employez des bundles qui en utilisent, alors les traductions dans la langue du paramtre {_locale} seront charges. Pensez mettre un requirements: sur la valeur de ce paramtre pour viter que vos utilisateurs ne demandent le russe alors que votre site n'est que bilingue franais-anglais.

Ajouter un prfixe lors de l'import de nos routes


V ous avez remarqu que nous avons mis /blog au dbut du pattern de chacune de nos routes. En effet, on cre un blog, on aimerait donc que toutes les URL aient ce prfixe /blog. Au lieu de les rpter chaque fois, Symfony2 vous propose de rajouter un prfixe lors de l'import du fichier de notre bundle. Modifiez donc le fichier app/config/routing.yml comme suit : Code : Autre # app/config/routing.yml SdzBlogBundle: resource: "@SdzBlogBundle/Resources/config/routing.yml" prefix: /blog

V ous pouvez ainsi enlever la partie /blog de chacune de vos routes. Bonus : si un jour vous souhaitez changer /blog par /blogdemichel, vous n'aurez qu' modifier une seule ligne.

Gnrer des URL Pourquoi gnrer des URL ?


J'ai mentionn plus haut que le routeur pouvait aussi gnrer des URL partir du nom des routes. Ce n'est pas une fonctionnalit annexe, mais bien un outil puissant que nous avons l ! Par exemple, nous avons une route nomme sdzblog_voir qui coute sur l'URL /blog/article/{id}. V ous dcidez un jour de raccourcir vos URL et vous aimeriez bien que vos articles soient disponibles depuis /blog/a/{id}. Si vous aviez crit toutes vos URL la main, vous auriez d toutes les changer la main, une par une. Grce la gnration d'URL, vous ne modifiez que la route : ainsi, toutes les URL gnres seront mises jour ! C'est un exemple simple, mais vous pouvez trouver des cas bien plus rels et tout aussi gnants sans la gnration d'URL.

Comment gnrer des URL ?


1. Depuis le contrleur
Pour gnrer une URL, vous devez le demander au routeur en lui donnant deux arguments : le nom de la route ainsi que les ventuels paramtres de cette route. Depuis un contrleur, c'est la mthode <?php $this->generateUrl() qu'il faut appeler. Par exemple : Code : PHP

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


<?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class BlogController extends Controller { public function indexAction() { // On fixe un id au hasard ici, il sera dynamique par la suite, videmment. $id = 5; // On veut avoir l'URL de l'article d'id $id. $url = $this->generateUrl('sdzblog_voir', array('id' => // $url vaut "/blog/article/5"

34/201

$id));

// On redirige vers cette URL (a ne sert rien, on est d'accord, c'est pour l'exemple !). return $this->redirect($url); } }

Pour gnrer une URL absolue, lorsque vous l'envoyez par e-mail, par exemple, il faut mettre le 3e argument true. Exemple : Code : PHP <?php $url = $this->generateUrl('sdzblog_voir', array('id' => $id), true);

Ainsi, $url vaut http://monsite.com/blog/article/5 et pas uniquement /blog/article/5 .

2. Depuis une vue Twig


Mais vous aurez bien plus l'occasion de devoir gnrer une URL depuis la vue. C'est la fonction path qu'il faut utiliser depuis un template Twig : Code : HTML & Django {# Dans une vue Twig, en considrant bien sr que la variable article_id est disponible #} <a href="{{ path('sdzblog_voir', { 'id': article_id }) }}">Lien vers l'article d'id {{ article_id }}</a>

Et pour gnrer une URL absolue depuis Twig, pas de 3e argument, mais on utilise la fonction url() au lieu de path() . Elle s'utilise exactement de la mme manire, seul le nom change. V oil : vous savez gnrer des URL, ce n'tait vraiment pas compliqu. Pensez bien utiliser la fonction {{ path }} pour tous vos liens dans vos templates.

Application : les routes de notre blog Construction des routes


Revenons notre blog. Maintenant que nous savons crer des routes, je vous propose de faire un premier jet de ce que seront nos URL. V oici les routes que je vous propose de crer, libre vous d'en changer.

Page d'accueil
On souhaite avoir une URL trs simple pour la page d'accueil : /blog. Comme /blog est dfini comme prfixe lors du chargement des routes de notre bundle, le pattern ici est " /". Mais on veut aussi pouvoir parcourir les articles plus anciens, donc il nous faut une notion de page courante. En rajoutant le paramtre facultatif {page}, nous aurons : /blog page = 1

/blog/1 page = 1 /blog/2 page = 2 C'est plutt joli, non ? V oici la route : Code : YAML

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


# src/Sdz/BlogBundle/Resources/config/routing.yml sdzblog_accueil: pattern: /{page} defaults: { _controller: SdzBlogBundle:Blog:index, page: 1 } requirements: page: \d*

35/201

Page de visualisation d'un article


Pour la page d'un unique article, la route est trs simple. Il suffit juste de bien mettre un paramtre {id} qui nous servira rcuprer le bon article ct contrleur. V oici la route : Code : YAML # src/Sdz/BlogBundle/Resources/config/routing.yml sdzblog_voir: pattern: defaults: /article/{id} { _controller: SdzBlogBundle:Blog:voir }

Ajout, modification et suppression


Les routes sont simples : Code : YAML # src/Sdz/BlogBundle/Resources/config/routing.yml sdzblog_ajouter: pattern: /ajouter defaults: { _controller: SdzBlogBundle:Blog:ajouter } sdzblog_modifier: pattern: /modifier/{id} defaults: { _controller: SdzBlogBundle:Blog:modifier } requirements: id: \d+ sdzblog_supprimer: pattern: /supprimer/{id} defaults: { _controller: SdzBlogBundle:Blog:supprimer } requirements: id: \d+

Rcapitulatif
V oici le code complet de notre fichier src/Sdz/BlogBundle/Resources/config/routing.yml : Code : YAML # src/Sdz/BlogBundle/Resources/config/routing.yml sdzblog_accueil: pattern: /{page} defaults: { _controller: SdzBlogBundle:Blog:index, page: 1 } requirements: page: \d* sdzblog_voir: pattern: /article/{id} defaults: { _controller: SdzBlogBundle:Blog:voir } requirements: id: \d+ sdzblog_ajouter: pattern: /ajouter defaults: { _controller: SdzBlogBundle:Blog:ajouter } sdzblog_modifier: pattern: /modifier/{id} defaults: { _controller: SdzBlogBundle:Blog:modifier } requirements: id: \d+ sdzblog_supprimer: pattern: /supprimer/{id} defaults: { _controller: SdzBlogBundle:Blog:supprimer } requirements: id: \d+

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World

36/201

N'oubliez pas de bien ajouter le prfixe /blog lors de l'import de ce fichier, dans app/config/routing.yml : Code : YAML # app/config/routing.yml SdzBlogBundle: resource: "@SdzBlogBundle/Resources/config/routing.yml" prefix: /blog Ce chapitre est termin, et vous savez maintenant tout ce qu'il faut savoir sur le routeur et les routes. Retenez que ce systme de route vous permet premirement d'avoir des belles URL et deuximement de dcoupler le nom de vos URL du nom de vos contrleurs. Rajoutez cela la gnration d'URL, et vous avez un systme extrmement flexible et maintenable. Le tout sans trop d'efforts ! Pour plus d'informations sur le systme de route, n'hsitez pas lire la documentation officielle.

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World

37/201

Les contrleurs avec Symfony2


Ah, le contrleur ! Notre ami ! V ous le savez, c'est lui qui contient toute la logique de notre site Internet. Cependant, cela ne veut pas dire qu'il contient beaucoup de code. En fait, il ne fait qu'utiliser des services, les modles, appeler la vue. Finalement, c'est un chef d'orchestre qui se contente de faire la liaison entre tout le monde. Nous verrons dans ce chapitre ses droits, mais aussi son devoir le plus ultime : retourner une rponse ! Bonne lecture !

Le rle du contrleur Retourner une rponse


Je vous l'ai dit de nombreuses fois depuis le dbut de ce cours : le rle du contrleur est de retourner une rponse. Mais concrtement, qu'est-ce que cela signifie, retourner une rponse ?

Souvenez-vous, Symfony2 s'est inspir des concepts du protocole HTTP. Il existe dans Symfony2 une classe Response. Retourner une rponse signifie donc tout simplement : instancier un objet Response, disons $response et faire un " return $response". V oici le contrleur le plus simple qui soit, c'est le contrleur qu'on avait a cr dans un des chapitres prcdents. Il dispose d'une seule mthode, nomme index , et retourne une rponse qui ne contient que : Hello World ! : Code : PHP <?php namespace Sdz\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class BlogController extends Controller { public function indexAction() { return new Response("Hello World !"); } }

Et voil, votre contrleur remplit parfaitement son rle ! Bien sr, vous n'irez pas trs loin en sachant juste cela. C'est pourquoi la suite de ce chapitre est dcoupe en 2 parties : les objets Request et Response qui vont vous permettre de construire une rponse en fonction de la requte ; les services qui vont vous permettre de raliser tout le travail ncessaire pour prparer le contenu de votre rponse.

Manipuler l'objet Request Les paramtres de la requte


Heureusement, toutes les requtes que l'on peut faire sur un site Internet ne sont pas aussi simples que notre Hello World . Dans bien des cas, une requte contient des paramtres : l'id d'un article afficher ou bien le nom d'un membre chercher dans la base de donnes, etc. Les paramtres sont la base de toute requte : la construction de la page afficher dpend de chacun des paramtres en entre. Ces paramtres, nous savons dj les grer, nous l'avons vu dans le chapitre sur le routeur. Mais voici un petit rappel.

Les paramtres contenus dans les routes


Tout d'abord ct route, souvenez-vous on utilisait dj des paramtres. Prenons l'exemple de la route sdzblog_voir : Code : YAML # src/Sdz/BlogBundle/Resources/config/routing.yml sdzblog_voir: pattern: /article/{id} defaults: { _controller: SdzBlogBundle:Blog:voir } requirements: id: \d+

Ici, le paramtre {id} de la requte est rcupr par la route, qui va le transformer en argument $id pour le contrleur. On a dj fait la mthode correspondante dans le contrleur, la voici pour rappel : Code : PHP

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


<?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class BlogController extends Controller { // ... public function voirAction($id) { return new Response("Affichage de l'article d'id : ".$id."."); } }

38/201

V oici donc la premire manire de rcuprer des arguments : ceux contenus dans la route.

Les paramtres hors routes


En plus des paramtres de routes que nous venons de voir, vous pouvez rcuprer les autres paramtres de l'URL, disons, " l'ancienne". Prenons par exemple l'URL " /blog/article/5?tag=vacances", il nous faut bien un moyen pour rcuprer ce paramtre tag ! C'est ici qu'intervient l'objet Request. Tout d'abord, voici comment rcuprer la requte depuis un contrleur : Code : PHP <?php $request = $this->get('request');

V oil, c'est aussi simple que a ! La mthode get() du contrleur, nous en reparlerons, permet d'accder toute sorte de services, dont notre requte. Maintenant que nous avons notre requte, rcuprons nos paramtres : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class BlogController extends Controller { // ... public function voirAction($id) { // On rcupre la requte. $request = $this->get('request'); // On rcupre notre paramtre tag. $tag = $request->query->get('tag'); return new Response("Affichage de l'article d'id : ".$id.", avec le tag : ".$tag); } }

Et vous n'avez plus qu' tester le rsultat bien sr : /blog/article/9?tag=vacances Nous avons utilis <?php $request->query pour rcuprer les paramtres de l'URL passs en GET , mais vous savez qu'il existe d'autres types de paramtres : Type de paramtres Variables d'URL Variables de formulaire Variables de Mthode Symfony2 Mthode traditionnelle Exemple <?php $request->query>get('tag') <?php $request->request>get('tag') <?php $request->cookies-

<?php $request->query $_GET <?php $request>request <?php $request$_POST $_COOKIE

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


cookies Variables de serveur Variables d'entte >cookies <?php $request>server <?php $request>headers <?php $request>attributes $_COOKIE $_SERVER $_SERVER['HTTP_*'] >get('tag') <?php $request->server>get('REQUEST_URI') <?php $request->headers>get('USER_AGENT') <?php $request->attributes>get('id') Est quivalent : <?php $id

39/201

Paramtres de route

n/a

Avec cette faon d'accder aux paramtres, vous n'avez pas besoin de tester leur existence. Par exemple, si vous faites $request>query->get('sdf') alors que le paramtre "sdf" n'est pas dfinie dans l'URL, cela vous retournera une chane vide, et non une erreur.

Les autres mthodes de l'objet Request


Heureusement, l'objet Request ne se limite pas la rcupration de paramtres. Il permet de savoir plusieurs choses intressantes propos de la requte en cours, voyons ses possibilits.

Rcuprer la mthode de la requte HTTP


Pour savoir si la page a t rcupre via GET (clic sur un lien) ou via POST (envoi d'un formulaire), il existe la mthode <? php $request->getMethod() ?> pour cela : Code : PHP <?php if( $request->getMethod() == 'POST' ) { // Un formulaire a t envoy, on peut le traiter ici. }

Savoir si la requte est une requte Ajax


Lorsque vous utiliserez Ajax dans votre site, vous aurez sans doute besoin de savoir, depuis le contrleur, si la requte en cours est une requte Ajax ou non. Par exemple, pour renvoyer du XML ou du JSON la place du HTML. Pour cela, rien de plus simple ! Code : PHP <?php if( $request->isXmlHttpRequest() ) { // C'est une requte Ajax, retournons du JSON, par exemple. }

Toutes les autres


Pour avoir la liste exhaustive des mthodes disponibles sur l'objet Request, je vous invite lire l'API de cet objet sur le site de Symfony2 : http://api.symfony.com/2.0/Symfony/Com [...] /Request.html. V ous y trouverez toutes les mthodes, mme si nous en avons dj survol les principales.

Manipuler l'objet Response Dcomposition de la construction d'un objet Response


Pour que vous compreniez ce qu'il se passe en coulisse lors de la cration d'une Rponse, voyons la manire longue et dcompose de construire et de retourner une rponse. Pour l'exemple, traitons le cas d'une page d'erreur 404 (page introuvable) : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class BlogController extends Controller { // On modifie voirAction car elle existe dj public function voirAction($id) { // On cre la rponse sans lui donner de contenu pour le

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


moment. $response = new Response; // On dfinit le contenu. $response->setContent('Ceci est une page d\'erreur 404'); // On dfinit le code HTTP. Rappelez-vous, 404 correspond page introuvable . $response->setStatusCode(404); // On retourne la rponse. return $response;

40/201

N'hsitez pas tester cette page, l'URL est http://localhost/Symfony/web/app_dev.php/blog/article/5 si vous avez gard les mmes routes depuis le dbut. Je ne vous le cache pas : nous n'utiliserons jamais cette longue mthode ! Lisez plutt la suite.

Rponses et vues
Gnralement, vous prfrerez que votre rponse soit contenue dans une vue, dans un template. Heureusement pour nous, le contrleur dispose d'un raccourci : la mthode <?php $this->render(). Elle prend en paramtre le nom du template et ses variables, puis s'occupe de tout : crer la rponse, y passer le contenu du template, et retourner la rponse. La voici en action : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; // Nous n'avons plus besoin du use pour l'objet Response // use Symfony\Component\HttpFoundation\Response; class BlogController extends Controller { public function voirAction($id) { // On utilise le raccourci : il cre un objet Response et lui donne comme contenu le contenu du template. return $this->render('SdzBlogBundle:Blog:voir.html.twig', array( 'id' => $id, )); } }

Et voil, en une seule ligne, c'est boucl ! C'est comme a que nous gnrerons la plupart de nos rponses. Finalement, l'objet Response est utilis en coulisses, nous n'avons pas le manipuler directement. N'oubliez pas de crer la vue associe bien entendu : Code : HTML & Django {# src/Sdz/BlogBundle/Resources/view/Blog/voir.html.twig #} <!DOCTYPE html> <html> <head> <title>Lecture de l'article { id }</title> </head> <body> <h1>Hello Article n{{ id }} !</h1> </body> </html>

Si vous ne deviez retenir qu'une seule chose de ce paragraphe, c'est bien cette mthode <?php $this->render(), car c'est vraiment ce que nous utiliserons en permanence.

Rponse et redirection
V ous serez srement amen faire une redirection vers une autre page. Or notre contrleur est oblig de retourner une rponse. Comment grer une redirection ? Eh bien, vous avez peut-tre vit le pige, mais une redirection est une rponse HTTP. Pour faire cela, il existe galement un raccourci du contrleur : la mthode <?php $this->redirect(). Elle prend en paramtre l'URL vers laquelle vous souhaitez faire la redirection et s'occupe de crer une rponse puis d'y dfinir un header qui contiendra

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


votre URL. En action, cela donne : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class BlogController extends Controller { public function voirAction($id) { // On utilise la mthode generateUrl() pour obtenir l'URL de la liste des articles la page 2, par exemple. return $this->redirect( $this>generateUrl('sdzblog_accueil', array('page' => 2)) ); } }

41/201

Essayez d'aller l'adresse http://localhost/Symfony/web/app_dev.php/blog/article/5 et vous serez redirig vers l'accueil !

Changer le Content-type de la rponse


Lorsque vous retournez autre chose que du HTML, il faut que vous changiez le Content-type de la rponse. Ce Contenttype permet au navigateur qui recevra votre rponse de savoir quoi s'attendre dans le contenu. Prenons l'exemple suivant : vous recevez une requte Ajax et souhaitez retourner un tableau en JSON : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class BlogController extends Controller { public function voirAction($id) { // Crons nous-mmes la rponse en JSON, grce la fonction json_encode(). $response = new Response(json_encode(array('id' => $id))); // Ici, nous dfinissons le Content-type pour dire que l'on renvoie du JSON et non du HTML. $response->headers->set('Content-Type', 'application/json'); return $response; // Nous n'avons pas utilis notre template ici, car il n'y en a pas vraiment besoin. } }

Testez ce rendu en allant sur http://localhost/Symfony/web/app_dev.php/blog/article/5.

Les diffrents services Qu'est-ce qu'un service ?


Je vous en ai dj brivement parl : un service est un script qui remplit un rle prcis et que l'on peut utiliser depuis notre contrleur. Imaginez par exemple un service qui a pour but d'envoyer des e-mails. Depuis notre contrleur, on appelle ce service, on lui donne les informations ncessaires (contenu de l'e-mail, destinataire, etc.), puis on lui dit d'envoyer l'e-mail. Ainsi, toute la logique cration et envoi d'e-mail se trouve dans ce service et non dans notre contrleur. Cela nous permet de rutiliser ce service trs facilement ! En effet, si vous codez en dur l'envoi d'e-mail dans un contrleur A et que, plus tard, vous avez envie d'envoyer un autre e-mail depuis un contrleur B, comment rutiliser ce que vous aviez dj fait ? C'est impossible et c'est exactement pour cela que les services existent. Il existe un chapitre sur les services plus loin dans ce tutoriel. N'allez pas le lire maintenant car il demande des notions que vous n'avez pas encore. Patience, sachez juste que c'est un point incontournable de Symfony2 et que nous les traiterons bien plus en dtail par la suite

Accder aux services


Pour accder aux services depuis votre contrleur, il faut utiliser la mthode <?php $this->get() du contrleur. Par

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


exemple, le service pour envoyer des e-mails se nomme justement mailer . Pour employer ce service, nous faisons donc : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class BlogController extends Controller { public function voirAction($id) { // Rcupration du service. $mailer = $this->get('mailer'); // Cration de l'e-mail : le service mailer utilise SwiftMailer, donc nous crons une instance de Swift_Message. $message = \Swift_Message::newInstance() ->setSubject('Hello zro !') ->setFrom('tutorial@symfony2.com') ->setTo('votre@email.com') ->setBody('Coucou, voici un email que vous venez de recevoir !'); // Retour au service mailer, nous utilisons sa mthode send() pour envoyer notre $message. $mailer->send($message); // N'oublions pas de retourner une rponse, par exemple, une page qui afficherait L'email a bien t envoy . return new Response('Email bien envoy'); } }

42/201

Pour que l'envoi d'email fonctionne, n'oubliez pas de configurer vos paramtres si ce n'est pas dj fait. Ouvrez le fichier app/config/parameters.yml pour modifier les paramtres mailer_*. Si vous voulez utiliser votre compte gmail : Code : YAML # app/config/parameters.yml mailer_transport: mailer_host: mailer_user: mailer_password: gmail vous@gmail.com mdp

Et si vous voulez utiliser un serveur SMTP classique : Code : YAML # app/config/parameters.yml mailer_transport: mailer_host: mailer_user: mailer_password: smtp smtp.votre-serveur.fr identifiant mdp

Chargez la page /blog/article/5, et allez lire votre email ! Retenez donc la mthode <?php $this->get('nom_du_service') !

Brve liste des services


Maintenant que vous savez rcuprer un service, encore faut-il connatre leur nom ! Et savoir les utiliser ! Ci-dessous est dresse une courte liste de quelques services utiles.

Templating
Templating est un service qui vous permet de grer vos templates (vos vues, vous l'aurez compris). En fait, vous avez dj utilis ce service via le raccourci <?php $this->render ! V oici la version longue d'un <?php $this>render('MonTemplate') : Code : PHP

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


<?php // ... public function voirAction($id) { // Rcupration du service. $templating = $this->get('templating'); // On rcupre le contenu de notre template. $contenu = $templating>render('SdzBlogBundle:Blog:voir.html.twig'); // On cre une rponse avec ce contenu et on la retourne. return new Response($contenu);

43/201

Le service Templating est utile, par exemple, pour notre e-mail de tout l'heure. Nous avons crit le contenu de l'e-mail en dur, ce qui n'est pas bien, videmment. Nous devrions avoir un template pour cela. Et pour en rcuprer le contenu, nous utilisons <? php $templating->render(). Une autre fonction de ce service qui peut servir, c'est <?php $templating>exists('SdzBlogBundle:Blog:inexistant') qui permet de vrifier si SdzBlogBundle:Blog:inexistant existe ou non.

Request
Eh oui, encore elle. C'est galement un service ! C'est pour cela qu'on l'a rcupr de cette faon : <?php $this>get('request').

Session
Les outils de session sont galement intgrs dans un service. V ous pouvez le rcuprer via <?php $this>get('session'); Pour dfinir et rcuprer des variables en session, il faut utiliser les mthodes get et set , tout simplement : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class BlogController extends Controller { public function voirAction($id) { // Rcupration du service $session = $this->get('session'); // On rcupre le contenu de la variable user_id $user_id = $session->get('user_id'); user_id // On dfinit une nouvelle valeur pour cette variable $session->set('user_id', 91);

// On n'oublie pas de renvoyer une rponse return new Response('Dsol je suis une page de test, je n\'ai rien dire'); } }

Pour connaitre les variables de la session courante, allez dans le profiler (la barre d'outils en bas), rubrique Request, puis descendez tout en bas au paragraphe "Session Attributes". Trs utile pour savoir si vous avez bien les variables de session que vous attendez .

La session se lance automatiquement ds que vous vous en servez. V oyez par exemple ce que le Profiler me dit sur une page o je n'utilise pas la session :

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World

44/201

Et le Profiler aprs que nous ayons dfini la variable user_id en session :

Le Profiler nous donne mme les informations sur la date de cration de la session, etc. Un autre outil trs pratique du service de session est ce que l'on appelle les "messages flash". Un terme prcis pour dsigner en ralit une variable de session qui ne dure que pendant 1 page. C'est une astuce utilise pour les formulaires par exemple : la page qui traite le formulaire dfinit un message flash ("Article bien enregistr" par exemple) puis redirige vers la page de visualisation de l'article nouvellement cr. Sur cette page, le message flash s'affiche, et est dtruit de la session. Alors si l'on change de page ou qu'on l'actualise, le message flash ne sera plus prsent. V oici un exemple d'utilisation : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class BlogController extends Controller { public function voirAction($id) { return $this->render('SdzBlogBundle:Blog:voir.html.twig', array( 'id' => $id )); } // Rajoutez cette mthode ajouterAction : public function ajouterAction() { // Bien sr, cette mthode devra rellement ajouter l'article, // Mais faisons comme si c'tait le cas. $this->get('session')->getFlashBag()->add('info', 'Article bien enregistr'); // Le "flashBag" que l'on a nomm "info" ici peut contenir plusieurs messages : $this->get('session')->getFlashBag()->add('info', 'Oui oui, il est bien enregistr !'); // Puis on redirige vers la page de visualisation de cet

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


article. return $this->redirect( $this->generateUrl('sdzblog_voir', array('id' => 5)) ); } }

45/201

V ous pouvez voir que la mthode ajouterAction dfinit un message flash (appel ici "info"). La lecture de ce message se fait dans la vue de l'action voirAction, que j'ai modifi comme ceci : Code : HTML & Django {# src/Sdz/BlogBundle/Resources/views/Blog/voir.html.twig #} <!DOCTYPE html> <html> <head> <title>Bienvenue sur ma premire page avec le Site du Zro !</title> </head> <body> <h1>Lecture d'un article</h1> <p> {# On affiche tous les messages flash #}

{% for message in app.session.flashbag.get('info') %} <p>{{ message }}</p> {% endfor %} </p> <p> }}<br /> Ici nous pourrons lire l'article ayant comme id : {{ id

Mais pour l'instant, nous ne savons pas encore le faire, cela viendra ! </p> </body> </html>

La variable Twig {{ app }} est une variable globale, disponible partout dans vos vues. Elle contient quelques variables utiles nous le verrons, dont le service session que nous venons d'utiliser via {{ app.session }} Essayez d'aller sur http://localhost/Symfony/web/app_dev.php/blog/ajouter vous allez tre redirig et voir le message flash. Faites F5 et hop, il a disparu !

Sachez galement que le service "session" est aussi accessible depuis le service "request". Ainsi, depuis un contrleur vous pouvez faire : Code : PHP <?php $session = $this->get('request')->getSession();

Les autres et les ntres !


Il existe videmment bien d'autres services : nous les rencontrerons au fur et mesure dans ce cours. Mais il existera surtout nos propres services ! En effet, la plupart des outils que nous allons crer (un formulaire, un gestionnaire d'utilisateurs personnalis, etc.) devront tre utiliss plusieurs fois. Quoi de mieux, dans ce cas, que de les dfinir en tant que service ? Nous verrons cela dans la partie 4, mais sachez qu'aprs une petite tape de mise en place (configuration, quelques conventions), les services sont vraiment trs pratiques !

Application : le contrleur de notre blog Construction du contrleur


Notre blog est un bundle plutt simple. On va mettre toutes nos actions dans un seul contrleur Blog . Plus tard, nous pourrons ventuellement crer un contrleur Tag pour manipuler les tags. Malheureusement, on ne connat pas encore tous les services indispensables. ce point du cours, on ne sait pas raliser un formulaire, manipuler les articles dans la base de donnes, ni mme crer de vrais templates. Pour l'heure, notre contrleur sera donc trs simple. On va crer la base de toutes les actions que l'on a mises dans nos routes. Je vous remets sous les yeux nos routes, et on enchane sur le contrleur : Code : YAML # src/Sdz/BlogBundle/Resources/config/routing.yml sdzblog_accueil: pattern: /{page}

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


defaults: { _controller: SdzBlogBundle:Blog:index, page: 1 } requirements: page: \d* sdzblog_voir: pattern: /article/{id} defaults: { _controller: SdzBlogBundle:Blog:voir } requirements: id: \d+ sdzblog_ajouter: pattern: /ajouter defaults: { _controller: SdzBlogBundle:Blog:ajouter } sdzblog_modifier: pattern: /modifier/{id} defaults: { _controller: SdzBlogBundle:Blog:modifier } requirements: id: \d+ sdzblog_supprimer: pattern: /supprimer/{id} defaults: { _controller: SdzBlogBundle:Blog:supprimer } requirements: id: \d+

46/201

Et le contrleur Blog : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\Httpfoundation\Response; class BlogController extends Controller { public function indexAction($page) { // On ne sait pas combien de pages il y a, mais on sait qu'une page // doit tre suprieure ou gale 1. if( $page < 1 ) { // On dclenche une exception NotFoundHttpException, cela va afficher // la page d'erreur 404 (on pourra personnaliser cette page plus tard, d'ailleurs). throw $this->createNotFoundException('Page inexistante (page = '.$page.')'); } // Ici, on rcuprera la liste des articles, puis on la passera au template. // Mais pour l'instant, on ne fait qu'appeler le template. return $this->render('SdzBlogBundle:Blog:index.html.twig');

public function voirAction($id) { // Ici, on rcuprera l'article correspondant l'id $id. } return $this->render('SdzBlogBundle:Blog:voir.html.twig');

public function ajouterAction() { // La gestion d'un formulaire est particulire, mais l'ide est la suivante : if( $this->get('request')->getMethod() == 'POST' ) { // Ici, on s'occupera de la cration et de la gestion du formulaire. $this->get('session')->getFlashBag()->add('notice', 'Article bien enregistr'); cet article. // Puis on redirige vers la page de visualisation de

return $this->redirect( $this>generateUrl('sdzblog_voir', array('id' => 5)) ); }

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


// Si on n'est pas en POST, alors on affiche le formulaire. return $this>render('SdzBlogBundle:Blog:ajouter.html.twig'); } public function modifierAction($id) { // Ici, on rcuprera l'article correspondant l'$id. // Ici, on s'occupera de la cration et de la gestion du formulaire. return $this>render('SdzBlogBundle:Blog:modifier.html.twig'); } public function supprimerAction($id) { // Ici, on rcuprera l'article correspondant l'$id. // Ici, on grera la suppression de l'article en question. return $this>render('SdzBlogBundle:Blog:supprimer.html.twig'); } }

47/201

retenir
L'erreur 404
Je vous ai donn un exemple qui vous montre comment dclencher une erreur 404. C'est quelque chose que l'on fera souvent, par exemple ds qu'un article n'existera pas ou qu'un argument ne sera pas bon (page = 0), etc. Lorsque l'on dclenche cette exception, le noyau l'attrape et gnre une belle page d'erreur 404. V ous pouvez aller voir le cookbook Comment personnaliser ses pages d'erreur.

La dfinition des mthodes


Nos mthodes vont tre appeles par le noyau : elles doivent donc respecter le nom et les arguments que nous avons dfini dans nos routes et se trouver dans le scope public . V ous pouvez bien entendu rajouter d'autres mthodes, par exemple pour excuter une fonction que vous rutiliserez dans deux actions diffrentes. Dans ce cas, vous ne devez pas les suffixer de Action (afin de ne pas confondre).

Testons-le
Naturellement, seules les actions "index" et "voir" en vont fonctionner car nous n'avons pas cr les templates associs (ce sera fait dans le prochain chapitre). Cependant, nous pouvons voir le type d'erreur que Symfony2 nous gnre. Allez sur la page de suppression d'un article, l'adresse http://localhost/Symfony/web/app_dev.php/blog/supprimer/5. V ous pouvez voir que l'erreur est trs explicite et nous permet de voir directement ce qui ne va pas. On a mme les logs en dessous de l'erreur : on peut voir tout ce qui a fonctionn avant que l'erreur ne se dclenche. Notez par exemple le log n4 : Citation Matched route "sdzblog_supprimer" (parameters: "_controller": "Sdz\BlogBundle\Controller\BlogController::supprimerAction", "id": "5", "_route": "sdzblog_supprimer") On voit que c'est bien la bonne route qui est utilise, super ! On voit aussi que le paramtre id est bien dfinit 5 par dfaut : re-super ! On peut galement tester notre erreur 404 gnre manuellement lorsque ce paramtre page est zro. Allez sur http://localhost/Symfony/web/app_dev.php/blog/0, et admirez notre erreur. Regardez entre autres la toolbar, nous avons bien :

Trs pratique pour vrifier que tout est comme on l'attend ! Crer un contrleur ce stade du cours n'est pas vident car vous ne connaissez et ne matrisez pas encore tous les services ncessaires. Seulement, vous avez pu comprendre son rle et voir un exemple concret. Rassurez-vous, dans la partie 4 du tutoriel on apprendra tout le ncessaire pour construire l'intrieur de nos contrleurs. En attendant, rendez-vous au prochain chapitre pour en apprendre plus sur les templates.

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


Pour plus d'informations concernant les contrleurs, n'hsitez pas lire la documentation officielle.

48/201

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World

49/201

Le moteur de template Twig


Les templates vont nous permettre de sparer le code PHP du code HTML/XML/Text/, etc. Intress ? Lisez la suite.

Les templates Twig Intrt


Les templates sont trs intressants. Nous l'avons dj vu, leur objectif est de sparer le code PHP du code HTML. Ainsi, lorsque vous faites du PHP, vous n'avez pas 100 balises HTML qui gnent la lecture de votre code PHP. De mme, lorsque votre designer fait du HTML, il n'a pas subir votre code barbare PHP auquel il ne comprend rien. Seulement, pour faire du HTML de prsentation, on a toujours besoin d'un peu de code dynamique : faire une boucle pour afficher tous les articles d'un blog, crer des conditions pour afficher un menu diffrent pour les utilisateurs authentifis ou non, etc. Pour faciliter ce code dynamique dans les templates, le moteur de template Twig offre son pseudo-langage lui. a n'est pas du PHP, mais c'est plus adapt et voici pourquoi : la syntaxe est plus concise et plus claire. Rappelez-vous, pour afficher une variable, {{ mavar }} suffit, alors qu'en PHP, il faudrait faire <?php echo $mavar; ?> ; il y a quelques fonctionnalits en plus, comme l'hritage de templates (nous le verrons). Cela serait bien entendu possible en PHP, mais il faudrait coder soi-mme le systme et cela ne serait pas aussi esthtique ; il scurise vos variables automatiquement : plus besoin de se soucier de <?php htmlentities() ou <?php addslashes() ou que sais-je encore. Pour ceux qui se posent la question de la rapidit : aucune inquitude ! Oui il faut transformer le langage Twig en PHP avant de l'excuter pour, finalement, afficher notre contenu. Mais Twig ne le fait que la premire fois et met en cache du code PHP simple afin que, ds la 2e excution de votre page, ce soit en fait aussi rapide que du PHP simple.

Des pages Web mais aussi des e-mails et autres


En effet, pourquoi se limiter nos pages HTML ? Les templates peuvent (et doivent) tre utiliss partout. Quand on enverra des e-mails, le contenu de l'e-mail sera plac dans un template. Il existe bien sr un moyen de rcuprer le contenu d'un template sans l'afficher immdiatement. Ainsi, en rcuprant le contenu du template dans une variable quelconque, on pourra le passer la fonction mail de notre choix. Mais il en va de mme pour un flux RSS par exemple ! Si l'on sait afficher une liste des news de notre site en HTML grce au template liste_news.html.twig, alors on saura afficher un fichier RSS en gardant le mme contrleur, mais en utilisant le template liste_news.rss.twig la place.

En pratique
On a dj cr un template, mais un rappel ne fait pas de mal. Depuis le contrleur, voici la syntaxe pour retourner une rponse HTTP toute faite, dont le contenu est celui d'un certain template : Code : PHP <?php // Depuis un contrleur return $this->render('SdzBlogBundle:Blog:index.html.twig', array( 'var1' => $var1, 'var2' => $var2 ));

Et voici comment, au milieu d'un contrleur, rcuprer le contenu d'un template en texte : Code : PHP <?php // Depuis un contrleur $contenu = $this->renderView('SdzBlogBundle:Blog:email.txt.twig', array( 'var1' => $var1, 'var2' => $var2 )); // Puis on envoie l'e-mail, par exemple : mail('moi@siteduzero.com', 'Inscription OK', $contenu);

Et le template SdzBlogBundle:Blog:email.txt.twig contiendrait par exemple : Code : Django {# src/Sdz/BlogBundle/Resources/views/Blog/email.txt.twig #} Bonjour {{ pseudo }}, Toute l'quipe du site se joint moi pour vous souhaiter la bienvenue sur notre site !

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


Revenez nous voir souvent !

50/201

savoir
Premire chose savoir sur Twig : vous pouvez afficher des variables et pouvez excuter des expressions . a n'est pas la mme chose : {{ ... }} affiche quelque chose ; {% ... %} fait quelque chose ; {# ... #} n'affiche rien et ne fait rien : c'est la syntaxe pour les commentaires. L'objectif de la suite de ce chapitre est donc : D'abord, vous donner les outils pour afficher des variables : variables simples, tableaux, objets, appliquer des filtres, etc. ; Ensuite, vous donner les outils pour construire un vrai code dynamique : faire des boucles, des conditions, etc. ; Enfin, vous donner les outils pour organiser vos templates grce l'hritage et l'inclusion de templates. Ainsi vous aurez un template matre qui contiendra votre design (avec les balises <html>, <head>, etc.) et vos autres templates ne contiendront que le contenu de la page (liste des news, etc.).

Afficher des variables Syntaxe de base pour afficher des variables


Afficher une variable se fait avec les doubles accolades {{ ... }} . V oici quelques exemples. Description Exemple Twig Code : Django Afficher une variable Pseudo : {{ pseudo }} Equivalent PHP Code : PHP Pseudo : <?php echo $pseudo; ?>

Code : Django Afficher l'index d'un tableau Identifiant : {{ user['id'] }}

Code : PHP Identifiant : <?php echo $user['id']; ? >

Code : Django Afficher l'attribut d'un objet, dont le getter respecte la convention $objet->getAttribut() Identifiant : {{ user.id }} Ou, quivalent : {{ user->getId() }}

Code : PHP Identifiant : <?php echo $user>getId(); ?>

Code : PHP Code : Django Afficher une variable en lui appliquant un filtre. Ici, upper met tout en majuscules : Pseudo en majuscules : {{ pseudo|upper }} Pseudo en lettre majuscules : <?php echo strtoupper($pseudo); ?>

Afficher une variable en combinant les filtres. striptags supprime les balises HTML. title met la premire lettre de chaque mot en majuscule.

Code : Django Message : {{ news.texte|striptags|title }}

Code : PHP Message : <?php echo ucwords(strip_tags($news>getTexte())); ?>

Code : Django Utiliser un filtre avec des arguments. Attention il faut que date soit un objet de type Datetime ici. Date : {{ date|date('d/m/Y') }}

Code : PHP Date : <?php echo $date>format('d/m/Y'); ? >

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


Code : Django Concatner Identit : {{ nom ~ " " ~ prenom }} Code : PHP Identit : <?php echo $nom.' '.$prenom; ?>

51/201

Prcisions sur la syntaxe {{ objet.attribut }}


Le fonctionnement de la syntaxe {{ objet.attribut }} est un peu plus complexe qu'il n'en a l'air. Il ne fait pas seulement objet->getAttribut. En ralit, voici ce qu'il fait exactement : Il vrifie si "objet" est un tableau, et si "attribut" est un index valide. Si c'est le cas, il affiche " objet['attribut']" ; Sinon, et si "objet" est un objet, il vrifie si " attribut" est un attribut valide (public donc). Si c'est le cas, il affiche " objet>attribut" ; Sinon, et si "objet" est un objet, il vrifie si " attribut()" est une mthode valide (publique donc). Si c'est le cas, il affiche " objet->attribut()" ; Sinon, et si "objet" est un objet, il vrifie si " getAttribut()" est une mthode valide. Si c'est le cas, il affiche " objet>getAttribut()" ; Sinon, et si "objet" est un objet, il vrifie si " isAttribut()" est une mthode valide. Si c'est le cas, il affiche " objet>isAttribut()" ; Sinon, il n'affiche rien et retourne null.

Les filtres utiles


Il y a quelques filtres disponibles nativement avec Twig, en voici quelques uns : Filtre Description Code : Django Upper Met toutes les lettres en majuscules. {{ var|upper }} Exemple Twig

Code : Django Supprime Striptags toutes les balises XML. Format la date selon le format donn un argument. La variable en entre doit tre une instance de Datetime. Insre des variables dans un texte, quivalent printf. Retourne le nom d'lments du tableau, ou le nombre de caractres d'une chane. {{ var|striptags }}

Code : Django {{ date|date('d/m/Y') }} Date d'aujourd'hui : {{ "now"|date('d/m/Y') }}

Date

Code : Django {{ "Il y a %s pommes et %s poires"|format(153, nb_poires) }}

Format

Code : Django Longueur de la variable : {{ texte|length }} Nombre d'lments du tableau : {{ tableau|length }}

Length

La documentation de tous les filtres disponibles est dans la documentation officielle de Twig : http://twig.sensiolabs.org/doc/filters/index.html

Nous pourrons galement crer nos propres filtres ! On le verra plus loin dans ce tutoriel.

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World

52/201

Twig et scurit
Dans tous les exemples prcdents, vos variables ont t protges par Twig ! Twig applique par dfaut un filtre sur toutes les variables que vous affichez, afin de les protger de balises HTML malencontreuses. Ainsi, si le pseudo d'un de vos membres contient un "<" par exemple, lorsque vous faites {{ pseudo }} celui ci est chapp, et le texte affich est en ralit "mon&lt;pseudo" au lieu de "mon<pseudo". Trs pratique ! Et donc savoir : inutile de protger vos variables en amont, Twig s'occupe de tout en fin de chane ! Et dans le cas o vous voulez afficher volontairement une variable qui contient du HTML (javascript, etc.), et que vous ne voulez pas que Twig l'chappe, il vous faut utiliser le filtre raw comme suit : {{ ma_variable_html|raw }}. Avec ce filtre, Twig dsactivera localement la protection HTML, et affichera la variable en brut, quelque soit ce qu'elle contient.

Les variables globales


Symfony2 enregistre quelques variables globales dans Twig pour nous faciliter la vie. V oici la liste des variables globales disponibles dans tous vos templates : Variable {{ app.request }} {{ app.session }} Description Le service Request qu'on a vu au chapitre prcdent sur les contrleurs. Le service Session qu'on a vu galement au chapitre prcdent.

{{ app.environment }} L'environnement courant : "dev", "prod", et ceux que vous avez dfini. {{ app.debug }} {{ app.security }} {{ app.user }} True si le mode debug est activ, False sinon. Le service Security, que nous verrons plus loin dans ce tutoriel. L'utilisateur courant, que nous verrons galement plus loin dans ce tutoriel.

Bien entendu, on peut enregistrer nos propres variables globales, pour qu'elles soient accessibles depuis toutes nos vues, au lieu de les injecter chaque fois. Pour cela, il faut diter le fichier de configuration de l'application comme suit : Code : YAML # app/config/config.yml # ... twig: # ... globals: webmaster: moi-mme

Ainsi, la variable {{ webmaster }} sera injecte dans toutes vos vues, et donc utilisable comme ceci : Code : HTML & Django <footer>Responsable du site : {{ webmaster }}.</footer>

Je profite de cet exemple pour vous faire passer un petit message. Pour ce genre de valeurs paramtrables, la bonne pratique est de les dfinir non pas directement dans le fichier de configuration config.yml, mais dans le fichier des paramtres, savoir parameters.yml. Attention je parle bien de la valeur du paramtre, non de la configuration. V oyez par vous-mmes. Valeur du paramtre : Code : YAML # app/config/parameters.yml parameters: # ... app_webmaster: moi-mme

Configuration (ici, injection dans toutes les vues) qui utilise le paramtre : Code : YAML # app/config/config.yml twig: globals: webmaster: %app_webmaster%

On a ainsi spar la valeur du paramtre, stocke dans un fichier simple ; et l'utilisation de ce paramtre, perdue dans le fichier de configuration.

Structures de contrle et expressions

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World

53/201

Les structures de contrle


Nous avons vu comment afficher quelque chose, maintenant nous allons faire des choses, avec la syntaxe {% ... %}. V oici quelques exemples : Description Code : Django {% if membre.age < 12 %} Il faut avoir 12 ans pour ce film. {% elseif membre.age < 18 %} OK bon film. {% else %} Un peu vieux pour voir ce film non ? {% endif %} Exemple Twig Equivalent PHP Code : PHP <?php if($membre>getAge() < 12) { ?> Il faut avoir 12 ans pour ce film. <?php } elseif($membre>getAge() < 18) { ?> OK bon film. <?php } else { ?> Un peux vieux pour voir ce film non ? <?php } ?>

Condition {% if %}

Code : HTML & Django <ul> {% for membre in liste_membres %} <li>{{ membre.pseudo }}</li> {% else %} <li>Pas d'utilisateur trouv.</li> {% endfor %} </ul> Boucle {% for %} Et pour avoir accs aux cls du tableau : Code : HTML & Django <select> {% for valeur, option in liste_options %} <option value="{{ valeur }}">{{ option }}</option> {% endfor %} </select>

Code : PHP <ul> <?php if(count($liste_membres) > 0) { foreach($liste_membres as $membre) { echo '<li>'.$membre>getPseudo().'</li>'; } } else { ?> <li>Pas d'utilisateur trouv.</li> <?php } ?> </ul>

Avec les cls : Code : PHP <?php foreach($liste_options as $valeur => $option) { // ... }

Code : Django Dfinition {% set %} {% set foo = 'bar' %}

Code : PHP <?php $foo = 'bar'; ?>

Une petite information sur la structure {% for %}, celle-ci dfinit une variable {{ loop }} au sein de la boucle, qui contient les attributs suivants : Variable {{ loop.index }} {{ loop.index0 }} {{ loop.revindex }} Description Le numro de l'itration courante (en commenant par 1). Le numro de l'itration courante (en commenant par 0). Le nombre d'itrations restantes avant la fin de la boucle (en finissant par 1).

{{ loop.revindex0 }} Le nombre d'itrations restantes avant la fin de la boucle (en finissant par 0). {{ loop.first }} {{ loop.last }} {{ loop.length }} True si c'est la premire itration, false sinon. True si c'est la dernire itration, false sinon. Le nombre total d'itration dans la boucle.

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World

54/201

Les tests utiles


Test Exemple Twig Code : Django Defined Pour vrifier si une variable existe. {% if var is defined %} ... {% endif %} <?php if(isset($var)) { } Equivalent PHP Code : PHP

Code : HTML & Django {% for valeur in liste %} <span class="{% if loop.index is even %}pair{% else %} impair{% endif %}"> {{ valeur }} </span> {% endfor %}

Code : PHP <?php $i = 0; foreach($liste as $valeur) { echo '<span class="'; echo $i % 2 ? 'impair' : 'pair'; echo '">'.$valeur.'</span>'; $i++; }

Even / Odd Pour tester si un nombre est pair / impair.

La documentation de tous les tests disponibles est dans la documentation officielle de Twig : http://twig.sensiolabs.org/doc/tests/index.html

Hriter et inclure des templates L'hritage de template


En voici une partie intressante ! Plus que la prcdente en tout cas. Je vous ai fait un teaser plus haut : l'hritage de templates va nous permettre de rsoudre la problmatique : J'ai un seul design et n'ai pas l'envie de le rpter sur chacun de mes templates . C'est un peu comme ce que vous devez faire aujourd'hui avec les <?php include(), mais en mieux !

Le principe
Le principe est simple : vous avez un template pre qui contient le design de votre site ainsi que quelques trous (appels blocks en anglais, que nous nommerons blocs en franais) et des templates fils qui vont remplir ces blocs. Les fils vont donc venir hriter du pre en remplaant certains lments par leur propre contenu. L'avantage est que les templates fils peuvent modifier plusieurs blocs du template pre. Avec la technique des <?php include(), un template inclus ne pourra pas modifier le template pre dans un autre endroit que l o il est inclus ! Les blocs classiques sont le centre de la page et le titre. Mais en fait, c'est vous de les dfinir ; vous en ajouterez donc autant que vous voudrez.

La pratique
V oici quoi peut ressembler un template pre (appel plus communment layout ). Mettons-le dans src/Sdz/BlogBundle/Resources/views/layout.html.twig : Code : HTML & Django {# src/Sdz/BlogBundle/Resources/views/layout.html.twig #} <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>{% block title %}SdzBlog{% endblock %}</title> </head> <body> {% block body %} {% endblock %} </body> </html>

V oici un de nos templates fils. Mettons-le dans src/Sdz/BlogBundle/Resources/views/Blog/index.html.twig : Code : HTML & Django

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


{# src/Sdz/BlogBundle/Resources/views/Blog/index.html.twig #} {% extends "SdzBlogBundle::layout.html.twig" %} {% block title %}{{ parent() }} - Index{% endblock %} {% block body %} OK, mme s'il est pour l'instant un peu vide, mon blog sera trop bien ! {% endblock %}

55/201

Qu'est-ce que l'on vient de faire ?


Pour bien comprendre tous les concepts utiliss dans cet exemple trs simple, dtaillons un peu.

Le nom du template pre


On a plac ce template dans views/layout.html.twig et non dans views/qqch/layout.html.twig. C'est tout fait possible ! En fait, il est inutile de mettre les templates qui ne concernent pas un contrleur particulier et qui peuvent tre rutiliss par plusieurs contrleurs dans un sous-rpertoire. Attention la notation pour accder ce template : du coup, a n'est plus SdzBlogBundle:MonController:layout.html.twig, mais SdzBlogBundle::layout.html.twig. C'est assez intuitif, en fait : on enlve juste la partie qui correspond au rpertoire MonController. C'est ce que l'on a fait la premire ligne du template fils.

La balise {% block %} ct pre


Pour dfinir un trou (dit bloc) dans le template pre, nous avons utilis la balise {% block %}. Un bloc doit avoir un nom afin que le template fils puisse modifier tel ou tel bloc de faon nominative. La base, c'est juste de faire {% block nom_du_block %}{% endblock %} et c'est ce que nous avons fait pour le body . Mais vous pouvez insrer un texte par dfaut dans les blocs, comme on l'a fait pour le titre. C'est utile pour deux cas de figure : lorsque le template fils ne redfinit pas ce bloc. Plutt que de n'avoir rien d'crit, vous aurez cette valeur par dfaut ; lorsque les templates fils veulent rutiliser une valeur commune. Par exemple, si vous souhaitez que le titre de toutes les pages de votre site commence par SdzBlog , alors depuis les templates fils, vous pouvez utiliser {{ parent() }} qui permet d'utiliser le contenu par dfaut du bloc ct pre. Regardez, nous l'avons fait pour le titre dans le template fils.

La balise {% block %} ct fils


Elle se dfinit exactement comme dans le template pre sauf que cette fois-ci, on y met notre contenu. Mais tant donn que les blocs se dfinissent et se remplissent de la mme faon, vous avez pu deviner qu'on peut hriter en cascade ! En effet, si l'on cre un troisime template petit-fils qui hrite de fils, on pourra faire beaucoup de choses.

Le modle triple hritage


Pour bien organiser ses templates, une bonne pratique est sortie du lot. Il s'agit de faire de l'hritage de template sur trois niveaux, chacun des niveaux remplissant un rle particulier. Les trois templates sont les suivants : layout gnral : c'est le design de votre site, indpendamment de vos bundles. Il contient le header, le footer, etc. La structure de votre site donc (c'est notre template pre) ; layout du bundle : il hrite du layout gnral et contient les parties communes toutes les pages d'un mme bundle. Par exemple, pour notre blog, on pourrait afficher un menu particulier, rajouter Blog dans le titre, etc. ; template de page : il hrite du layout du bundle et contient le contenu central de votre page. Nous verrons un exemple de ce triple hritage juste aprs dans l'exemple du blog. Question : puisque le layout gnral ne dpend pas d'un bundle en particulier, o le mettre ?

Rponse : dans votre rpertoire app/ ! En effet, dans ce rpertoire, vous pouvez toujours avoir des fichiers qui crasent ceux des bundles ou bien des fichiers communs aux bundles. Le layout gnral de votre site fait partie de ces ressources communes. Son rpertoire exact doit tre app/Resources/views/layout.html.twig. Et pour l'appeler depuis vos templates, la syntaxe est la suivante : " ::layout.html.twig". Encore une fois, c'est trs intuitif : aprs avoir enlev le nom du contrleur tout l'heure, on enlve juste cette fois-ci le nom du bundle. Afin de bien vous reprsenter l'architecture adopte, je vous propose un petit schma. Il vaut ce qu'il vaut, mais vous permet de bien comprendre ce qu'on fait :

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World

56/201

Je vous parle du bloc rouge un peu aprs, c'est une inclusion non pas de template, mais d'action de contrleur ! Il ne fait pas parti du modle triple hritage proprement parler.

L'inclusion de templates
La thorie : quand faire de l'inclusion ?
Hriter, c'est bien, mais inclure, cela n'est pas mal non plus. Prenons un exemple pour bien faire la diffrence. Le formulaire pour ajouter un article est le mme que celui pour modifier un article. On ne va pas faire du copier-coller de code, cela serait assez moche, et puis nous sommes fainants. C'est ici que l'inclusion de templates intervient. On a nos deux templates SdzBlogBundle:Blog:ajouter.html.twig et SdzBlogBundle:Blog:modifier.html.twig qui hritent chacun de SdzBlogBundle::layout.html.twig. L'affichage exact de ces deux templates diffre un peu, mais chacun d'eux inclut SdzBlogBundle:Blog:formulaire.html.twig l'endroit exact pour afficher le formulaire. On voit bien qu'on ne peut pas faire d'hritage sur le template formulaire.html.twig car il faudrait le faire hriter une fois de ajouter.html.twig, une fois de modifier.html.twig, etc. Comment savoir ? Et si un jour nous souhaitons ne le faire hriter de rien du tout pour afficher le formulaire tout seul dans une popup par exemple ? Bref, c'est bien une inclusion qu'il nous faut ici.

La pratique : comment le faire ?


Comme toujours avec Twig, cela se fait trs facilement. Il faut utiliser la balise {% include %}, comme ceci : Code : HTML & Django {% include "SdzBlogBundle:Blog:formulaire.html.twig" %}

Ce code inclura le contenu du template l'endroit de la balise. Une sorte de copier-coller automatique, en fait ! V oici un exemple avec la vue ajouter.html.twig : Code : HTML & Django {# src/Sdz/BlogBundle/Resources/views/Blog/ajouter.html.twig #} {% extends "SdzBlogBundle::layout.html.twig" %} {% block body %} <h2>Ajouter un article</h2> {% include "SdzBlogBundle:Blog:formulaire.html.twig" %} <p> Attention : cet article sera ajout directement sur la page d'accueil aprs validation du formulaire.

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


</p> {% endblock %}

57/201

Et voici le code du template inclu : Code : HTML & Django {# src/Sdz/BlogBundle/Resources/views/Blog/formulaire.html.twig #} {# Cette vue n'hrite de personne, elle sera inclue par d'autres vues qui, elles, hriteront srement du layout. #} {# Je dis "srement" car, ici pour cette vue, on n'en sait rien et c'est une info qui ne nous concerne pas. #} <h3>Formulaire d'article</h3> {# Ici on laisse vide la vue pour l'instant, on la comblera plus tard lorsque saura afficher un formulaire. #} <div class="well"> Ici se trouvera le formulaire. </div>

A l'intrieur du template inclus, vous retrouvez toutes les variables qui sont disponibles dans le templates qui fait l'inclusion : exactement comme si vous copiez-collez le contenu.

L'inclusion de contrleurs
La thorie : quand inclure des contrleurs ?
V oici un dernier point savoir absolument avec Twig, un des points les plus puissants dans son utilisation avec Symfony2. On vient de voir comment inclure des templates : ceux-ci profitent des variables du template qui fait l'inclusion, trs bien. Seulement dans bien des cas, depuis le template qui fait l'inclusion, vous voudrez inclure un autre template mais vous n'avez pas les variables ncessaires pour lui. Restons sur l'exemple de notre blog, dans le schma un peu plus haut je vous ai mis un bloc rouge : considrons que dans cette partie du menu, accessible sur toutes les pages mme hors du blog, on veut afficher les 3 derniers articles du blog. C'est donc depuis le layout gnral qu'on va inclure non pas un template du bundle Blog, nous n'aurions pas les variables lui donner, mais un contrleur du bundle Blog. Le contrleur va crer les variables dont il a besoin, et les donner son template, pour ensuite tre inclus l o on le veut !

La pratique : comment le faire ?


Au risque de me rpter : cela se fait trs simplement ! Du ct du template qui fait l'inclusion, la place de la balise {% include %}, il faut utiliser le code suivant : Code : Autre {% render "SdzBlogBundle:Blog:menu" %}

Ici, "SdzBlogBundle:Blog:menu" n'est pas un template mais une action de contrleur, c'est la syntaxe qu'on utilise dans les routes vous l'aurez reconnu. V oici par exemple ce qu'on mettrait dans le layout : Code : HTML & Django {# app/Resources/views/layout.html.twig #} <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>{% block title %}SdzBlog{% endblock %}</title> </head> <body> <div id="menu"> {% render "SdzBlogBundle:Blog:menu" %} </div> {% block body %} {% endblock %}

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


</body> </html>

58/201

Et du ct du contrleur, c'est une mthode trs classique : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php // ... public function menuAction() { // On fixe en dur une liste ici, bien entendu par la suite on les rcuprera depuis la BDD ! $liste = array( 2 => 'Mon dernier weekend !', 5 => 'Sortie de Symfony2.1', 9 => 'Petit test' ); return $this->render('SdzBlogBundle:Blog:menu.html.twig', array( 'liste_articles' => $liste // C'est ici tout l'intrt : le contrleur passe les variables ncessaires au template ! )); }

Et enfin, un exemple de ce que pourrait tre le template menu.html.twig : Code : HTML & Django {# src/Sdz/BlogBundle/Resources/views/Blog/menu.html.twig #} {# Ce template n'hrite de personne, tout comme le template inclus avec {% include %} #} <ul> {% for id, titre in liste_articles %} <li><a href="{{ path('sdzblog_voir', {'id': id}) }}">{{ titre }}</a></li> {% endfor %} </ul>

Application : les templates de notre blog


Revenons notre blog. Faites en sorte d'avoir sous la main le contrleur que l'on a ralis au chapitre prcdent. Le but ici est de crer tous les templates que l'on a utiliss depuis le contrleur, ou du moins leur squelette. tant donn que l'on n'a pas encore la vraie liste des articles, on va faire avec des variables vides : a va se remplir par la suite, mais le fait d'employer des variables vides va nous permettre ds maintenant de construire le template. Pour encadrer tout a, nous allons utiliser le modle d'hritage sur trois niveaux : layout gnral, layout du bundle et template.

Layout gnral
La thorie
Comme voqu plus tt, le layout est la structure HTML de notre site avec des blocs aux endroits stratgiques pour permettre aux templates qui hriteront de ce dernier de personnaliser la page. On va ici crer une structure simple ; je vous laisse la personnaliser si besoin est. Pour les blocs, pareil pour l'instant, on fait simple : un bloc pour le body et un bloc pour le titre. Encore une fois, vous devez personnaliser tout a ! Je ne suis pas l pour faire le blog votre place, juste pour vous guider. Si vous ne pratiquez pas de votre ct en ajoutant, supprimant et amliorant tout ce que l'on voit ici, vous serez vite perdu ! Je vous fais confiance, ne faites pas que lire, codez.

Je vais galement en profiter pour introduire l'utilisation de ressources CSS/JS/etc dans Symfony2. Cela se fait trs bien avec la fonction {{ asset() }} de Twig, qui va chercher vos ressources dans le rpertoire /web. Regardez simplement comment elle est utilise dans l'exemple et vous saurez l'utiliser de faon basique. Pour le design du blog que l'on va construire, je vais utiliser le bootstrap de Twitter. C'est un framework CSS, l'quivalent pour le CSS de ce que Symfony2 est pour le PHP. Cela permet de faire des beaux designs, boutons ou liens trs rapidement. V ous pourrez le voir dans les vues que je fais par la suite. Mais tout d'abord vous devez le tlcharger ici : http://twitter.github.com/bootstrap/assets/bootstrap.zip et l'extraire dans le rpertoire /web. V ous devez avoir le fichier bootstrap.css disponible dans le rpertoire /web/css/bootstrap.css par exemple.

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


Pour votre information, il existe un tutoriel sur ce framework Bootstrap sur le siteduzero : Bootstrap : un kit CSS et plus. Je vous invite y jeter un oeil si ce framework vous intresse, ou juste par curiosit.

59/201

La pratique
Commenons par faire le layout gnral de l'application, la vue situe dans le rpertoire /app. V oici le code exemple que je vous propose : Code : HTML & Django {# app/Resources/views/layout.html.twig #} <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>{% block title %}Sdz{% endblock %}</title> {% block stylesheets %} <link rel="stylesheet" href="{{ asset('css/bootstrap.css') }}" type="text/css" /> {% endblock %} </head> <body> <div class="container"> <div id="header" class="hero-unit"> <h1>Mon Projet Symfony2</h1> <p>Ce projet est propuls par Symfony2, et construit grce au tutoriel du siteduzero.</p> <p><a class="btn btn-primary btn-large" href="http://www.siteduzero.com/tutoriel-3-517569-symfony2.html"> Lire le tutoriel &raquo; </a></p> </div> <div class="row"> <div id="menu" class="span3"> <h3>Le blog</h3> <ul class="nav nav-pills nav-stacked"> <li><a href="{{ path('sdzblog_accueil') }}">Accueil du blog</a></li> <li><a href="{{ path('sdzblog_ajouter') }}">Ajouter un article</a></li> </ul> {% render "SdzBlogBundle:Blog:menu" with {'nombre': 3} %} </div> <div id="content" class="span9"> {% block body %} {% endblock %} </div> </div> <hr> <footer> <p>The sky's the limit &copy; 2012 and beyond.</p> </footer> </div> {% block javascripts %} {# Ajoutez ces javascripts si vous comptez vous servir des fonctionnalits du bootstrap Twitter #} <script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script type="text/javascript" src="{{ asset('js/bootstrap.js') }}"></script> {% endblock %} </body> </html>

J'ai surlign les parties qui contiennent un peu de Twig : Ligne 8 : cration du bloc title avec Sdz comme contenu par dfaut ; Lignes 11 : appel du CSS situ dans /web/css/bootstrap.css ; Lignes 29 et 30 : utilisation de la fonction {{ path }} pour faire des liens vers d'autres routes ; Ligne 33 : inclusion de la mthode "menu" du contrleur "Blog" du bundle "SdzBlogBundle", avec l'argument "nombre" dfini 3 ; Lignes 36 et 37 : cration du bloc body sans contenu par dfaut.

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


Et voil, nous avons notre layout gnral ! Pour pouvoir tester nos pages, il faut maintenant s'attaquer au layout du bundle.

60/201

Layout du bundle
La thorie
Comme on l'a dit, ce template va hriter du layout gnral, rajouter la petite touche perso au bundle Blog , puis se faire hriter son tour par les templates finaux. En fait, il ne contient pas grand-chose. Laissez courir votre imagination, mais moi, je ne vais rajouter qu'une balise <h1>, vous voyez ainsi le mcanisme et pouvez personnaliser votre sauce. La seule chose laquelle il faut faire attention, c'est au niveau du nom des blocs que ce template cre pour ceux qui vont l'hriter. Une bonne pratique consiste prfixer le nom des blocs par le nom du bundle courant. Regardez le code et vous comprendrez.

La pratique
V oici ce que j'ai mis pour le layout du bundle : Code : HTML & Django {# src/Sdz/BlogBundle/Resources/views/layout.html.twig #} {% extends "::layout.html.twig" %} {% block title %} Blog - {{ parent() }} {% endblock %} {% block body %} {# On dfinit un sous-titre commun toutes les pages du bundle, par exemple #} <h1>Blog</h1> <hr> {# On gre les messages flash de faon globale pour tout le bundle On peut catgoriser les messages dans diffrents sacs (bag), ici on les a mis dans "info", "success" et "error", qui correspondent aux classes CSS du Bootstrap #} {% for type in ['info', 'success', 'error'] %} {% for message in app.session.flashbag.get(type) %} <div class="alert alert-{{ type }}"> {{ message }} </div> {% endfor %} {% endfor %} {# On dfinit un nouveau block, que les vues du bundle pourront remplir #} {% block sdzblog_body %} {% endblock %} {% endblock %}

On a ajout un <h1> dans le bloc body puis cr un nouveau bloc qui sera personnalis par les templates finaux. On a prfix le nom du nouveau block pour le body afin d'avoir un nom unique pour notre bundle.

Les templates finaux


La thorie
Pas trop de thorie ici, il ne reste plus qu' hriter et personnaliser les templates.

Blog/index.html.twig
C'est le template de la page d'accueil. On va faire notre premire boucle sur la variable {{ articles }}. Cette variable n'existe pas encore, on va modifier le contrleur juste aprs. Code : HTML & Django {# src/Sdz/BlogBundle/Resources/views/Blog/index.html.twig #} {% extends "SdzBlogBundle::layout.html.twig" %} {% block title %} Accueil - {{ parent() }} {% endblock %} {% block sdzblog_body %} <h2>Liste des articles</h2>

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


<h2>Liste des articles</h2> <ul> {% for article in articles %} <li> <a href="{{ path('sdzblog_voir', {'id': article.id}) }}">{{ article.titre }}</a> par {{ article.auteur }}, le {{ article.date|date('d/m/Y') }} </li> {% else %} <li>Pas (encore !) d'articles</li> {% endfor %} </ul> {% endblock %}

61/201

Pas grand-chose dire, on a juste utilis les variables et expressions expliques plus haut dans ce chapitre. Afin que cette page fonctionne, il nous faut modifier l'action indexAction() du contrleur pour passer une variable {{ articles }} cette vue. Pour l'instant, voici juste de quoi se dbarrasser de l'erreur : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php // Dans l'action indexAction() : return $this->render('SdzBlogBundle:Blog:index.html.twig', array( 'articles' => array() ));

V ous pouvez ds maintenant voir votre nouvelle peau : http://localhost/Symfony/web/app_dev.php/blog !

Si vous n'aviez pas rajout l'action "menu" du contrleur tout l'heure, voici comment la faire, et aussi comment l'adapter l'argument qu'on lui a pass cette fois-ci : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php public function menuAction($nombre) // Ici, nouvel argument $nombre, on a transmis via le "with" depuis la vue { // On fixe en dur une liste ici, bien entendu par la suite on les rcuprera depuis la BDD ! // On pourra rcuprer $nombre articles depuis la BDD, avec $nombre un paramtre qu'on peut changer lorsqu'on appelle cette action. $liste = array( 2 => 'Mon dernier weekend !', 5 => 'Sortie de Symfony2.1', 9 => 'Petit test' ); array( return $this->render('SdzBlogBundle:Blog:menu.html.twig',

'liste_articles' => $liste // C'est ici tout l'intrt : le contrleur passe les variables ncessaires au template ! )); }

Avec sa vue associe : Code : HTML & Django {# src/Sdz/BlogBundle/Resources/views/Blog/menu.html.twig #} <h3>Les derniers articles</h3> <ul class="nav nav-pills nav-stacked"> {% for id, titre in liste_articles %} <li><a href="{{ path('sdzblog_voir', {'id': id}) }}">{{ titre }}</a></li> {% endfor %} </ul>

Si vous avez bien ajout le CSS de Twiter, voici ce quoi cela devrait ressembler :

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World

62/201

V ous voulez voir des articles au lieu du message pas trs drle ? Je suis trop bon, voici un tableau d'articles ajouter temporairement dans la mthode indexAction(), que vous pouvez passer en paramtre la mthode render(). C'est un tableau pour l'exemple, par la suite il faudra bien sr rcuprer les articles depuis la base de donnes : Secret (cliquez pour afficher) Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php // ... public function indexAction() { // ... // Les articles : $articles = array( array('titre' => 'Mon weekend a Phi Phi Island !', 'id' => 1, 'auteur' => 'winzou', 'contenu' => 'Ce weekend tait trop bien. Blabla...', 'date' => new \Datetime()), array('titre' => 'Repetition du National Day de Singapour', 'id' => 2, 'auteur' => 'winzou', 'contenu' => 'Bientt prt pour le jour J. Blabla...', 'date' => new \Datetime()), array('titre' => 'Chiffre d\'affaire en hausse', 'id' => 3, 'auteur' => 'M@teo21', 'contenu' => '+500% sur 1 an, fabuleux. Blabla...', 'date' => new \Datetime()) ); // Puis modifiez la ligne du render comme ceci, pour prendre en compte nos articles : return $this->render('SdzBlogBundle:Blog:index.html.twig', array( 'articles' => $articles )); }

Rechargez la page, et profitez du rsultat. Attention, on vient de dfinir des articles en brut dans le contrleur, mais c'est uniquement pour l'exemple d'utilisation de Twig ! Ce n'est bien sr pas du tout une faon correcte de le faire, par la suite nous les rcuprerons depuis la base de donnes.

Blog/voir.html.twig
Il ressemble beaucoup index.html.twig sauf qu'on passe la vue une variable {{ article }} contenant un seul article, et non plus une liste d'articles. V oici un code par exemple :

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


Code : HTML & Django {# src/Sdz/BlogBundle/Resources/views/Blog/voir.html.twig #} {% extends "SdzBlogBundle::layout.html.twig" %} {% block title %} Lecture d'un article - {{ parent() }} {% endblock %} {% block sdzblog_body %} <h2>{{ article.titre }}</h2> <i>Par {{ article.auteur }}, le {{ article.date|date('d/m/Y') }}</i> <div class="well"> {{ article.contenu }} </div> <p> <a href="{{ path('sdzblog_accueil') }}" class="btn"> <i class="icon-chevron-left"></i> Retour la liste </a> <a href="{{ path('sdzblog_modifier', {'id': article.id}) }}" class="btn"> <i class="icon-edit"></i> Modifier l'article </a> <a href="{{ path('sdzblog_supprimer', {'id': article.id}) }}" class="btn"> <i class="icon-trash"></i> Supprimer l'article </a> </p> {% endblock %}

63/201

Et l'adaptation du contrleur bien videmment : Secret (cliquez pour afficher) Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php // ... public function voirAction($id) { // ... $article = array( 'id' => 1, 'titre' => 'Mon weekend a Phi Phi Island !', 'auteur' => 'winzou', 'contenu' => 'Ce weekend tait trop bien. Blabla...', 'date' => new \Datetime() ); // Puis modifiez la ligne du render comme ceci, pour prendre en compte l'article : return $this->render('SdzBlogBundle:Blog:voir.html.twig', array( 'article' => $article )); }

Ainsi que le rsultat visuel /blog/article/1 :

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World

64/201

Blog/modifier.html.twig et ajouter.html.twig
Ceux-ci contiennent une inclusion de template. En effet, rappelez-vous, j'avais pris l'exemple d'un formulaire utilis pour l'ajout mais galement la modification. C'est notre cas ici, justement. V oici donc le fichier modifier.html.twig : Code : HTML & Django {# src/Sdz/BlogBundle/Resources/views/Blog/modifier.html.twig #} {% extends "SdzBlogBundle::layout.html.twig" %} {% block title %} Modifier un article - {{ parent() }} {% endblock %} {% block sdzblog_body %} <h2>Modifier un article</h2> {% include "SdzBlogBundle:Blog:formulaire.html.twig" %} <p> Vous ditez un article dj existant, ne le changez pas trop pour viter aux membres de ne pas comprendre ce qu'il se passe. </p> <p> <a href="{{ path('sdzblog_voir', {'id': article.id}) }}" class="btn"> <i class="icon-chevron-left"></i> Retour l'article </a> </p> {% endblock %}

Le template ajouter.html.twig lui ressemble normment, je vous laisse donc le faire. Quant formulaire.html.twig, on ne sait pas le faire encore car il demande des notions de formulaire, mais faisons dj sa structure pour le moment : Code : HTML & Django {# src/Sdz/BlogBundle/Resources/views/Blog/formulaire.html.twig #} {# Cette vue n'hrite de personne, elle sera inclue par d'autres vues qui, elles, hriteront srement du layout. #} {# Je dis "srement" car, ici pour cette vue, on n'en sait rien et c'est une info qui ne nous concerne pas. #}

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


<h3>Formulaire d'article</h3> {# Ici on laisse vide la vue pour l'instant, on la comblera plus tard lorsque saura afficher un formulaire. #} <div class="well"> Ici se trouvera le formulaire. </div>

65/201

Une chose importantes ici : dans ce template, il n'y a aucune notion de bloc, d'hritage, etc. Ce template est un lectron libre : vous pouvez l'inclure depuis n'importe quel autre template. Et bien sr il faut adapter le contrleur pour passer la variable article : Secret (cliquez pour afficher) Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php public function modifierAction($id) { // Ici, on rcuprera l'article correspondant l'$id. // Ici, on s'occupera de la cration et de la gestion du formulaire. $article = array( 'id' => 1, 'titre' => 'Mon weekend a Phi Phi Island !', 'auteur' => 'winzou', 'contenu' => 'Ce weekend tait trop bien. Blabla...', 'date' => new \Datetime() ); // Puis modifiez la ligne du render comme ceci, pour prendre en compte l'article : return $this>render('SdzBlogBundle:Blog:modifier.html.twig', array( 'article' => $article )); }

Et le rsultat visuel /blog/modifier/1 :

Conclusion
Et voil, nous avons gnr presque tous nos templates. Bien sr, ils sont encore un peu vides car on ne sait pas utiliser les formulaires ni rcuprer les articles depuis la base de donnes. Mais vous savez maintenant les raliser et c'tait une tape

www.siteduzero.com

Partie 2 : Les bases de Symfony2 : Hello World


importante ! Je vais vous laisser crer les templates manquants ou d'autres afin de vous faire la main. Bon code ! V oil, c'est termin pour ce chapitre ; vous savez afficher avec mise en forme le contenu de votre site. Ce chapitre clt la premire partie du cours. En effet, vous savez presque tout faire maintenant ! Bon OK, c'est vrai, il vous manque encore des concepts cls. Mais vous matrisez pleinement la base et rajouter d'autres concepts par-dessus sera bien plus facile, heureusement. La deuxime partie du cours va nous permettre de dcouvrir la gestion de la base de donnes avec Doctrine2, l'outil livr par dfaut avec Symfony2. Pour plus d'informations concernant Twig et ses possibilits, n'hsitez pas lire la documentation officielle.

66/201

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2

67/201

Partie 3 : Grer la base de donnes avec Doctrine2


Symfony2 est livr par dfaut avec l'ORM Doctrine2. Qu'est-ce qu'un ORM ? Qu'est-ce que Doctrine2 ? Ce tutoriel pour dbutants est fait pour vous, car c'est ce que nous allons apprendre dans cette partie ! Lisez bien l'ensemble des chapitres de cette partie : ils forment un tout, et toutes vos questions seront rsolues la fin de la partie !

La couche mtier : les entits


Dans ce chapitre, on va explorer la couche Modle sous Symfony2. Ce modle sera implment en utilisant lORM (pour Object Relation Mapper, soit en franais Lien Objet-Relation) Doctrine2 .

Notions d'ORM : fini les requtes, utilisons des objets Dfinition d'ORM : Object-Relational Mapper
L'objectif d'un ORM est simple : se charger de l'enregistrement de vos donnes en vous faisant oublier que vous avez une base de donnes. Comment ? En s'occupant de tout ! Nous n'allons plus crire de requtes, ni crer de tables via phpMyAdmin. Dans notre code PHP, nous allons faire appel Doctrine2, l'ORM par dfaut de Symfony2, pour faire tout cela. Un rapide exemple pour bien comprendre ? Supposons que vous disposez d'une variable <?php $utilisateur, un objet User qui reprsente l'un de vos utilisateurs qui vient de s'inscrire sur votre site. Pour sauvegarder cet objet, vous tes habitu crer votre propre fonction qui effectue une requte SQL du type INSERT INTO dans la bonne table, etc. Bref, vous devez grer tout ce qui touche l'enregistrement en base de donnes. En utilisant un ORM, vous n'aurez plus qu' utiliser quelques fonctions de cet ORM, par exemple : <?php $orm->save($utilisateur). Et ce dernier s'occupera de tout ! V ous avez enregistr votre utilisateur en une seule ligne . Bien sr, a n'est qu'un exemple, nous verrons les dtails pratiques dans la suite de ce chapitre, mais retenez bien l'ide. Mais l'effort que vous devrez faire pour bien utiliser un ORM, c'est d'oublier votre ct administrateur de base de donnes . Oubliez les requtes SQL, pensez objet !

Vos donnes sont des objets


Dans ORM, il y a la lettre O comme Objet. En effet, pour que tout le monde se comprenne, toutes vos donnes doivent tre sous forme d'objets. Concrtement, qu'est-ce que cela implique dans notre code ? Pour reprendre l'exemple de notre utilisateur, quand vous tiez petit, vous utilisiez srement un tableau puis vous accdiez vos attributs via <?php $utilisateur['pseudo'] ou <?php $utilisateur['email'] par exemple. Soit, c'tait trs courageux de votre part. Mais nous allons aller plus loin, maintenant. Utiliser des objets n'est pas une grande rvolution en soi. Faire <?php $utilisateur->getPseudo() au lieu de <?php $utilisateur['pseudo'], c'est joli, mais limit. Ce qui est une rvolution, c'est de coupler cette reprsentation objet avec l'ORM. Qu'est-ce que vous pensez d'un <?php $utilisateur->getCommentaires() ? Ahah ! V ous ne pouviez pas faire cela avec votre tableau ! Ici, la mthode <?php $utilisateur->getCommentaires() dclencherait la bonne requte, rcuprerait tous les commentaires posts par votre utilisateur, et vous retournerait une sorte de tableau d'objets de type Commentaire que vous pourriez afficher sur la page de profil de votre utilisateur, par exemple. a commence devenir intressant, n'est-ce pas ? Au niveau du vocabulaire, un objet dont vous confiez l'enregistrement l'ORM s'appelle une entit (entity en anglais). On dit galement persister une entit, plutt qu'enregistrer une entit. V ous savez, l'informatique et le jargon...

Crer une premire entit avec Doctrine2 Une entit, c'est juste un objet
Derrire ce titre se cache la vrit. Une entit, ce que l'ORM va manipuler et enregistrer dans la base de donnes, ce n'est vraiment rien d'autre qu'un simple objet. V oici ce quoi pourrait ressembler l'objet Article de notre blog : Code : PHP <?php // src/Sdz/BlogBundle/Entity/Article.php namespace Sdz\BlogBundle\Entity; class Article { protected $id; protected $date; protected $titre; protected $auteur;

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


protected $contenu; // Et bien sr les getter/setter : public function setId($id) { $this->id = $id; } public function getId() { return $this->id; } public function setDate($date) { $this->date = $date; } public function getDate() { return $this->date; } public function setTitre($titre) { $this->titre = $titre; } public function getTitre() { return $this->titre; } public function setAuteur($auteur) { $this->auteur = $auteur; } public function getAuteur() { return $this->auteur; } public function setContenu($contenu) { $this->contenu = $contenu; } public function getContenu() { return $this->contenu; }

68/201

Inutile de crer ce fichier pour l'instant, nous allons le gnrer plus bas, patience.

Comme vous pouvez le voir, c'est trs simple. Un objet, des proprits, et bien sr, les getters/setters correspondants. On pourrait en ralit utiliser notre objet ds maintenant ! Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller; use Sdz\BlogBundle\Entity\Article; // ... public function testAction() { $article = new Article; $article->setDate(new \Datetime()); // date d'aujourd'hui $article->setTitre('Mon dernier weekend'); $article->setAuteur('Bibi'); $article->setContenu("C'tait vraiment super et on s'est bien amus."); return $this->render('SdzBlogBundle:Article:test.html.twig', array('article' => $article)); }

Rajoutez cela la vue correspondante qui afficherait l'article pass en argument avec un joli code HTML, et vous avez un code oprationnel. Bien sr il est un peu limit car statique, mais l'ide est l et vous voyez comment l'on peut se servir d'une entit. Retenez donc : une entit n'est rien d'autre qu'un objet. Normalement, vous devez vous poser une question : comment l'ORM va-t-il faire pour enregistrer cet objet dans la base de

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


donnes s'il ne connat rien de nos proprits date , titre et contenu ? Comment peut-il deviner que notre proprit date doit tre stocke avec un champ de type DATE dans la table ? La rponse est aussi simple que logique : il ne devine rien, on va le lui dire !

69/201

Une entit, c'est juste un objet mais avec des commentaires !


Quoi ? Des commentaires ?

O.K., je dois avouer que a n'est pas intuitif si vous ne vous en tes jamais servi, mais... oui, on va rajouter des commentaires dans notre code et Symfony2 va se servir directement de ces commentaires pour ajouter des fonctionnalits notre application. Ce type de commentaire se nomme l'annotation. Les annotations doivent respecter une syntaxe particulire, regardez par vousmme : Code : PHP <?php // src/Sdz/BlogBundle/Entity/Article.php namespace Sdz\BlogBundle\Entity; // On dfinit le namespace des annotations utilises par Doctrine2 // En effet il existe d'autres annotations, on le verra par la suite, qui utiliseront un autre namespace use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Entity(repositoryClass="Sdz\BlogBundle\Entity\ArticleRepository") */ class Article { /** * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @ORM\Column(name="date", type="date") */ private $date; /** * @ORM\Column(name="titre", type="string", length=255) */ private $titre; /** * @ORM\Column(name="auteur", type="string", length=255) */ private $auteur; /** * @ORM\Column(name="contenu", type="text") */ private $contenu; // les getters // les setters

Ne recopiez toujours pas toutes ces annotations la main, on utilise le gnrateur en console au paragraphe juste en dessous . Attention par contre pour les prochaines annotations que vous serez amener crire la main : elles doivent tre dans des commentaires de type " /**", avec prcisment deux toiles. Si vous essayer de les mettre dans un commentaire de type " /*" ou encore " //", elles seront simplement ignores.

Grce ces annotations, Doctrine2 dispose de toutes les informations ncessaires pour utiliser notre objet, crer la table correspondante, l'enregistrer, dfinir un identifiant (id) en auto-incrment, nommer les colonnes, etc. Ces informations se nomment les metadatas de notre entit. Je ne vais pas piloguer sur les annotations, elles sont suffisamment claires pour tre comprises par tous. Ce qu'on vient de faire, savoir rajouter les metadatas notre objet Article s'appelle mapper l'objet Article. C'est--dire faire le lien entre notre objet de base et la reprsentation physique qu'utilise Doctrine2. Sachez quand mme que, bien que l'on utilisera les annotations tout au long de ce tutoriel, il existe d'autres moyens de dfinir les metadatas d'une entit : en YAML, en XML et en PHP. Si cela vous intresse, vous trouverez plus d'informations sur la dfinition des metadatas via les autres moyens dans le chapitre Doctrine2 de la documentation de Symfony2.

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2

70/201

Gnrer une entit : le gnrateur la rescousse !


En tant que bon dveloppeur, on est fainant souhait, et a, Symfony2 l'a bien compris ! On va donc se refaire une petite session en console afin de gnrer notre premire entit. Entrez la commande suivante et suivez le guide : Code : Console C:\wamp\www\Symfony\php app/console generate:doctrine:entity

1.

Code : Console D:\backup\www\Symfony>php app/console generate:doctrine:entity Welcome to the Doctrine2 entity generator

This command helps you generate Doctrine2 entities. First, you need to give the entity name you want to generate. You must use the shortcut notation like AcmeBlogBundle:Post. The Entity shortcut name:_

Grce ce que le gnrateur vous dit, vous l'avez compris, il faut rentrer le nom de l'entit sous le format NomBundle:NomEntit. Dans notre cas, on entre donc SdzBlogBundle:Article ; 2. Code : Console The Entity shortcut name: SdzBlogBundle:Article Determine the format to use for the mapping information. Configuration format (yml, xml, php, or annotation) [annotation]:_

Comme je vous l'ai dit plus haut, on va utiliser les annotations qui sont d'ailleurs le format par dfaut. Appuyez juste sur la touche Entre ; 3. Code : Console Configuration format (yml, xml, php, or annotation) [annotation]: Instead of starting with a blank entity, you can add some fields now. Note that the primary key will be added automatically (named id). Available types: array, object, boolean, integer, smallint, bigint, string, text, datetime, datetimetz, date, time, decimal, float, blob. New field name (press <return> to stop adding fields):_

On commence saisir le nom de nos champs. Lisez bien ce qui est inscrit avant : Doctrine2 va ajouter automatiquement l'id, de ce fait, pas besoin de le redfinir ici. On entre donc notre date : date ; 4. Code : Console New field name (press <return> to stop adding fields): date Field type [string]:_

C'est maintenant que l'on va dire Doctrine quel type correspond notre proprit date . V oici la liste des types possibles : array, object, boolean, integer, smallint, bigint, string, text, datetime, datet imetz, date, time, decimal, et float. Tapez donc datetime ; 5. Rptez les points 3 et 4 pour les proprits titre , auteur et contenu . Titre et Auteur sont de type string de 255 caractres (pourquoi pas). Contenu est par contre de type text ; 6. Code : Console New field name (press <return> to stop adding fields): date Field type [string]: datetime New field name (press <return> to stop adding fields): titre Field type [string]: string Field length [255]: 255 New field name (press <return> to stop adding fields): auteur Field type [string]: string Field length [255]: 255

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


New field name (press <return> to stop adding fields): contenu Field type [string]: text New field name (press <return> to stop adding fields):_

71/201

7.

Lorsque vous avez fini, appuyez sur la touche Entre ; Code : Console New field name (press <return> to stop adding fields): Do you want to generate an empty repository class [no]?_

Oui, on va crer le repository associ, c'est trs pratique nous en reparlerons largement. Entrez donc yes ; 8. Confirmez la gnration, et voil ! Code : Console Do you want to generate an empty repository class [no]? yes Summary before generation You are going to generate a "SdzBlogBundle:Article" Doctrine2 entity using the "annotation" format. Do you confirm generation [yes]? Entity generation Generating the entity code: OK You can now start using the generated code!

C:\wamp\www\Symfony>_

Allez tout de suite voir le rsultat dans le fichier Entity/Article.php. Symfony2 a tout gnr, mme les getters et les setters ! V ous tes l'heureux propritaire d'une simple classe avec plein d'annotations ! On a utilis le gnrateur de code pour nous faciliter la vie. Mais sachez que vous pouvez tout fait vous en passer ! Comme vous pouvez le voir, le code gnr n'est pas franchement compliqu, et vous pouvez bien entendu l'crire la main si vous prfrez.

Affiner notre entit avec de la logique mtier


L'exemple de notre entit Article est un peu simple, mais rappelez-vous que la couche modle dans une application est la couche mtier. C'est--dire qu'en plus de grer vos donnes, un modle contient galement la logique de l'application. V oyez par vousmmes avec les exemples ci-dessous.

Attributs calculs
Prenons l'exemple d'une entit Commande, qui reprsenterait un ensemble de produit acheter sur un site d'e-commerce. Cette entit aurait les attributs suivant : ListeProduits : qui contient un tableau des produits de la commande ; AdresseLivraison : qui contient l'adresse o expdier la commande ; Date : qui contient la date de la prise de la commande ; Etc. Ces trois attributs devront bien entendu tre mapps (c'est--dire dfini comme des colonnes pour l'ORM via des annotations) pour tre enregistrs en base de donnes par Doctrine2. Mais il existe d'autres caractristiques pour une commande, qui ncessitent un peu de calcul : le prix total, un ventuel coupon de rduction, etc. Ces caractristiques n'ont pas tre persist en base de donnes, car ils peuvent tre dduits des informations que l'on a dj. Par exemple pour avoir le prix total, il suffit de faire une boucle sur ListeProduits et dadditionner les prix de chaque produit : Code : PHP <?php // Exemple : class Commande { public function getPrixTotal() { $prix = 0;

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


foreach($this->getListeProduits() as $produit) { $prix += $produit->getPrix(); } return $prix;

72/201

N'hsitez donc pas crer des mthodes getQuelquechose() qui contiennent de la logique mtier. L'avantage de mettre la logique dans l'entit mme est que vous tes sr de rutiliser cette mme logique partout dans votre application. Il est bien plus propre et pratique de faire <?php $commande->getPrixTotal() que d'parpiller droite et gauche diffrentes manires de calculer ce prix total . Bien sr, ces mthodes n'ont pas d'quivalent setQuelquechose(), cela n'a pas de sens !

Attributs par dfaut


V ous avez aussi besoin des fois de dfinir une certaine valeur vos entits lors de leur cration. Or nos entits sont de simple objets PHP, et la cration d'un objet PHP fait appel... au constructeur. Pour notre entit Article on pourrait dfinir le constructeur suivant : Code : PHP <?php // src/Sdz/BlogBundle/Entity/Article.php namespace Sdz\BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity */ class Article { // La dfinition des attributs ... public function __construct() { $this->date = new \Datetime(); // Par dfaut, la date de l'article est la date d'aujourd'hui } } // Les getter/setter ...

Conclusion
N'oubliez pas : une entit est un objet PHP qui correspond un besoin dans votre application. N'essayez donc pas de raisonner en termes de tables, base de donnes, etc. V ous travaillez maintenant avec des objets PHP, qui contiennent une part de logique mtier, et qui peuvent se manipuler facilement. C'est vraiment important que vous fassiez l'effort ds maintenant de prendre l'habitude de manipuler des objets, et non des tables.

Tout sur le mapping !


V ous avez rapidement vu comment mapper vos objets avec les annotations. Mais ces annotations permettent d'inscrire pas mal d'autres informations. Il faut juste en connaitre la syntaxe, c'est l'objectif de cette sous-partie. Tout ce qui va tre dcrit ici se trouve bien entendu dans la documentation officielle sur le mapping, que vous pouvez garder porter de main.

L'annotation Entity
L'annotation Entity s'applique sur une classe, il faut donc la placer avant la dfinition de la classe en PHP. Elle dfinit un objet comme tant une entit, et donc persist par Doctrine. Cette annotation s'crit comme suit : Code : Autre @ORM\Entity

Il existe un seul paramtre facultatif pour cette annotation, "repositoryClass". Il permet de prciser le namespace complet du repository qui gre cette entit. Nous donnerons le mme nom nos repository qu' nos entits, en les suffixant simplement de "Repository". Pour notre entit Article, cela donne : Code : Autre @ORM\Entity(repositoryClass="Sdz\BlogBundle\Entity\ArticleRepository")

Un repository sert rcuprer vos entits depuis la BDD, on en reparle dans un chapitre ddi plus loin dans le tutoriel.

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2

73/201

L'annotation Table
L'annotation Table s'applique sur une classe galement. C'est une annotation facultative, une entit se dfinit juste par son annotation Entity. Cependant l'annotation Table permet de personnaliser le nom de la table qui sera cre dans la base de donnes. Par exemple, on pourrait prfixer notre table article par "sdz" : Code : Autre @ORM\Table(name="sdz_article")

Elle se positionne juste avant la dfinition de la classe. Par dfaut, si vous ne prcisez pas cette annotation, le nom de la table cre par Doctrine2 est le mme que celui de l'entit. Dans notre cas, cela aurait t "Article" (avec la majuscule donc, attention).

L'annotation Column
L'annotation Column s'applique sur un attribut de classe, elle se positionne donc juste avant la dfinition PHP de l'attribut concern. Cette annotation permet de dfinir les caractristiques de la colonne concerne. Elle s'crit comme suit : Code : Autre @ORM\Column

L'annotation Column comprend quelques paramtres, dont le plus important est le type de la colonne.

Les types de colonnes


Les types de colonnes que vous pouvez dfinir en annotation sont des types Doctrine, et uniquement Doctrine. Ne les confondez pas avec leurs homologues SQL ou PHP, ce sont des types Doctrine seul. Ils font la transition des types SQL aux types PHP. V oici la liste exhaustive des types Doctrine2 disponibles : Type Doctrine string integer smallint bigint boolean decimal date ou datetime time text object array float Type SQL V ARCHAR INT Type PHP string integer Utilisation Toutes les chaines de caractres jusqu' 255 caractres. Tous les nombres jusqu' 2 147 483 647. Tous les nombres jusqu' 32 767. Tous les nombres jusqu' 9 223 372 036 854 775 807. Attention PHP reoit une chane de caractres car il ne supporte pas un si grand nombre (suivant que vous tes en 32 ou en 64 bits). Les valeurs boolennes true et false. Les nombres virgules. Toutes les dates et heures. Toutes les heures. Les chanes de caractres de plus de 255 caractres.

SMALLINT integer BIGINT BOOLEAN DECIMAL DATETIME TIME CLOB CLOB CLOB FLOAT string boolean double objet DateTime objet DateTimestring

Type de Stocke un objet PHP en utilisant serialize/unserialize. l'objet stock array double Stocke un tableau PHP en utilisant serialize/unserialize. Tous les nombres virgules. Attention, fonctionne uniquement sur les serveurs dont la locale utilise un point comme sparateur.

Les types Doctrine sont sensibles la casse. Ainsi, le type "String" n'existe pas, il s'agit du type "string". Facile retenir : tout est en minuscule !

Le type de colonne se dfinit en tant que paramtre de l'annotation Column, comme suit : Code : Autre @ORM\Column(type="string")

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2

74/201

Les paramtres de l'annotation Column


Il existe 7 paramtres, tous facultatifs, que l'on peut passer l'annotation Column afin de personnaliser le comportement. V oici la liste exhaustive : Paramtre type Valeur par dfaut string Nom de l'attribut Utilisation Dfinit le type de colonne comme nous venons de le voir. Dfinit le nom de la colonne dans la table. Par dfaut, le nom de la colonne est le nom de l'attribut de l'objet, ce qui convient parfaitement. Mais vous pouvez changer le nom de la colonne, par exemple si vous prfrez "isExpired" en attribut mais "is_expired" dans la table. Dfinit la longueur de la colonne. Applicable uniquement sur un type de colonne string . Dfinit la colonne comme unique. Par exemple sur une colonne email pour vos membres. Permet la colonne de contenir des NULL. Dfinit la prcision d'un nombre virgule, c'est--dire le nombre de chiffres en tout. Applicable uniquement sur un type de colonne decimal . Dfinit le scale d'un nombre virgule, c'est--dire le nombre de chiffres aprs la virgule. Applicable uniquement sur un type de colonne decimal .

name

length unique nullable precision scale

255 false false 0 0

Pour dfinir plusieurs options en mme temps, il faut simplement les sparer avec une virgule. Par exemple, pour une colonne "email" en string 255, et unique : Code : Autre @ORM\Column(type="string", length=255, unique=true) V ous savez maintenant tout ce qu'il faut savoir sur la couche Modle sous Symfony2 en utilisant les entits de l'ORM Doctrine2. Dans le prochain chapitre, nous apprendrons manipuler ces entits.

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2

75/201

Manipuler ses entits avec Doctrine2


Dans ce chapitre, nous allons apprendre manipuler les entits que vous avez faite au prcdent chapitre.

Matrialiser les tables en base de donnes


Avant de pouvoir utiliser notre entit comme il se doit, on doit d'abord crer la table correspondante dans la base de donnes !

Crer la table correspondante dans la base de donnes


Alors, j'espre que vous avez install et configur phpMyAdmin, on va faire de la requte SQL l ! ... Ceux qui m'ont cru, relisez le chapitre prcdent. Les autres, venez, on est bien trop fainants pour ouvrir phpMyAdmin !

Vrifiez tout d'abord que vous avez bien configur l'accs votre base de donnes dans Symfony2. Si ce n'est pas le cas, il suffit d'ouvrir le fichier app/config/parameters.yml et de mettre les bonnes valeurs aux lignes commenant par database_ : serveur, nom de la base, nom d'utilisateur et mot de passe. V ous avez l'habitude de ces paramtres : Code : YAML # app/config/parameters.yml parameters: database_driver: database_host: database_port: database_name: database_user: database_password: pdo_mysql localhost ~ symfony root ~

Ensuite, direction la console. Cette fois-ci, on ne va pas utiliser une commande du generator mais une commande de Doctrine, car on ne veut pas gnrer du code mais une table dans la base de donnes. D'abord, si vous ne l'avez pas dj fait, il faut crer la base de donnes. Pour cela, excutez la commande (vous n'avez le faire qu'une seule fois videmment) : Code : Console C:\wamp\www\Symfony>php app/console doctrine:database:create Created database for connection named `symfony` D:\backup\www\Symfony>_

Ensuite, il faut gnrer les tables l'intrieur de cette base de donnes. Excutez donc la commande suivante : Code : Console php app/console doctrine:schema:update --dump-sql

Cette dernire commande est vraiment performante. Elle va comparer l'tat actuel de la base de donnes avec ce qu'elle devrait tre en tenant compte de toutes nos entits. Puis, elle affiche les requtes SQL excuter pour passer de l'tat actuel au nouvel tat. En loccurrence, nous avons seulement cr une entit, donc la diffrence entre l'tat actuel (base de donnes vide) et le nouvel tat (base de donnes avec une table article) n'est que d'une seule requte SQL : la requte de cration de la table. Doctrine vous affiche donc cette requte : Code : SQL CREATE TABLE Article (id INT AUTO_INCREMENT NOT NULL, date DATETIME NOT NULL, titre VARCHAR(255) NOT NULL, auteur VARCHAR(255) NOT NULL, contenu LONGTEXT NOT NULL, PRIMARY KEY(id)) ENGINE = InnoDB;

Pour l'instant, rien n'a t fait en base de donnes, Doctrine nous a seulement affich la ou les requtes qu'il s'apprte excuter. Pensez toujours valider rapidement ces requtes, pour tre sr de ne pas avoir fait d'erreur dans le mapping des entits. Mais maintenant, il est temps de passer aux choses srieuses, et dexcuter concrtement cette requte ! Lancez la commande suivante : Code : Console C:\wamp\www\Symfony>php app/console doctrine:schema:update --force Updating database schema... Database schema updated successfully! "1" queries were executed

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


C:\wamp\www\Symfony>_

76/201

Si tout se passe bien, vous avez le droit au Database schema updated successfully! . Gnial, mais bon, vrifions-le quand mme. Cette fois-ci, ouvrez phpMyAdmin (vraiment, a n'est pas un pige), allez dans votre base de donnes et voyez le rsultat : la table article a bien t cre avec les bonnes colonnes, l'id en auto-incrment, etc. C'est super !

Modifier une entit


Pour modifier une entit, il suffit de lui crer un attribut et de lui attacher l'annotation correspondante. Faisons-le ds maintenant en rajoutant un attribut $publication, un boolen qui indique si l'article est publi (true pour l'affiche sur la page d'accueil, false sinon), ce n'est qu'un exemple bien entendu. Rajoutez donc ces lignes dans votre entit : Code : PHP <?php // sdz/Sdz/BlogBundle/Entity/Article.php class Article { // ... /** * @ORM\Column(name="publication", type="boolean") */ private $publication; // Et modifions le constructeur pour mettre cet attribut publication true par dfaut public function __construct() { $this->date = new \Datetime(); $this->publication = true; } } // ...

Ensuite, soit vous crivez vous-mmes le getter getPublication et le setter setPublication, soit vous faites comme moi et vous utilisez le gnrateur ! Aprs la commande doctrine:generate:entity pour gnrer une entit entire, vous avez la commande doctrine:generate:entities . C'est une commande qui gnre les entits en fonction du mapping que Doctrine connait. Lorsque vous faites votre mapping en Yaml, il peut gnrer toute votre entit. Dans notre cas, nous faisons notre mapping en annotation alors nous avons dj dfinit l'attribut. La commande va donc gnrer ce qu'il manque : le getter et le setter ! Allons-y : Code : Console C:\wamp\www\Symfony>php app/console doctrine:generate:entities SdzBlogBundle:Article Generating entity "Sdz\BlogBundle\Entity\Article" > backing up Article.php to Article.php~ > generating Sdz\BlogBundle\Entity\Article

Allez vrifier votre entit, tout en bas de la classe le gnrateur a rajout les mthodes getPublication() et setPublication(). V ous pouvez voir galement qu'il a sauvegard l'ancienne version de votre entit dans un fichier nomm </minicode>Article.php~</minicode> : vrifiez toujours son travail, et si celui-ci ne vous convient pas, vous avez votre sauvegarde . Maintenant, il ne reste plus qu' enregistrer ce schma en base de donnes. Excutez donc : Code : Console php app/console doctrine:schema:update --dump-sql

Pour vrifier que la requte est bien : Code : SQL ALTER TABLE article ADD publication TINYINT(1) NOT NULL

C'est le cas, cet outil de Doctrine est vraiment pratique ! Puis, excutez la commande pour modifier effectivement la table

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


correspondante : Code : Console php app/console doctrine:schema:update --force

77/201

Et voil ! V otre entit a un nouvel attribut qui sera persist en base de donnes lorsque vous l'utiliserez.

A retenir
A chaque modification du mapping des entits, ou lors de l'ajout/suppression d'une entit, il faudra rpter ces commandes doctrine:schema:update --dump-sql puis --force pour mettre jour la base de donnes.

Enregistrer ses entits avec l'EntityManager Les services Doctrine2

Maintenant, apprenons manipuler nos entits. On va apprendre le faire en deux partie : d'abord l'enregistrement en base de donnes, ensuite la rcupration depuis la base de donnes. Mais d'abord, tudions un petit peu le service Doctrine.

Un service est une classe qui rempli une fonction bien prcise, accessible partout dans notre code. Nous verrons cette notion de service, trs importante dans Symfony2, dans un prochain chapitre. Pour l'instant, concentrons nous sur ce qui nous intresse : accder aux fonctionnalits Doctrine2.

Le service Doctrine
Le service Doctrine est celui qui va nous permettre de grer la persistance de nos objets. Ce service est accessible depuis le contrleur comme n'importe quel service : Code : PHP <?php $doctrine = $this->get('doctrine');

Mais, afin de profiter de l'autocompltion de votre IDE, la classe Controller de Symfony2 intgre un raccourci. Il fait exactement la mme chose mais est plus joli et permet l'autocompltion : Code : PHP <?php $doctrine = $this->getDoctrine();

C'est donc ce service Doctrine qui va nous permettre de grer la base de donnes. Il permet de grer deux choses : Les diffrentes connexions des bases de donnes. C'est la partie DBAL de Doctrine2. En effet vous pouvez tout fait utiliser plusieurs connexions plusieurs bases de donnes diffrentes. Cela n'arrive que dans des cas particuliers, mais c'est toujours bon savoir que Doctrine le gre bien. Le service Doctrine dispose donc, entre autres, de la mthode <? php $doctrine->getConnection($name) qui permet de rcuprer une connexion partir de son nom. Cette partie DBAL permet Doctrine2 de fonctionner sur plusieurs types de SGBDR, tels que MySQL, PostgreSQL, etc. Les diffrents gestionnaires d'entits, ou EntityManager. C'est la partie ORM de Doctrine2. Encore une fois c'est logique, vous pouvez bien sr utiliser plusieurs gestionnaires d'entits, ne serait-ce qu'un par connexion ! Le service dispose donc, entre autres, de la mthode dont nous nous servirons beaucoup : <?php $doctrine>getEntityManager($name) qui permet de rcuprer un ORM partir de son nom. Dans la suite du tutoriel, je considre que vous n'avez qu'un seul EntityManager, ce qui est le cas par dfaut. La mthode getEntityManager() permet de rcuprer l'EntityManager par dfaut en omettant l'argument $name. J'utiliserai donc toujours $doctrine->getEntityManager() sans argument, mais pensez adapter si ce n'est pas votre cas ! Si vous souhaitez utiliser plusieurs EntityManager, vous pouvez vous rfrer la documentation officielle qui l'explique.

Le service EntityManager
On vient de le voir, le service qui va nous intresser vraiment n'est pas doctrine, mais l'EntityManager de Doctrine. V ous savez dj le rcuprer depuis le contrleur via : Code : PHP <?php $em = $this->getDoctrine()->getEntityManager();

Mais sachez que, comme tout service qui se respecte, vous pouvez y accder directement via : Code : PHP <?php

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


$em = $this->get('doctrine.orm.entity_manager');

78/201

Mais attention, la premire mthode vous assure l'autocompletion alors que la deuxime non

C'est avec l'EntityManager que l'on va passer le plus clair de notre temps. C'est lui qui permet de dire Doctrine " Persiste cet objet ", c'est lui qui va excuter les requtes SQL (que l'on ne verra jamais), bref, c'est lui qui fera tout. La seule chose qu'il ne sait pas faire facilement, c'est rcuprer les entits depuis la base de donnes. Pour faciliter l'accs aux objets, on va utiliser des Repository.

Les services Repository


Les Repository sont des services qui utilisent un EntityManager dans les coulisses, mais qui sont bien plus facile et pratique utiliser de notre point de vue. Je parle des Repository au pluriel car il en existe un par entit. Quand on parle d'un Repository en particulier, il faut donc toujours prciser le Repository de quelle entit, afin de bien savoir de quoi on parle. On accde ces Repository de la manire suivante : Code : PHP <?php $em = $this->getDoctrine()->getEntityManager(); $repository_article = $em->getRepository('SdzBlogBundle:Article');

L'argument de la mthode getRepository est l'entit pour laquelle rcuprer le repository. Il y a deux manire de spcifier l'entit voulu : Soit en utilisant le namespace complet de l'entit. Pour notre exemple, cela donnerait : 'Sdz\BlogBundle\Entity\Article'. Soit en utilisant le raccourci Nom_du_bundle:Nom_de_l'entit. Pour notre exemple, c'est donc 'SdzBlogBundle:Article'. C'est un raccourci qui fonctionne partout dans Doctrine. Attention, ce raccourci ne fonctionne que si vous avez mis vos entits dans le namespace Entity dans votre bundle.

Ce sont donc ces repository qui nous permettront de rcuprer nos entits. Ainsi, pour charger deux entits diffrentes, il faut d'abord rcuprer leur repository respectif. Un simple plis prendre, mais trs logique.

Conclusion
V ous savez maintenant accder aux principaux acteurs que nous allons utiliser pour manipuler nos entits. Il reviendront trs souvent, sachez les rcuprer par coeur, cela vous facilitera la vie . Afin de bien les visualiser, voici un petit schma avoir en tte :

Enregistrer ses entits en base de donnes


Rappelez-vous on a dj vu comment crer une entit. Maintenant que l'on a cette magnifique entit entre les mains, il faut la donner Doctrine pour qu'il l'enregistre en base de donnes. L'enregistrement effectif en base de donnes se fait en deux tapes

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


trs simple depuis un contrleur. Modifiez la mthode ajouterAction() de notre contrleur pour faire les tests : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php // Attention bien rajouter ce use en dbut de contrleur use Sdz\BlogBundle\Entity\Article; // ... public function ajouterAction() { // Cration de l'entit $article = new Article(); $article->setTitre('Mon dernier weekend'); $article->setAuteur('Bibi'); $article->setContenu("C'tait vraiment super et on s'est bien amus."); // On peut ne pas dfinir pas la date ni la publication, // car ces attributs sont dfinis automatiquement dans le constructeur // On rcupre l'EntityManager $em = $this->getDoctrine()->getEntityManager(); // Etape 1 : On "persiste" l'entit $em->persist($article); // Etape 2 : On "flush" tout ce qui a t persist avant $em->flush(); // Reste de la mthode qu'on avait dj crit if( $this->get('request')->getMethod() == 'POST' ) { $this->get('session')->setFlash('notice', 'Article bien enregistr'); return $this->redirect( $this>generateUrl('sdzblog_voir', array('id' => $article->getId())) ); } return $this>render('SdzBlogBundle:Blog:ajouter.html.twig'); }

79/201

Reprenons ce code. La ligne 12 permet de crer l'entit, et les lignes 13 et 14 de renseigner ses attributs ; La ligne 18 permet de rcuprer l'EntityManager, on en a dj parl je ne reviens pas dessus ; L'tape 1 dit Doctrine de "persister" l'entit. Cela veut dire qu' partir de maintenant cette entit (qui n'est qu'un simple objet !) est gre par Doctrine. Cela n'excute pas encore de requte SQL ni rien. L'tape 2 dit Doctrine d'excuter effectivement les requtes ncessaires pour sauvegarder les entits qu'on lui a dit de persister ; Ligne 30, notre Article tant maintenant enregistr en base de donnes grce au flush(), Doctrine2 lui a attribu un id ! Allez sur la page /blog/ajouter, et voil, vous venez d'ajouter un article dans la base de donnes ! La requte SQL effectue vous intresse ? Je vous invite cliquer sur l'icne tout droite dans la barre d'outil Symfony2 en bas de la page :

Ma page a excut 2 requtes en l'occurrence. V ous arrivez alors dans la partie Doctrine du profiler de Symfony2, et vous pouvez voir les diffrentes requtes SQL excutes par Doctrine. C'est trs utile pour vrifier la valeur des paramtres, la structure des requtes, etc. N'hsitez pas y faire des tours !

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2

80/201

Alors vous me direz qu'ici on n'a persist qu'une seule entit, c'est vrai. Mais on peut tout fait faire plusieurs persists sur diffrentes entits avant d'excuter un seul flush. Le flush permet d'excuter les requtes les plus optimises pour enregistrer tous nos persists.

Doctrine utilise les transactions


Pourquoi deux mthodes <?php $em->persist() et <?php $em->flush() ? Car cela permet entre autres de profiter des transactions . Imaginons que vous ayez plusieurs entits persister en mme temps. Par exemple, lorsque l'on cre un sujet sur un forum, il faut enregistrer l'entit Sujet mais aussi l'entit Message, les deux en mme temps. Sans transaction, vous feriez d'abord la premire requte, puis la deuxime. Logique au final. Mais imaginez que vous ayez enregistr votre Sujet, et que l'enregistrement de votre Message choue : vous avez un sujet sans message ! a casse votre base de donnes car la relation n'est plus respecte. Avec une transaction, les deux entits sont enregistres en mme temps , ce qui fait que si la deuxime choue, alors la premire est annule, et vous gardez une base de donnes propre. Concrtement, avec notre EntityManager, chaque <?php $em->persist() est quivalent dire : Garde cette entit en mmoire, tu l'enregistreras au prochain flush(). . Et un <?php $em->flush() est quivalent ceci : Ouvre une transaction et enregistre toutes les entits qui t'ont t donnes depuis le dernier flush(). .

Doctrine simplifie la vie


V ous devez savoir une chose galement : la mthode <?php $em->persist() traite indiffremment les nouvelles entits des entits dj en base de donnes. V ous pouvez donc lui passer une entit frachement cre comme dans notre exemple cidessus, mais galement une entit que vous auriez rcupre grce l'EntityRepository et que vous auriez modifie (ou non, d'ailleurs). L'EntityManager s'occupe de tout, je vous disais ! Concrtement, cela veut dire que vous n'avez plus vous soucier de faire des INSERT INTO dans le cas d'une cration d'entit, et des UPDATE dans le cas d'entits dj existantes. Exemple : Code : PHP <?php // Depuis un contrleur $em = $this->getDoctrine()->getEntityManager(); // On cre un nouvel article $article1 = new Article; $article1->setTitre('Mon dernier weekend'); $article1->setContenu("C'tait vraiment super et on s'est bien amus."); // Et on le persiste $em->persist($article1); // On rcupre l'article d'id 5. On n'a pas encore vu cette mthode find(), mais elle est simple comprendre.

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


// Pas de panique on la voit en dtail dans un prochain chapitre ddi aux Repository $article2 = $em->getRepository('SdzBlogBundle:Article')->find(5); // On modifie cet article, en changeant la date la date d'aujourd'hui $article2->setDate(new \Datetime()); // Ici, pas besoin de faire un persist() sur $article2. En effet, comme on a rcupr cet article via Doctrine, // il sait dj qu'il doit grer cette entit. Rappelez-vous, un persist ne sert qu' donner la responsabilit de l'objet Doctrine. // Enfin, on applique les changements la base de donnes $em->flush();

81/201

Le flush() va donc excuter un INSERT INTO et un UPDATE tout seul. De notre ct, on a trait $article1 exactement comme $article2, ce qui nous simplifie bien la vie. Comment sait-il si l'entit existe dj ou non ? Grce la cl primaire de votre entit (dans notre cas, l'id). Si l'id est nul, c'est une nouvelle entit, tout simplement. Retenez bien galement le fait qu'il est inutile de faire un persist($entite) lorsque $entite a t rcupre grce Doctrine. En effet, rappelez-vous qu'un persist ne fait rien d'autre que de donner la responsabilit d'un objet Doctrine. Dans le cas de la variable $article1 de l'exemple ci-dessus, Doctrine ne peut pas deviner qu'il doit s'occuper de cet objet si on ne le lui dit pas ! D'o le persist(). Mais l'inverse, comme c'est Doctrine qui nous a donn l'objet $article2, il est grand et prend tout seul la responsabilit de cet objet, inutile de le lui rpter. Sachez galement que Doctrine est assez intelligent pour savoir si une entit a t modifie ou non. Ainsi, si dans notre exemple on ne modifiait pas $article2, Doctrine ne ferait pas de requte UPDATE inutile.

Les autres mthodes utiles de l'EntityManager


En plus des deux mthodes les plus importantes, persist et flush, l'EntityManager dispose de quelques mthodes intressantes. Je ne vais vous prsenter ici que les plus utilises, mais elles sont bien sr toutes documentes dans la documentation officielle, que je vous invite fortement aller voir. Mthode Rle Exemple Code : PHP <?php $em->persist($article); $em>persist($commentaire); $em->clear(); $em->flush(); // N'excutera rien car les deux persists sont annuls par le clear.

clear($nomEntite)

Annule tous les persist effectus. Si le nom d'une entit est prcis (son namespace complet ou son raccourci), uniquement les persist sur des entits de ce type seront annuls.

Code : PHP <?php $em->persist($article); $em>persist($commentaire); $em->detach($article); $em->flush(); // Enregistre le $commentaire mais pas l'$article

detach($entite)

Annule le persist effectu sur l'entit en argument. Au prochain flush(), aucun changement ne sera appliqu l'entit donc.

Code : PHP <?php $em->persist($article); var_dump($em>contains($article)); // Affiche true var_dump($em>contains($commentaire)); // Affiche false

contains($entite)

Retourne true si l'entit donne en argument est gre par l'EntityManager (s'il y a eu un persist sur l'entit donc).

Code : PHP <?php $article>setTitre('Un

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


refresh($entite) Met jour l'entit donne en argument dans l'tat o elle est en base de donnes. Cela crase et donc annule tous les changements qu'il a pu y avoir sur l'entit concerne. >setTitre('Un nouveau titre'); $em>refresh($article); var_dump($article>getTitre()); // Affiche "Un ancien titre"

82/201

Code : PHP <?php $em>remove($article); $em->flush(); // Excute un DELETE sur l'$article

remove($entite)

Supprime l'entit donne en argument de la base de donnes. Effectif au prochain flush().

Rcuprer ses entits avec un EntityRepository


Un prochaine chapitre entier est consacr aux repository, juste aprs dans cette partie sur Doctrine. Les repository ne sont qu'un outil pour rcuprer vos entits trs facilement, nous apprendrons les matriser entirement. Mais en avant premire, sachez au moins rcuprer une unique entit en fonction de son id. Il faut d'abord pour cela rcuprer le repository de l'entit que vous voulez. On l'a vu plus haut, voici un rappel : Code : PHP <?php // Depuis un contrleur $repository = $this->getDoctrine() ->getEntityManager() ->getRepository('SdzBlogBundle:Article');

Puis, depuis ce repository, il faut utiliser la mthode find($id) qui permet de retourner l'entit correspondant l'id $id. Je vous invite essayer ce code directement dans la mthode voirAction() de notre contrleur Blog, l o on avait dfinit en dur un tableau $article. On pourra ainsi voir l'effet immdiatement : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php // ... public function voirAction($id) { // On rcupre le repository $repository = $this->getDoctrine() ->getEntityManager() ->getRepository('SdzBlogBundle:Article'); // On rcupre l'entit correspondant l'id $id $article = $repository->find($id); // $article est donc une instance de Sdz\BlogBundle\Entity\Article // Ou null si aucun article n'a t trouv avec l'id $id if($article === null) { throw $this->createNotFoundException('Article[id='.$id.'] inexistant.'); } return $this->render('SdzBlogBundle:Blog:voir.html.twig', array( 'article' => $article ));

Allez voir le rsultat sur la page /blog/article/1 ! V ous pouvez changer l'id de l'article rcuprer dans l'URL, en fonction des articles que vous avez ajout plus haut depuis la mthode ajouterAction(). Sachez aussi qu'il existe une autre syntaxe pour faire la mme chose directement depuis l'EntityManager, je vous la prsente afin que vous ne soyez pas surpris si vous la croisez : Code : PHP

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


<?php // Depuis un contrleur $article = $this->getDoctrine() ->getEntityManager() ->find('SdzBlogBundle:Article', $id); // 1er argument : le nom de l'entit // 2e argument : l'id de l'instance rcuprer

83/201

Je n'en dis pas plus pour le moment, patientez jusqu'au chapitre ddi sur les repository ! Maintenant que vous savez faire et manipuler des entits simples, il est temps d'apprendre crer des relations entre entits. Rendez-vous au prochain chapitre !

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2

84/201

Les relations entre entits avec Doctrine2


Dans ce chapitre nous allons apprendre mettre en relation nos entits, afin de crer un ensemble cohrent.

Prsentation Prsentation
V ous savez dj stocker vos entits indpendamment les unes des autres, c'est trs bien. Simplement on est rapidement limit ! L'objectif de ce chapitre est de vous apprendre tablir des relations entre les entits. Rappelez-vous, au dbut de la partie sur Doctrine2 je vous avais promis des choses comme <?php $article>getCommentaires(). Et bien c'est cela que nous allons faire ici !

Les diffrents types de relations


Il y a plusieurs faon de lier des entits entre elles. En effet il n'est pas pareil de lier une multitudes de commentaires un seul article, que de lier un membre un seul groupe. Il existe donc plusieurs types de relations, pour rpondre plusieurs besoins concrets. Ce sont les relations OneToOne, OneToMany et ManyToMany. On les tudie juste aprs ces quelques notions de base avoir.

Notions techniques d'ORM savoir


Avant de voir en dtail les relations, il faut comprendre comment elles fonctionnent. N'ayez pas peur, il y a juste deux notions savoir avant d'attaquer.

Notion de propritaire et d'inverse


La notion de propritaire et d'inverse est abstraite mais importante comprendre. Dans une relation entre deux entits, il y a toujours une entit dite propritaire, et une dite inverse. Pour comprendre cette notion, il faut revenir la vieille poque, lorsque l'on faisait nos bases de donnes la main. L'entit propritaire est celle qui contient la rfrence l'autre entit. Attention cette notion, avoir en tte lors de la cration des entits, n'est pas li votre logique mtier, elle est purement technique. Prenons un exemple simple, toujours les commentaires d'un article de blog. V ous disposez de la table commentaire et de la table article , trs bien. Pour crer une relation entre ces deux tables, vous allez mettre naturellement une colonne article_id dans la table commentaire . La table commentaire est donc propritaire de la relation, car c'est elle qui contient la colonne de liaison article_id . Assez simple au final ! N'allez pas me crer une colonne article_id dans la table des commentaires ! C'est une image, de ce que vous faisiez avant. Aujourd'hui on va laisser Doctrine grer tout a, et on ne va jamais mettre la main dans PhpMyAdmin. Rappelezvous : on pense objet dornavant, et pas base de donnes.

Notion d'Unidirectionnalit et de Bidirectionnalit


Cette notion est galement simple comprendre : une relation peut tre sens unique ou double sens . On ne va traiter dans ce chapitre que les relations sens unique, dites unidirectionnelles. Cela signifie que vous pourrez faire <?php $entiteProprietaire->getEntiteInverse() (dans notre exemple <?php $commentaire>getArticle()), mais vous ne pourrez pas faire <?php $entiteInverse->getEntiteProprietaire() (pour nous, <?php $article->getCommentaires()). Attention, cela ne nous empchera pas de rcuprer les commentaires d'un article, on utilisera juste une autre mthode, via l'EntityRepository. Cette limitation nous permet de simplifier la faon de dfinir les relations. Pour bien travailler avec, il suffit juste de se rappeler qu'on ne peut faire $entiteInverse->getEntiteProprietaire(). Pour des cas spcifiques, ou des prfrences dans votre code, cette limitation peut tre contourne en utilisant les relations double sens, dites bidirectionnelles. Je les expliquerai rapidement la fin de ce chapitre.

Rien n'est magique


Non rien n'est magique. Je dois vous avertir qu'un <?php $article->getCommentaires() est vraiment sympa mais... qu'il dclenche bien sr une requte SQL ! Lorsqu'on rcupre une entit (notre $article par exemple), Doctrine ne rcupre pas toutes les entits qui lui sont lies (les commentaires dans l'exemple), et heureusement ! S'il le faisait, cela serait extrmement lourd. Imaginez qu'on veuille juste rcuprer un Article pour avoir son titre, et Doctrine nous rcupre la liste des 54 commentaires, qui en plus sont lis leurs 54 auteurs respectifs, etc. ! Doctrine utilise ce qu'on appelle le Lazy Loading , chargement fainant en franais. C'est--dire qu'il ne va charger les entits l'autre bout de la relation que si vous voulez accder ces entits. C'est donc pile au moment o vous faites <?php $article->getCommentaires() que Doctrine va charger les commentaires (avec une nouvelle requte SQL donc) puis va vous les transmettre. Heureusement pour nous, il est possible d'viter cela ! Parce que cette syntaxe est vraiment pratique, il serait dommage de s'en priver pour cause de requtes SQL trop nombreuses. Il faudra simplement utiliser nos propres mthodes pour charger les entits, dans lesquelles nous ferons des jointures toutes simples. L'ide est de dire Doctrine : " charge l'entit Article mais galement tous ses Commentaires". Avoir nos propres mthodes pour cela permet de ne les excuter que si nous voulons vraiment avoir les Commentaires en plus de l'Article. En sommes, on se garde le choix de charger ou non la relation. Mais nous verrons tout cela dans le prochain chapitre sur les Repository. Pour l'instant, revenons nos relations !

Relation One-To-One Prsentation


La relation One-To-One, ou 1..1 , est assez classique. Elle correspond, comme son nom l'indique, une relation unique entre deux

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


objets. Pour illustrer cette relation dans le cadre du blog, nous allons crer une entit Image. Imaginons qu'on offre la possibilit de lier une image un article, une sorte d'icne pour illustrer un peu l'article. Si chaque article on ne peut afficher qu'une seule image, et que chaque image ne peut tre lie qu' un seul article, alors on est bien dans le cadre d'une relation One-To-One. V oici un schma :

85/201

Tout d'abord, histoire qu'on parle bien de la mme chose, crez cette entit Image avec au moins les attributs "url" et "alt" pour qu'on puisse l'afficher correctement. V oici la mienne : Secret (cliquez pour afficher) Code : PHP <?php namespace Sdz\BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * Sdz\BlogBundle\Entity\Image * * @ORM\Table() * @ORM\Entity(repositoryClass="Sdz\BlogBundle\Entity\ImageRepository") */ class Image { /** * @var integer $id * * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @var string $url * * @ORM\Column(name="url", type="string", length=255) */ private $url; /** * @var string $alt * * @ORM\Column(name="alt", type="string", length=255) */ private $alt; } // Les getters et setters

Dfinition de la relation dans les entits


Annotation
Pour tablir une relation One-To-One entre deux entits Article et Image, la syntaxe est la suivante :

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


Entit propritaire, Article : Code : PHP <?php /** * @ORM\Entity */ class Article { /** * @ORM\OneToOne(targetEntity="Sdz\BlogBundle\Entity\Image") */ private $image; } // ...

86/201

Entit inverse, Image : Code : PHP <?php /** * @ORM\Entity */ class Image { // Nul besoin de rajouter une proprit ici. } // ...

La dfinition de la relation est plutt simple, mais dtaillons la bien. Tout d'abord, j'ai choisi de dfinir l'entit Article comme entit propritaire de la relation, car un Article "possde" une Image. On aura donc plus tendance rcuprer l'image partir de l'article que l'inverse. Cela permet galement de rendre indpendante l'entit Image : elle pourra tre utilise par d'autres entits que Article, de faon totalement invisible pour elle. Ensuite, vous voyez que seule l'entit propritaire a t modifie, ici Article. C'est parce qu'on a une relation unidirectionnelle, rappelez-vous on peut donc faire $article->getImage() mais pas $image->getArticle(). Dans une relation unidirectionnelle l'entit inverse, ici Image, ne sait en fait mme pas qu'elle est lie une autre entit, ce n'est pas son rle. Enfin, concernant l'annotation en elle mme : Code : Autre @ORM\OneToOne(targetEntity="Sdz\BlogBundle\Entity\Image")

Il y a plusieurs choses savoir sur cette annotation : Elle est incompatible avec l'annotation @ORM\Column qu'on a vu dans un chapitre prcdent. En effet, l'annotation Column dfinit une valeur (un nombre, une chaine de caractres, etc.), alors que OneToOne dfinit une relation vers une autre entit ; Elle possde au moins l'attribut "targetEntity", qui vaut simplement le namespace complet vers l'entit lie ; Elle possde d'autres attributs, mais que nous verrons plus loin dans ce chapitre ; Par dfaut, une relation est facultative, c'est--dire que vous pouvez avoir un Article qui n'a pas d'Image lie. C'est le comportement que nous voulons pour l'exemple : on se donne le droit d'ajouter un article sans forcment trouver une image d'illustration. Si vous souhaitez forcer la relation, il faut rajouter l'annotation JoinColumn et dfinir son attribut "nullable" false, comme ceci : Code : Autre /** * @ORM\OneToOne(targetEntity="Sdz\BlogBundle\Entity\Image") * @ORM\JoinColumn(nullable=false) */ private $image;

N'oubliez pas de mettre jour la base de donnes avec la commande doctrine:schema:update !

Getter et setter
D'abord n'oubliez pas de dfinir un getter et un setter dans l'entit propritaire, ici Article. V ous pouvez utiliser la commande php app/console doctrine:generate:entities SdzBlogBundle:Article , ou alors les copier-coller, les voici : Code : PHP

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


<?php // src/Sdz/BlogBundle/Entity/Article.php /** * @ORM\Entity */ class Article { /** * @ORM\OneToOne(targetEntity="Sdz\BlogBundle\Entity\Image") */ private $image; // Vos autres attributs ... /** * @param Sdz\BlogBundle\Entity\Image $image * @return Article */ public function setImage(\Sdz\BlogBundle\Entity\Image $image = null) { $this->image = $image; } /** * @return Sdz\BlogBundle\Entity\Image */ public function getImage() { return $this->image; } } // Vos autres getter/setter ...

87/201

V ous voyez qu'on a forc le type de l'argument pour le setter setImage() : cela permet de dclencher une erreur si vous essayez de passer un autre objet que Image la mthode. Trs utile pour viter de chercher des heures l'origine d'un problme parce que vous avez passez un mauvais argument . Notez galement le " = null" qui permet d'accepter les valeurs null : rappelez-vous la relation est facultative ! Prenez bien conscience d'une chose galement : le getter getImage() retourne une instance de la classe Image directement. Lorsque vous avez un Article, disons $article, et que vous voulez rcuprer l'URL de l'Image associe, il faut donc faire : Code : PHP <?php $image = $article->getImage(); $url = $image->getUrl(); // Ou bien sr en plus simple : $url = $article->getImage()->getUrl();

Pour les curieux qui auront t voir ce qui a t fait en base de donnes : une colonne "image_id" a bien t ajout la table article. Cependant, ne confondez surtout par cette colonne "image_id" avec notre attribut "image", et gardez bien ces deux points en tte : L'entit Article ne contient pas d'attribut "image_id" ; L'attribut "image" ne contient pas l'id de l'Image lie, il contient une instance de la classe Sdz\BlogBundle\Entity\Image qui, elle, contient un attribut id. Ne faites surtout pas la confusion : une Entit n'est pas une Table.

Exemple d'utilisation
Pour utiliser cette relation, c'est trs simple. V oici un exemple pour ajouter un nouvel Article et son Image depuis un contrleur. Modifions l'action ajouterAction(), qui tait dj bien complte : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php // N'oubliez pas de rajouter ce use ! use Sdz\BlogBundle\Entity\Image; // ... public function ajouterAction() { // Cration de l'entit Article

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


$article = new Article(); $article->setTitre('Mon dernier weekend'); $article->setContenu("C'tait vraiment super et on s'est bien amus."); $article->setAuteur('winzou'); // Cration de l'entit Image $image = new Image(); $image>setUrl('http://uploads.siteduzero.com/icones/478001_479000/478657.png'); $image->setAlt('Logo Symfony2'); // On lie l'image l'article $article->setImage($image); // On rcupre l'EntityManager $em = $this->getDoctrine()->getEntityManager(); // Etape 1 : On persiste les entits $em->persist($article); $em->persist($image); // Etape 2 : On dclenche l'enregistrement $em->flush(); } // ... reste de la mthode

88/201

Pour information, voici comment on pourrait modifier la vue de visualisation d'un article, pour y intgrer l'image : Secret (cliquez pour afficher) Code : HTML & Django {# src/Sdz/BlogBundle/Resources/views/Blog/voir.html.twig #} {% extends "SdzBlogBundle::layout.html.twig" %} {% block title %} Lecture d'un article - {{ parent() }} {% endblock %} {% block sdzblog_body %} <h2> {# On vrifie qu'une image soit bien associe l'article #} {% if article.image is not null %} <img src="{{ article.image.url }}" alt="{{ article.image.alt }}" /> {% endif %} {{ article.titre }} </h2> <i>Par {{ article.auteur }}, le {{ article.date|date('d/m/Y') }}</i> <div class="well"> {{ article.contenu }} </div> <p> <a href="{{ path('sdzblog_accueil') }}" class="btn"> <i class="icon-chevron-left"></i> Retour la liste </a> <a href="{{ path('sdzblog_modifier', {'id': article.id}) }}" class="btn"> <i class="icon-edit"></i> Modifier l'article </a> <a href="{{ path('sdzblog_supprimer', {'id': article.id}) }}" class="btn"> <i class="icon-trash"></i> Supprimer l'article </a> </p> {% endblock %}

Et voici un autre exemple, qui modifierait l'Image d'un Article dj existant. Ici je vais prendre une mthode arbitraire mais vous savez tout ce qu'il faut pour l'implmenter rellement :

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


Code : PHP <?php // Dans un contrleur, celui que vous voulez public function modifierImageAction($id_article) { $em = $this->getDoctrine()->getEntityManager(); // On rcupre l'article. $article = $em->getRepository('SdzBlogBundle:Article')>find($id_article); // On modifie l'URL de l'image par exemple. $article->getImage()->setUrl('test.png'); // faites // depuis On n'a pas besoin de persister notre article (si vous le aucune erreur n'est dclenche, Doctrine l'ignore). Rappelez-vous il l'est automatiquement car on l'a rcupr Doctrine.

89/201

// Pas non plus besoin de persister l'image ici, car elle est galement rcupre par Doctrine. // On dclenche la modification. $em->flush(); } return new Response('OK');

Le code parle de lui-mme : grer une relation est vraiment ais avec Doctrine !

Relation Many-To-One Prsentation


La relation Many-To-One, ou n..1 , est assez classique galement. Elle correspond, comme son nom l'indique, une relation qui permet une entit A d'avoir une relation avec plusieurs entits B. Pour illustrer cette relation dans le cadre de notre blog, nous allons crer une entit Commentaire. L'ide est de pouvoir ajouter plusieurs commentaires un article, et que chaque commentaire ne soit li qu' un seul article. Nous avons ainsi plusieurs commentaires (Many) lier (To ) un seul article (One). V oici un schma :

Comme plus haut, pour tre sr qu'on parle bien de la mme chose, crez cette entit Commentaire avec au moins les attributs "auteur", "contenu" et "date". V oici la mienne : Secret (cliquez pour afficher)

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


Code : PHP <?php // src/Sdz/BlogBundle/Entity/Commentaire.php namespace Sdz\BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * Sdz\BlogBundle\Entity\Commentaire * * @ORM\Table() * @ORM\Entity(repositoryClass="Sdz\BlogBundle\Entity\CommentaireRepository") */ class Commentaire { /** * @var integer $id * * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @var string $auteur * * @ORM\Column(name="auteur", type="string", length=255) */ private $auteur; /** * @var text $contenu * * @ORM\Column(name="contenu", type="text") */ private $contenu; /** * @var datetime $date * * @ORM\Column(name="date", type="datetime") */ private $date; public function __construct() { $this->date = new \Datetime(); } /** * Get id * * @return integer */ public function getId() { return $this->id; } /** * Set auteur * * @param string $auteur * @return Commentaire */ public function setAuteur($auteur) { $this->auteur = $auteur; return $this; } /** * Get auteur * * @return string */ public function getAuteur() { return $this->auteur; } /** * Set contenu * * @param text $contenu

90/201

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


* @param text $contenu * @return Commentaire */ public function setContenu($contenu) { $this->contenu = $contenu; return $this; } /** * Get contenu * * @return text */ public function getContenu() { return $this->contenu; } /** * Set date * * @param datetime $date * @return Commentaire */ public function setDate(\Datetime $date) { $this->date = $date; return $this; } /** * Get date * * @return datetime */ public function getDate() { return $this->date; } }

91/201

Dfinition de la relation dans les entits


Annotation
Pour tablir cette relation dans votre entit, la syntaxe est la suivante : Entit propritaire, Commentaire : Code : PHP <?php /** * @ORM\Entity */ class Commentaire { /** * @ORM\ManyToOne(targetEntity="Sdz\BlogBundle\Entity\Article") * @ORM\JoinColumn(nullable=false) */ private $article; } // ...

Entit inverse, Article : Code : PHP <?php /** * @ORM\Entity */ class Article { // Nul besoin de rajouter de proprit, ici. } // ...

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


L'annotation utiliser est tout simplement ManyToOne. Premire remarque : l'entit propritaire pour cette relation est Commentaire, et non Article. Pourquoi ? Parce que rappelezvous, le propritaire est celui qui contient la colonne rfrence. Ici, on aura bien une colonne article_id dans la table commentaire . En fait, de faon systmatique, c'est le ct Many d'une relation Many-To-One qui est le propritaire, vous n'avez pas le choix. Ici, on a plusieurs commentaires pour un seul article, le Many correspond aux commentaires, donc l'entit Commentaire est la propritaire. Deuxime remarque : j'ai volontairement rajout l'annotation JoinColumn avec son attribut nullable false, pour interdire la cration d'un commentaire sans article. En effet, dans notre cas, un commentaire qui n'est rattach aucun article n'a pas de sens. Aprs attention, il se peut trs bien que dans votre application vous deviez laisser la possibilit au ct Many de la relation d'exister sans forcment tre attach un ct One. N'oubliez pas de mettre jour la base de donnes avec la commande doctrine:schema:update !

92/201

Getter et setter
Ajoutons maintenant le getter et le setter correspondant dans l'entit propritaire. Comme tout l'heure, vous pouvez utiliser la mthode php app/console doctrine:generate:entities SdzBlogBundle:Commentaire , ou alors copier-coller ceux-l : Code : PHP <?php // src/Sdz/BlogBundle/Entity/Commentaire.php /** * @ORM\Entity */ class Commentaire { /** * @ORM\ManyToOne(targetEntity="Sdz\BlogBundle\Entity\Article") * @ORM\JoinColumn(nullable=false) */ private $article; // ... reste des attributs /** * Set article * * @param Sdz\BlogBundle\Entity\Article $article * @return Commentaire */ public function setArticle(\Sdz\BlogBundle\Entity\Article $article) { $this->article = $article; return $this; } /** * Get article * * @return Sdz\BlogBundle\Entity\Article */ public function getArticle() { return $this->article; } } // ... reste des getter et setter

V ous pouvez remarquer que, comme notre relation est obligatoire, il n'y a pas le "= null" dans le setArticle().

Exemple d'utilisation
La mthode pour grer une relation ManyToOne n'est pas trs diffrente que pour une relation OneToOne, voyez par vousmmes dans ces exemples. Tout d'abord pour ajouter un nouvel Article et ses Commentaires, modifions encore la mthode ajouterAction() de notre contrleur : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


// N'oubliez pas ce use ! use Sdz\BlogBundle\Entity\Commentaire; public function ajouterAction() { // Cration de l'entit Article $article = new Article(); $article->setTitre('Mon dernier weekend'); $article->setContenu("C'tait vraiment super et on s'est bien amus."); $article->setAuteur('winzou'); // Cration d'un premier commentaire $commentaire1 = new Commentaire(); $commentaire1->setAuteur('winzou'); $commentaire1->setContenu('On veut les photos !'); // Cration d'un deuxime commentaire, par exemple $commentaire2 = new Commentaire(); $commentaire2->setAuteur('Choupy'); $commentaire2->setContenu('Les photos arrivent !'); // On lie les commentaires l'article $commentaire1->setArticle($article); $commentaire2->setArticle($article); // On rcupre l'EntityManager $em = $this->getDoctrine()->getEntityManager(); // Etape 1 : On persiste les entits $em->persist($article); $em->persist($commentaire1); $em->persist($commentaire2); // Etape 2 : On dclenche l'enregistrement $em->flush(); } // ... reste de la mthode

93/201

Pour information, voici comment on pourrait modifier l'action voirAction() du contrleur pour passer non seulement l'article la vue, mais galement ses commentaires : Secret (cliquez pour afficher) Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php public function voirAction($id) { // On rcupre l'EntityManager $em = $this->getDoctrine() ->getEntityManager(); // On rcupre l'entit correspondant l'id $id $article = $em>getRepository('SdzBlogBundle:Article') ->find($id); if($article === null) { throw $this>createNotFoundException('Article[id='.$id.'] inexistant.'); } // On rcupre la liste des commentaires $liste_commentaires = $em>getRepository('SdzBlogBundle:Commentaire') ->findAll(); // Puis modifiez la ligne du render comme ceci, pour prendre en compte l'article : return $this>render('SdzBlogBundle:Blog:voir.html.twig', array( 'article' => $article, 'liste_commentaires' => $liste_commentaires )); }

Nous verrons plus en dtails la mthode findAll() dans le prochain chapitre. Et bien entendu, il faudrait adapter la vue si vous voulez afficher la liste des commentaires que nous venons de lui passer.

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2

94/201

Relation Many-To-Many Prsentation


La relation Many-To-Many, ou n..n, correspond une relation qui permet plein d'objets d'tre en relation avec plein d'autres ! O.K., prenons l'exemple cette fois-ci des articles de notre blog, rpartis dans des catgories. Un article peut appartenir plusieurs catgories . l'inverse, une catgorie peut contenir plusieurs articles . On a donc une relation Many-To-Many entre Article et Catgorie. V oici un schma :

Cette relation est particulire dans le sens o Doctrine va devoir crer une table intermdiaire. En effet, avec la mthode traditionnelle en base de donnes, comment feriez-vous pour faire ce genre de relation ? V ous avez une table membre , une autre table groupe , mais vous avez surtout besoin d'une table membre_groupe qui fait la liaison entre les deux ! Cette table de liaison ne contient que deux colonnes : membre_id et groupe_id . Cette table intermdiaire, vous ne la connatrez pas : elle napparat pas dans nos entits, et c'est Doctrine qui la cre et qui la gre tout seul ! Je vous ai parl de cette table intermdiaire pour que vous compreniez comment Doctrine fonctionne. Cependant attention, nous sommes d'accord que vous devez totalement oublier cette notion de "table intermdiaire" lorsque vous manipulez des objets (les entits) ! J'insiste sur le fait que si vous voulez utiliser Doctrine, alors il faut le laisser grer la base de donnes tout seul : vous utilisez des objets, lui utilise une base de donnes, chacun son travail.

Encore une fois, pour tre sr que l'on parle bien de la mme chose, crez cette entit Categorie avec au moins un attribut "nom". V oici la mienne : Secret (cliquez pour afficher) Code : PHP <?php // src/Sdz/BlogBundle/Entity/Categorie.php namespace Sdz\BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * Sdz\BlogBundle\Entity\Categorie * * @ORM\Table() * @ORM\Entity(repositoryClass="Sdz\BlogBundle\Entity\CategorieRepository") */ class Categorie { /** * @var integer $id *

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


* @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @var string $nom * * @ORM\Column(name="nom", type="string", length=255) */ private $nom; /** * Get id * * @return integer */ public function getId() { return $this->id; } /** * Set nom * * @param string $nom * @return Categorie */ public function setNom($nom) { $this->nom = $nom; return $this; } /** * Get nom * * @return string */ public function getNom() { return $this->nom; } }

95/201

Dfinition de la relation dans les entits


Annotation
Pour tablir cette relation dans vos entits, la syntaxe est la suivante. Entit propritaire, Article : Code : PHP <?php /** * @ORM\Entity */ class Article { /** * @ORM\ManyToMany(targetEntity="Sdz\BlogBundle\Entity\Categorie") */ private $categories; } // ...

/** * @ORM\Entity */ class Groupe { // Nul besoin d'ajouter une proprit, ici. } // ...

J'ai mis Article comme propritaire de la relation. C'est un choix que vous pouvez faire comme bon vous semble, ici. Mais bon, rcuprer les catgories d'un article se fera assez souvent, alors que rcuprer les articles d'une catgorie moins. Et puis, pour

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


rcuprer les articles d'une catgorie, on aura surement besoin de personnaliser la requte, donc on le fera de toute faon depuis le CategorieRepository.

96/201

Getter et setter
Dans ce type de relation, il faut soigner un peu plus l'entit propritaire. Tout d'abord, on a pour la premire fois un attribut (ici $categories) qui contient une liste d'objets . C'est parce qu'il contient une liste d'objets qu'on a mis le nom de cet attribut au pluriel . Les listes d'objets avec Doctrine2 ne sont pas de simples tableaux, mais des ArrayCollection, il faudra donc dfinir l'attribut comme tel dans le constructeur. Un ArrayCollection est un objet utilis par Doctrine2, qui a toute les proprits d'un tableau normal. V ous pouvez faire un foreach dessus, et le traiter comme n'importe quel tableau, etc. Il dispose juste de quelques mthodes supplmentaires trs pratique, que nous verrons. Ensuite, le getter est classique et s'appelle getCategories(). Par contre, c'est les setters qui vont diffrer un peu. En effet, "$categories" est une liste de catgories, mais au quotidien ce qu'on va faire c'est ajouter une une des catgories cette liste. Il nous faut donc une mthode addCategorie() (sans "s", on n'ajoute qu'une seule catgorie la fois) et non une setCategories(). Du coup, il nous faut galement une mthode pour supprimer une catgorie de la liste, que l'on appelle removeCategorie(). Ajoutons maintenant le getter et les setter correspondants dans l'entit propritaire, Article. Comme tout l'heure, vous pouvez utiliser la mthode php app/console doctrine:generate:entities SdzBlogBundle:Article , ou alors copier-coller ceux-l : Code : PHP <?php // src/Sdz/BlogBundle/Entity/Article.php /** * @ORM\Entity */ class Article { /** * @ORM\ManyToMany(targetEntity="Sdz\BlogBundle\Entity\Categorie") */ private $categories; // ... vos autres attributs // Comme la proprit $categories doit tre un ArrayCollection, souvenez-vous ; // on doit la dfinir dans un constructeur : public function __construct() { // Si vous aviez dj un constructeur, rajoutez juste cette ligne : $this->categories = new \Doctrine\Common\Collections\ArrayCollection(); } /** * Add categories * * @param Sdz\BlogBundle\Entity\Categorie $categories * @return Article */ public function addCategorie(\Sdz\BlogBundle\Entity\Categorie $categories) // addCategorie sans s ! { // Ici, on utilise l'ArrayCollection vraiment comme un tableau, avec la syntaxe [] $this->categories[] = $categories; return $this; } /** * Remove categories * * @param Sdz\BlogBundle\Entity\Categorie $categories */ public function removeCategorie(\Sdz\BlogBundle\Entity\Categorie $categories) // removeCategorie sans s ! { // Ici on utilise une mthode de l'ArrayCollection, pour supprimer la catgorie en argument $this->categories->removeElement($categories); } /** * Get categories * * @return Doctrine\Common\Collections\Collection */ public function getCategories() // Notez le s , on rcupre une liste de catgories ici ! { return $this->categories;

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


} } // ... vos autres getter/setter

97/201

N'oubliez pas de mettre jour la base de donnes avec la commande doctrine:schema:update

Remplissons la base de donnes


Avant de voir un exemple, j'aimerais vous faire ajouter quelques catgories en base de donnes histoire d'avoir de quoi jouer avec. Pour cela, petit apart, nous allons faire une commande Symfony2 nous ! Nous le verrons plus en dtails dans un chapitre ddi en fin de tutoriel, mais sachez que c'est vraiment trs simple. On ne va pas s'tendre dessus maintenant, crez juste le fichier src/Sdz/BlogBundle/Command/FixtureCategoriesCommand.php : Secret (cliquez pour afficher) Code : PHP <?php // src/Sdz/BlogBundle/Command/FixtureCategoriesCommand.php namespace Sdz\BlogBundle\Command; use use use use Symfony\Component\Console\Input\InputInterface; Symfony\Component\Console\Output\OutputInterface; Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; Sdz\BlogBundle\Entity\Categorie;

class FixtureCategoriesCommand extends ContainerAwareCommand { protected function configure() { $this->setName('sdzblog:fixture:categories'); } protected function execute(InputInterface $input, OutputInterface $output) { // On rcupre l'EntityManager $em = $this->getContainer()>get('doctrine.orm.entity_manager'); // Liste des noms de catgorie ajouter $noms = array('Symfony2', 'Doctrine2', 'Tutoriel', 'Evenement'); foreach($noms as $i => $nom) { $output->writeln('Creation de la categorie : '.$nom); // On cre la catgorie $liste_categories[$i] = new Categorie(); $liste_categories[$i]->setNom($nom); // On la persiste $em->persist($liste_categories[$i]);

$output->writeln('Enregistrement des categories...'); // On dclenche l'neregistrement $em->flush(); excute } } // On retourne 0 pour dire que la commande s'est bien return 0;

C'est tout ! V ous pouvez ds prsent excuter la commande suivante : Code : Console C:\wamp\www\Symfony>php app/console sdzblog:fixture:categories Creation de la categorie : Symfony2 Creation de la categorie : Doctrine2 Creation de la categorie : Tutoriel Creation de la categorie : Evenement Enregistrement des categories... C:\wamp\www\Symfony>_

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2

98/201

Et voil ! Ces quatre catgories sont maintenant enregistres en base de donnes, on va pouvoir s'en servir dans nos exemples.

Exemples d'utilisation
V oici un exemple pour ajouter un article existant plusieurs catgories existantes. Je vous propose de mettre ce code dans notre mthode modifierAction() par exemple : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php class BlogController extends Controller { // ... : // Ajout d'un article existant plusieurs catgories existantes public function modifierAction($id) { // On rcupre l'EntityManager $em = $this->getDoctrine() ->getEntityManager(); // On rcupre l'entit correspondant l'id $id $article = $em->getRepository('SdzBlogBundle:Article') ->find($id); if($article === null) { throw $this>createNotFoundException('Article[id='.$id.'] inexistant.'); } // On rcupre toutes les catgories : $liste_categories = $em>getRepository('SdzBlogBundle:Categorie') ->findAll(); // On boucle sur les catgories pour les lier l'article foreach($liste_categories as $categorie) { $article->addCategorie($categorie); } Doctrine // Inutile de persister l'article, on l'a rcupr avec // Etape 2 : On dclenche l'enregistrement $em->flush(); return new Response('OK'); } // ... reste de la mthode

Je vous mets un exemple concret d'application pour que vous puissiez vous reprsenter l'utilisation de la relation dans un vrai cas d'utilisation. Mais sinon les seules lignes qui concernent vraiment l'utilisation de notre relation ManyToMany sont les lignes surlignes en jaune : la boucle sur les catgories pour ajouter chaque catgorie une une l'article en question. V oici un autre exemple pour enlever toutes les catgories d'un article. Modifions la mthode supprimerAction() pour l'occasion : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php class BlogController extends Controller { // ... // Suppression des catgories d'un article : public function supprimerAction($id) { // On rcupre l'EntityManager $em = $this->getDoctrine() ->getEntityManager(); // On rcupre l'entit correspondant l'id $id

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


$article = $em->getRepository('SdzBlogBundle:Article') ->find($id); if($article === null) { throw $this>createNotFoundException('Article[id='.$id.'] inexistant.'); } // On rcupre toutes les catgories : $liste_categories = $em>getRepository('SdzBlogBundle:Categorie') ->findAll(); // On enlve toutes ces catgories de l'article foreach($liste_categories as $categorie) { // On fait appel la mthode removeCategorie() dont on a parl plus haut // Attention ici $categorie est bien une instance de Categorie, et pas seulement un id. $article->removeCategorie($categorie); } // On n'a pas modifi les catgories : inutile de les persister // On a modifier la relation Article - Categorie, // il faudrait persister l'entit propritaire pour persister la relation. // Or l'article a t rcupr depuis Doctrine, inutile de le persister // On dclenche la modification. $em->flush(); } return new Response('OK');

99/201

Encore une fois, je vous ai mis un code complet, mais ce qui nous intresse dans le cadre de la relation ce n'est que les lignes surlignes en jaune. Enfin, voici un dernier exemple pour afficher les catgories d'un article dans la vue : Code : HTML & Django {# src/Sdz/BlogBundle/Resources/views/Blog/voir.html.twig #} {% extends "SdzBlogBundle::layout.html.twig" %} {% block title %} Lecture d'un article - {{ parent() }} {% endblock %} {% block sdzblog_body %} <h2> #} {# On vrifie qu'une image soit bien associe l'article

{% if article.image is not null %} <img src="{{ article.image.url }}" alt="{{ article.image.alt }}" /> {% endif %} {{ article.titre }} </h2> <i>Par {{ article.auteur }}, le {{ article.date|date('d/m/Y') }}</i> {% if article.categories.count > 0 %} - Catgories : {% for categorie in article.categories %} {{ categorie.nom }} {% if not loop.last %} - {% endif %} {% endfor %} {% endif %} <div class="well"> {{ article.contenu }} </div> <p> <a href="{{ path('sdzblog_accueil') }}" class="btn"> <i class="icon-chevron-left"></i> Retour la liste

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


Retour la liste </a> <a href="{{ path('sdzblog_modifier', {'id': article.id}) }}" class="btn"> <i class="icon-edit"></i> Modifier l'article </a> <a href="{{ path('sdzblog_supprimer', {'id': article.id}) }}" class="btn"> <i class="icon-trash"></i> Supprimer l'article </a> </p> {% endblock %}

100/201

V ous voyez qu'on accde aux catgories d'un article grce l'attribut "categories" de l'article, tout simplement. En Twig, cela signifie {{ article.categories }}, en PHP on ferait $article->getCategories(). Il suffit en suite de parcourir ce tableau grce une boucle, et d'en faire ce que l'on veut.

Relation Many-To-Many avec attributs Prsentation


La relation Many-To-Many qu'on vient de voir peut suffire dans bien des cas, mais elle est en fait souvent incomplte pour les besoins d'une application. Pour illustrer ce manque, rien de tel qu'un exemple : considrons l'entit Produit d'un site e-commerce ainsi que l'entit Commande. Une commande contient plusieurs produits, et bien entendu un mme produit peut tre dans diffrentes commandes. On a donc bien une relation Many-To-Many. V oyez-vous le manque ? Lorsqu'un utilisateur ajoute un produit une commande, o met-on la quantit de ce produit qu'il veut ? Si je veux 3 exemplaires de Harry Potter, o mettre cette quantit ? Dans l'entit Commande ? Non cela n'a pas de sens. Dans l'entit Produit ? Non, cela n'a pas de sens non plus. Cette quantit est un attribut de la relation qui existe entre Produit et Commande, et non un attribut de Produit ou de Commande. Il n'y a pas de moyen simple de grer les attributs d'une relation avec Doctrine. Pour cela, il faut esquiver en crant simplement une entit intermdiaire qui va reprsenter la relation, appelons-la CommandeProduit. Et c'est dans cette entit que l'on mettra les attributs de relation, comme notre quantit. Ensuite il faut bien entendu mettre en relation cette entit intermdiaire avec les deux autres entits d'origine, Commande et Produit. Pour cela, il faut logiquement faire : Commande One-To-Many CommandeProduit Many-To-One Produit En effet, une commande (One) peut avoir plusieurs relations avec des produits (Many), plusieurs CommandeProduit donc ! La relation est symtrique pour les produits. Attention, dans le titre de cette sous-partie j'ai parl de la relation " Many-To-Many avec attributs", mais il s'agit bien en fait de deux relations Many-To-One des plus normales, soyons d'accord . On ne va donc rien apprendre dans ce prochain paragraphe, car on sait dj faire une Many-To-One, mais c'est une astuce qu'il faut bien connaitre et savoir utiliser, donc prenons le temps de bien la comprendre. J'ai pris l'exemple de produits et de commandes car c'est plus intuitif pour comprendre l'enjeu et l'utilit de cette relation. Cependant, pour rester dans le cadre de notre blog, on va faire une relation entre des Article et des Comptence, et l'attribut de la relation sera le niveau. L'ide est de pouvoir afficher sur chaque article la liste des comptences utilises (Symfony2, Doctrine2, Formulaire, etc.) avec le niveau dans chaque comptence (Dbutant, Avis et Expert). On a alors l'analogie suivante : Article <=> Commande Article_Competence <=> Commande_Produit Produit <=> Competence Et donc : Article One-To-Many Article_Competence Many-To-One Competence Pour cela, crez d'abord cette entit Comptence, avec au moins un attribut "nom". V oici la mienne : Secret (cliquez pour afficher) Code : PHP <?php // src/Sdz/BlogBundle/Entity/Competence.php namespace Sdz\BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * Sdz\BlogBundle\Entity\Competence * * @ORM\Table() * @ORM\Entity(repositoryClass="Sdz\BlogBundle\Entity\CompetenceRepository") */ class Competence { /** * @var integer $id

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


* @var integer $id * * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @var string $nom * * @ORM\Column(name="nom", type="string", length=255) */ private $nom; /** * Get id * * @return integer */ public function getId() { return $this->id; } /** * Set nom * * @param string $nom * @return Competence */ public function setNom($nom) { $this->nom = $nom; return $this; } /** * Get nom * * @return string */ public function getNom() { return $this->nom; } }

101/201

Dfinition de la relation dans les entits


Annotation
Tout d'abord, on va crer notre entit de relation (notre Article_Competence) comme ceci : Code : PHP <?php // src/Sdz/BlogBundle/Entity/Article_Competence.php namespace Sdz\BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity */ class Article_Competence { /** * @ORM\Id * @ORM\ManyToOne(targetEntity="Sdz\BlogBundle\Entity\Article") */ private $article; /** * @ORM\Id * @ORM\ManyToOne(targetEntity="Sdz\BlogBundle\Entity\Competence") */ private $competence; /** * @ORM\Column() */ private $niveau; // Ici j'ai un attribut de relation "niveau"

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


} // ... Vous pouvez ajouter d'autres attributs bien entendu

102/201

Comme les cts Many des deux relations Many-To-One sont dans Article_Competence, cette entit est l'entit propritaire des deux relations. Et ces @ORM\Id ? Pourquoi il y en a 2 et qu'est-ce qu'ils viennent faire ici ?

Trs bonne question. Comme toute entit, notre Article_Competence se doit d'avoir un identifiant. C'est obligatoire pour que Doctrine puisse la grer par la suite. Depuis le dbut nous avons rajout un attribut id qui tait en auto-incrment, on ne s'en occupait pas trop donc. Ici c'est diffrent, comme une Article_Competence correspond une unique couple Article/Competence (pour chaque couple Article/Competence, il n'y a qu'une seule Article_Competence), on peut se servir de ces deux attributs pour former l'identifiant de cette entit. Pour cela, il suffit de dfinir @ORM\Id sur les deux colonnes, et Doctrine saura mettre une cl primaire sur ces deux colonnes puis les grer comme n'importe quel autre identifiant. Encore une fois, merci Doctrine ! Mais, avec une relation uni-directionnelle, on ne pourra pas faire $article->getArticleCompetence() pour rcuprer les Article_Competence et donc les comptences ? ? Ni l'inverse depuis $competence ? En effet, et c'est pourquoi la prochaine sous-partie de ce chapitre traite des relations bidirectionnelles ! En attendant, pour notre relation One-To-Many-To-One, continuons simplement sur une relation unidirectionnelle. Sachez quand mme que vous pouvez viter une relation bi-directionnelle ici en utilisant simplement la mthode findByCommande($commande->getId()) (pour rcuprer les produits d'une commande) ou findByProduit($produit->getId()) (pour l'inverse) du repository CommandeProduitRepository. L'intrt de la bi-directionnelle ici est lorsque vous voulez afficher une liste des commandes avec leurs produits. Dans la boucles sur les commandes, vous n'allez pas faire appel une mthode du repository qui va gnrer une requte par boucle, il faudra passer par un $commande->getCommandeProduits(). Nous le verrons plus loin dans ce chapitre. N'oubliez pas de mettre jour votre base de donnes en excutant la commande doctrine:schema:update

Getter et setter
Comme d'habitude les getter et setter doivent se dfinir dans l'entit propritaire. Ici, rappelez-vous nous sommes en prsence de deux relations Many-To-One dont la propritaire est l'entit Article_Competence. Nous avons donc 2 getters et 2 setters classiques crire. V ous pouvez les gnrer avec la commande doctrine:generate:entities SdzBlogBundle:Article_Competence , ou les copier-coller : Code : PHP <?php // src/Sdz/BlogBundle/Entity/Article_Competence.php namespace Sdz\BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity */ class Article_Competence { /** * @ORM\Id * @ORM\ManyToOne(targetEntity="Sdz\BlogBundle\Entity\Article") */ private $article; /** * @ORM\Id * @ORM\ManyToOne(targetEntity="Sdz\BlogBundle\Entity\Competence") */ private $competence; /** * @ORM\Column() */ private $niveau; // Ici j'ai un attribut de relation "niveau" // ... les autres attributs // Getter et setter pour l'entit Article public function setArticle(\Sdz\BlogBundle\Entity\Article $article) { $this->article = $article;

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


return $this; } public function getArticle() { return $this->article; } // Getter et setter pour l'entit Competence public function setCompetence(\Sdz\BlogBundle\Entity\Competence $competence) { $this->competence = $competence; return $this; } public function getCompetence() { return $this->competence; } // On dfinit le getter/setter de l'attribut "niveau" public function setNiveau($niveau) { $this->niveau = $niveau; return $this; } public function getNiveau() { return $this->niveau; } } // ... les autres getter/setter si vous en avez

103/201

Remplissons la base de donnes


Comme plus haut, on va d'abord rajouter des comptences en base de donnes grce une commande Symfony2. Pour faire une nouvelle commande, il faut crer un nouveau fichier. Je vous invite crer le fichier src/Sdz/BlogBundle/Command/FixtureCompetencesCommand.php : Secret (cliquez pour afficher) Code : PHP <?php // src/Sdz/BlogBundle/Command/FixtureCompetencesCommand.php namespace Sdz\BlogBundle\Command; use use use use Symfony\Component\Console\Input\InputInterface; Symfony\Component\Console\Output\OutputInterface; Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; Sdz\BlogBundle\Entity\Competence;

class FixtureCompetencesCommand extends ContainerAwareCommand { protected function configure() { $this->setName('sdzblog:fixture:competences'); } protected function execute(InputInterface $input, OutputInterface $output) { // On rcupre l'EntityManager $em = $this->getContainer()>get('doctrine.orm.entity_manager'); // Liste des noms de comptences ajouter $noms = array('Debutant', 'Avise', 'Expert'); foreach($noms as $i => $nom) { $output->writeln('Creation de la competence : '.$nom); // On cre la comptence $liste_competences[$i] = new Competence(); $liste_competences[$i]->setNom($nom); // On la persiste $em->persist($liste_competences[$i]);

$output->writeln('Enregistrement des competences...'); // On dclenche l'neregistrement $em->flush();

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


// On retourne 0 pour dire que la commande s'est bien return 0;

104/201

excute } }

Et maintenant on peut excuter la commande : Code : Console C:\wamp\www\Symfony>php app/console sdzblog:fixture:competences Creation de la competence : Debutant Creation de la competence : Avise Creation de la competence : Expert Enregistrement des competences... C:\wamp\www\Symfony>_

Exemple d'utilisation
La manipulation des entits dans une telle relation est un peu plus compliqu, surtout sans la bidirectionnalit. Mais on peut tout de mme s'en sortir. Tout d'abord, voici un exemple pour crer un nouvel article contenant plusieurs comptences, mettons ce code dans la mthode ajouterAction() : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php // N'oubliez pas ce use videmmenet use Sdz\BlogBundle\Entity\ArticleCompetence; class BlogController extends Controller { // ... public function ajouterAction() { // On rcupre l'EntityManager $em = $this->getDoctrine() ->getEntityManager(); // Cration de l'entit Article $article = new Article(); $article->setTitre('Mon dernier weekend'); $article->setContenu("C'tait vraiment super et on s'est bien amus."); $article->setAuteur('winzou'); // Dans ce cas, on doit crer effectivement l'article en bdd pour lui assigner un id // On doit faire a pour pouvoir enregistrer les ArticleCompetence par la suite $em->persist($article); $em->flush(); // Maintenant, $article a un id dfinit bdd // Les comptences existent dj, on les rcupre depuis la

$liste_competences = $em>getRepository('SdzBlogBundle:Competence') ->findAll(); // Pour l'exemple, notre Article contient toutes les Competences // Pour chaque comptence foreach($liste_competences as $i => $competence) { // On cre une nouvelle "relation entre 1 article et 1 comptence" $articleCompetence[$i] = new ArticleCompetence; // On la lie l'article, qui est ici toujours le mme $articleCompetence[$i]->setArticle($article); // On la lie la comptence, qui change ici dans la boucle foreach $articleCompetence[$i]->setCompetence($competence); // Arbitrairement, on dit que chaque comptence requis au niveau 'Expert' $articleCompetence[$i]->setNiveau('Expert'); // Et bien sr, on persiste cette entit de relation,

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


propritaire des deux autres relations $em->persist($articleCompetence[$i]); } // On dclenche l'enregistrement $em->flush(); } // ... reste de la mthode

105/201

Et un exemple pour rcuprer les comptence et leur niveau partir d'un article donn. Je vous propose de modifier la mthode voirAction() : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php class BlogController extends Controller { // ... public function voirAction($id) { // On rcupre l'EntityManager $em = $this->getDoctrine() ->getEntityManager(); // On rcupre l'entit correspondant l'id $id $article = $em->getRepository('SdzBlogBundle:Article') ->find($id); if($article === null) { throw $this>createNotFoundException('Article[id='.$id.'] inexistant.'); } $article // On rcupre les articleCompetence pour l'article

$liste_articleCompetence = $em>getRepository('SdzBlogBundle:ArticleCompetence') ->findByArticle($article->getId()); // Puis modifiez la ligne du render comme ceci, pour prendre en compte les articleCompetence : return $this->render('SdzBlogBundle:Blog:voir.html.twig', array( 'article' => $article, 'liste_articleCompetence' => $liste_articleCompetence, // ... et videmment les autres variables que vous pouvez avoir )); } }

Et le code de la vue correspondante : Secret (cliquez pour afficher) Code : HTML & Django {# src/Sdz/BlogBundle/Resources/views/Blog/voir.html.twig #} {% extends "SdzBlogBundle::layout.html.twig" %} {% block title %} Lecture d'un article - {{ parent() }} {% endblock %} {% block sdzblog_body %} <h2> #} {# On vrifie qu'une image soit bien associe l'article

{% if article.image is not null %} <img src="{{ article.image.url }}" alt="{{ article.image.alt }}" /> {% endif %} {{ article.titre }} </h2> <i>Par {{ article.auteur }}, le {{ article.date|date('d/m/Y') }}</i>

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


}}</i> {% if article.categories.count > 0 %} - Catgories : {% for categorie in article.categories %} {{ categorie.nom }} {% if not loop.last %} - {% endif %} {% endfor %} {% endif %} <div class="well"> {{ article.contenu }} </div> {% if liste_articleCompetence|length > 0 %} <div> Comptences utilises dans cet article : <ul> {% for articleCompetence in liste_articleCompetence %} <li>{{ articleCompetence.competence.nom }} : {{ articleCompetence.niveau }}</li> {% endfor %} </ul> </div> {% endif %} <a href="{{ path('sdzblog_accueil') }}" class="btn"> <i class="icon-chevron-left"></i> Retour la liste </a> <a href="{{ path('sdzblog_modifier', {'id': article.id}) }}" class="btn"> <i class="icon-edit"></i> Modifier l'article </a> <a href="{{ path('sdzblog_supprimer', {'id': article.id}) }}" class="btn"> <i class="icon-trash"></i> Supprimer l'article </a> </p> {% endblock %} <p>

106/201

C'est un exemple simple bien sr, dans lequel j'ai considr que les entits Commande et Produit avaient un attribut Nom pour les afficher. Dans cet exemple, la mthode findByArticle() utilise dans le contrleur ne slectionne que les ArticleCompetence. Donc, lorsque dans la boucle dans la vue, on fait {{ articleCompetence.competence }}, en ralit Doctrine va effectuer une requte pour rcuprer la Competence associe cette ArticleCompetence. C'est bien sr une horreur, car il va faire une requte... par itration dans le for ! Si vous avez 20 comptences attaches l'article, cela ferait 20 requtes, inimaginable. Pour charger les Competence en mme temps que les ArticleCompetence dans le contrleur, et ainsi ne plus faire de requte dans la boucle, il faut faire une mthode nous dans le repository de ArticleCompetence. On voit tout a dans le chapitre suivant ddi aux repository. N'utilisez donc jamais cette technique, attendez le prochain chapitre ! La seule diffrence dans le contrleur sera d'utiliser une autre mthode que findByArticle(), et la vue ne changera mme pas.

Les relations bidirectionnelles Prsentation


V ous avez vu que jusqu'ici, nous n'avons jamais modifi l'entit inverse d'une relation, mais seulement l'entit propritaire. Toutes les relations que l'on vient de faire sont donc des relations unidirectionnelles. Leur avantage est de dfinir la relation d'une faon trs simple. Mais l'inconvnient est de ne pas pouvoir rcuprer l'entit propritaire depuis l'entit inverse, le fameux <?php $entiteInverse->getEntiteProprietaire() (pour nous, <? php $article->getCommentaires() par exemple). Je dis inconvnient, mais vous avez pu constater que cela ne nous a pas du tout empch de faire ce qu'on voulait ! A chaque fois, on a russi ajouter, lister, modifier nos entits et leurs relations. Mais dans certains cas, avoir une relation bidirectionnelle est bien utile. Nous allons les voir rapidement dans cette sous partie. Sachez que la documentation l'explique galement trs bien : vous pourrez vous renseigner sur le chapitre sur la cration des relations, puis celui sur leur utilisation.

Dfinition de la relation dans les entits


Pour tudier la dfinition d'une relation bidirectionnelle, nous allons tudier une relation ManyToOne. Souvenez-vous bien de cette relation, dans sa version unidirectionnelle, pour pouvoir attaquer sa version bidirectionnelle dans les meilleures conditions. Nous allons ici construire une relation bidirectionnelle de type Many-To-One, base sur notre exemple ArticleCommentaire, mais la mthode est exactement la mme pour les relations de type One-To-One ou Many-To-Many

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


Annotation
Alors, attaquons la gestion d'une relation bidirectionnelle. L'objectif de cette relation est de rendre possible l'accs l'entit propritaire depuis l'entit inverse. Avec une unidirectionnelle cela n'est pas possible car on ne rajoute pas d'attribut dans l'entit inverse, ce qui signifie que l'entit inverse ne sait mme pas qu'elle fait partie d'une relation. La premire tape consiste donc rajouter un attribut, et son annotation, notre entit inverse Article : Code : PHP <?php // src/Sdz/BlogBundle/Entity/Article.php /** * @ORM\Entity */ class Article { /** * @ORM\OneToMany(targetEntity="Sdz\BlogBundle\Entity\Commentaire", mappedBy="article") */ private $commentaires; // Ici commentaires prend un "s", car un article a plusieurs commentaires ! } // ...

107/201

Bien entendu, je vous dois des explications sur ce que l'on vient de faire. Commenons par l'annotation. L'inverse d'un ManyToOne est... un OneToMany, tout simplement ! Il faut donc utiliser l'annotation OneToMany dans l'entit inverse. Je rappelle que le propritaire d'une relation ManyToOne est toujours le ct Many, donc lorsque vous voyez l'annotation ManyToOne, vous tes forcment du ct propritaire. Ici on a un OneToMany, on est bien du ct inverse . Ensuite les paramtres de cette annotation. Le targetEntity est vident, il s'agit toujours de l'entit l'autre bout de la relation, ici notre entit Commentaire. Le mappedBy correspond lui l'attribut de l'entit propritaire (Commentaire) qui pointe vers l'entit inverse (Article) : c'est le "private $article" de l'entit Commentaire. Il faut le renseigner pour que l'entit inverse soit au courant des caractristiques de la relation : celles-ci sont dfinies dans l'annotation de l'entit propritaire . Il faut galement adapter l'entit propritaire, pour lui dire que maintenant la relation est de type bidirectionnelle et non plus unidirectionnelle. Pour cela il faut rajouter le paramtre inversedBy dans l'annotation ManyToOne : Code : PHP <?php // src/Sdz/BlogBundle/Entity/Commentaire.php /** * @ORM\Entity */ class Commentaire { /** * @ORM\ManyToOne(targetEntity="Sdz\BlogBundle\Entity\Article", inversedBy="commentaires") * @ORM\JoinColumn(nullable=false) */ private $article; } // ...

Ici, nous avons seulement rajout le paramtre inversedBy. Il correspond au symtrique du mappedBy, c'est--dire l'attribut de l'entit inverse (Article) qui point evers l'entit propritaire (Commentaire). C'est donc l'attribut "commentaires". Tout est bon ct annotation, maintenant il faut galement rajouter les getter et setter dans l'entit inverse bien entendu.

Getter et setter
Bien entendu, on part d'une relation unidirectionnelle fonctionnelle, donc les getter et setter de l'entit propritaire sont bien dfinis. Dans un premier temps, rajoutons assez logiquement le getter et le setter dans l'entit inverse. On vient de lui rajouter un attribut, il est normal que le getter et le setter aillent de paire. Comme nous sommes du ct One d'un OneToMany, l'attribut "adresses" est un ArrayCollection. C'est donc un addAdresse / removeAdresse / getAdresses qu'il nous faut. Encore une fois, vous pouvez le gnrer avec doctrine:generate:entities SdzBlogBundle:Article , ou alors vous pouvez copier-coller ceux-l :

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


Code : PHP <?php // src/Sdz/BlogBundle/Entity/Article.php /** * @ORM\Entity */ class Article { /** * @ORM\OneToMany(targetEntity="Sdz\BlogBundle\Entity\Commentaire", mappedBy="article") */ private $commentaires; // Ici commentaires prend un "s", car un article a plusieurs commentaires ! // ... vos autres attributs public function __construct() { // Rappelez-vous, on a un attribut qui doit contenir un ArrayCollection, on doit l'initialiser dans le constructeur $this->commentaires = new \Doctrine\Common\Collections\ArrayCollection(); } public function addCommentaire(\Sdz\BlogBundle\Entity\Commentaire $commentaires) { $this->commentaires[] = $commentaires; return $this; } public function removeCommentaire(\Sdz\BlogBundle\Entity\Commentaire $commentaires) { $this->commentaires->removeElement($commentaires); } public function getCommentaires() { return $this->commentaires; } } // ...

108/201

Maintenant, il faut nous rendre compte d'un petit manque. V oici une petite problmatique, lisez bien ce code : Code : PHP <?php // Cration des entits $article = new Article; $commentaire = new Commentaire; // On lie le commentaire l'article $article->addCommentaire($commentaire);

Question : Que retourne $commentaire->getArticle() ?

Rponse : Rien ! En effet pour qu'un $commentaire->getArticle() retourne un article, il faut d'abord le lui dfinir en appelant $commentaire->setArticle($article), c'est logique ! Attention Doctrine2 n'est pas de la magie ! Si vous ne voyez pas pourquoi il n'a pas rempli l'attribut "article" de l'objet $commentaire, il faut revenir aux fondamentaux. V ous tes en train d'crire du PHP, $article et $commentaire sont des objets PHP, tels qu'ils existaient bien avant la naissance de Doctrine. Pour que l'attribut "article" soit dfinit, il faut absolument faire appel au setter setArticle() car c'est le seul qui accde cet attribut (qui est en private). Dans le petit exemple que je vous ai mis, on n'a pas excut de fonction Doctrine : aucun moment il n'a pu intervenir et ventuellement excuter le setArticle()

C'est logique en soi, mais du coup dans notre code cela va tre moins beau : il faut en effet lier le commentaire l'article, et l'article au commentaire. Comme ceci : Code : PHP <?php

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


// Cration des entits $article = new Article; $commentaire = new Commentaire; // On lie le commentaire l'article $article->addCommentaire($commentaire); // On lie l'article au commentaire $commentaire->setArticle($article);

109/201

Mais ces deux mthodes tant intimement lies, on doit en fait les imbriquer. En effet, laisser le code dans l'tat est possible, mais imaginez qu'un jour vous oubliez d'appeler l'une des deux mthodes, et votre code ne sera plus cohrent. Et un code non cohrent est un code qui a des risques de contenir des bugs. La bonne mthode est donc simplement de faire appel l'une des mthodes depuis l'autre. V oici concrtement comme le faire en modifiant les setter dans l'une des deux entits : Code : PHP <?php // src/Sdz/BlogBundle/Entity/Article /** * @ORM\Entity */ class Article { // ... public function addCommentaire(\Sdz\BlogBundle\Entity\Commentaire $commentaires) { $this->commentaires[] = $commentaires; $commentaires->setArticle($this); return $this; } public function removeCommentaire(\Sdz\BlogBundle\Entity\Commentaire $commentaires) { $this->commentaires->removeElement($commentaires); $commentaires->setArticle(null); } } // ...

Notez qu'ici j'ai modifi un ct de la relation (l'inverse en l'occurrence), mais surtout pas les deux ! En effet, si addCommentaire() excute setArticle(), qui excute son tour addCommentaire(), qui... etc. On se retrouve avec une boucle infinie, pas joli. Bref, l'important est de se prendre un ct (propritaire ou inverse n'a pas d'importance), et de l'utiliser. Par utiliser j'entends que dans le reste du code (contrleur, service, etc.), il faudra ici excuter $article->addCommentaire() qui garde la cohrence entre les deux entits. Il ne faudra pas excuter $commentaire->setArticle(), car lui ne garde pas la cohrence ! Retenez : On modifie le setter d'un ct, et on utilise ensuite ce setter l. C'est simple, mais important respecter. Pour matriser les relations que nous venons d'apprendre, il faut vous entraner les crer et les manipuler. N'hsitez donc pas crer des entits d'entrainement, et voir leur comportement dans les relations.

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2

110/201

Rcuprer ses entits avec Doctrine2


L'une des principales fonctions de la couche Modle dans une application MVC, c'est la rcupration des donnes. Rcuprer des donnes n'est pas toujours vident, surtout lorsqu'on veut rcuprer seulement certaines donnes, les classer selon des critres, etc. Tout ceci se fait grce aux repository , que nous tudions dans ce chapitre. Bonne lecture !

Le rle des Repository


On s'est dj rapidement servi de quelques repository, donc vous devriez sentir leur utilit, mais il est temps de thoriser un peu.

Dfinition
Un repository centralise tout ce qui touche la rcupration de vos entits. Concrtement donc, vous ne devez pas faire la moindre requte SQL ailleurs que dans un repository, c'est la rgle. On va donc y construire des mthodes pour rcuprer une entit par son id, pour rcuprer une liste d'entit suivant un critre spcifique, etc. Bref, chaque fois que vous devez rcuprer des entits dans votre base de donnes, vous utiliserez le repository de l'entit correspondante. Rappelez-vous, il existe un repository par entit. Cela permet de bien organiser son code. Bien sr, cela n'empche pas qu'un repository utilise plusieurs types d'entit, dans le cas d'une jointure par exemple. Les repository ne fonctionnent pas par magie, ils utilisent en ralit directement l'EntityManager pour faire leur travail. V ous le verrez, des fois nous ferons directement appel l'EntityManager depuis des mthodes du repository.

Les mthodes de rcupration des entits


Depuis un repository, il existe deux moyens de rcuprer les entits : en utilisant du DQL et en utilisant le QueryBuilder.

Le Doctrine Query Language (DQL)


Le DQL n'est rien d'autre que du SQL adapt la vision par objet que Doctrine utilise. Il s'agit donc de faire ce qu'on a l'habitude de faire, des requtes textuelles comme celle-ci par exemple : Code : SQL SELECT a FROM SdzBlogBundle:Article a

V ous venez de voir votre premire requte DQL. Retenez le principe : avec une requte qui n'est rien d'autre que du texte, on effectue le traitement voulu.

Le QueryBuilder
Le QueryBuilder est un moyen plus nouveau. Comme son nom l'indique, il sert construire une requte, par tape. Si l'intrt n'est pas vident au dbut, son utilisation se rvle vraiment pratique ! V oici la mme requte que prcdemment, mais en utilisant le QueryBuilder : Code : PHP <?php $QueryBuilder ->select('a') ->from('SdzBlogBundle:Article', 'a');

Un des avantages est qu'il est possible de construire la requte en plusieurs fois. Ainsi, vous pouvez dvelopper une mthode qui rajoute une condition une requte, par exemple pour slectionner tous les membres actifs (qui se sont connects depuis moins d'un mois par exemple). Comme cette condition risque de servir souvent, dans plusieurs requtes, avant vous deviez la rcrire chaque fois. Avec le QueryBuilder, vous pourrez faire appel la mme mthode, sans rcrire la condition. Pas de panique on verra des exemples dans la suite du chapitre !

Les mthodes de rcupration de base Dfinition


V os repository hritent de la classe Doctrine\ORM\EntityRepository, qui propose dj quelques mthodes trs utiles pour rcuprer des entits. Ce sont ces mthodes l que nous allons voir ici.

Les mthodes normales


Il existe quatre mthodes, que voici (tous les exemples sont depuis un contrleur) : Mthode Explications Code : PHP <?php $repository = $this->getDoctrine() ->getEntityManager() >getRepository('SdzBlogBundle:Article'); $article_5 = $repository->find(5); // $article 5 est une instance de Sdz\BlogBundle\Entity\Article Exemple

find($id)

La mthode find($id) rcupre tout simplement l'entit correspondant l'id $id. Dans le cas de notre ArticleRepository, elle retourne une instance d'Article.

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2

111/201

Code : PHP <?php $repository = $this->getDoctrine() ->getEntityManager() >getRepository('SdzBlogBundle:Article'); $liste_articles = $repository>findAll(); La mthode findAll() retourne toutes les entits. Elle ne retourne pas un Array mais un ArrayCollection, nous avons vu cette classe dans le chapitre sur les relations. Elle s'utilise comme un tableau et vous pouvez bien sr la parcourir avec un foreach. foreach($liste_articles as $article) { // $article est une instance de Article echo $article->getContenu(); }

findAll()

Ou dans une vue Twig, si l'on a pass la variable $liste_articles au template : Code : HTML <ul> {% for article in liste_articles %} <li>{{ article.contenu }}</li> {% endfor %} </ul>

Code : PHP <?php $repository = $this->getDoctrine() ->getEntityManager() >getRepository('SdzBlogBundle:Article'); La mthode findBy() est un peu plus intressante. Comme findAll() elle permet de retourner une liste d'entit, sauf qu'elle est capable d'effectuer un filtre pour ne retourner que les entits correspondant un critre. Elle peut aussi trier les entits, et mme n'en rcuprer qu'un certain nombre (pour une pagination). $liste_articles = $repository>findBy(array('auteur' => 'winzou'), array('date' => 'desc'), 0); foreach($liste_articles as $article) { // $article est une instance de Article echo $article->getContenu(); } 5,

<?php findBy(array $criteres, array $orderBy = null, $limite = null, $offset = null)

Cet exemple va rcuprer toutes les entits ayant comme auteur winzou en les classant par date dcroissante et en en slectionnant cinq (5) partir du dbut (0). Elle retourne un ArrayCollection galement. V ous pouvez mettre plusieurs entres dans le tableau des critres, afin d'appliquer plusieurs filtres. Code : PHP La mthode findOneBy($criteres) fonctionne sur le mme principe que la mthode findBy(), sauf qu'elle ne retourne qu'une seule entit. Les arguments orderBy, limite et offset n'existe donc pas. <?php $repository = $this->getDoctrine() ->getEntityManager() >getRepository('SdzBlogBundle:Article'); $article = $repository>findOneBy(array('titre' => 'Mon dernier weekend')); // $article est une instance de Article

findOneBy(array $criteres)

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


Ces mthodes permettent de couvrir pas mal de besoin. Mais pour aller plus loin encore, Doctrine nous offre deux autres mthodes magiques.

112/201

Les mthodes magiques


V ous connaissez le principe des mthodes magiques, comme __call() qui mule des mthodes. Ces mthodes mules n'existent pas dans la classe, elle sont prises en charge par __call() qui va excuter du code en fonction du nom de la mthode appele. V oici les deux mthodes gres par __call() dans les repository : Mthode Explications En remplaant X par le nom d'une proprit de votre entit. Dans notre cas, pour l'entit Article, nous avons donc plusieurs mthodes : findByTitre(), findByDate(), findByAuteur(), findByContenu(), etc. findByX($valeur) Cette mthode fonctionne comme findBy(), sauf que vous ne pouvez mettre qu'un seul critre, celui du nom de la mthode. Attention la limite de cette mthode est que vous ne pouvez pas utiliser les arguments pour trier, ni pour mettre une limite. En remplaant X par le nom d'une proprit de votre entit. Dans notre cas, pour l'entit Article, nous avons donc plusieurs mthodes : findOneByTitre(), findOneByDate(), findOneByX($valeur) findOneByAuteur(), findOneByContenu(), etc. Cette mthode fonctionne comme findOneBy(), sauf que vous ne pouvez mettre qu'un seul critre, celui du nom de la mthode. Exemple

Code : PHP <?php $repository = $this->getDoctrine() ->getEntityManager() >getRepository('SdzBlogBundle:Article'); $liste_articles = $repository>findByAuteur('winzou'); // $liste_articles est un ArrayCollection qui contient tous les articles crits par winzou

Code : PHP <?php $repository = $this->getDoctrine() ->getEntityManager() >getRepository('SdzBlogBundle:Article'); $article = $repository>findOneByTitre('Mon dernier weekend'); // $article est une instance d'Article

Toutes ces mthodes permettent de rcuprer vos entits dans la plupart des cas. Simplement, elles montrent rapidement leurs limites lorsqu'on doit faire des jointures, ou effectuer des conditions plus complexes. Pour cela, et cela nous arrivera trs souvent, il faudra faire nos propres mthodes de rcupration.

Les mthodes de rcupration personnelles La thorie


Pour effectuer nos propres mthodes, il faut bien comprendre comment fonctionne Doctrine2 pour effectuer ses requtes. Il faut notamment distinguer 3 types d'objets qui vont nous servir, et qu'il ne faut pas confondre : le QueryBuilder, la Query et les rsultats.

Le QueryBuilder
On l'a dj vu rapidement, le QueryBuilder permet de construire une Query, mais il n'est pas une Query ! Pour rcuprer un QueryBuilder, on peut utiliser simplement l'EntityManager. En effet, il dispose d'une mthode createQueryBuilder() qui nous retournera une instance de QueryBuilder. L'EntityManager est accessible depuis un repository en utilisant l'attribut "_em" d'un repository, soit <?php $this->_em. Le code complet pour rcuprer un QueryBuilder neuf depuis une mthode d'un repository est donc <?php $this->_em->createQueryBuilder(). Cependant cette mthode nous retourne un QueryBuilder vide, c'est--dire sans rien de prdfini. C'est dommage, car lorsqu'on rcupre un QueryBuilder depuis un repository, c'est que l'on veut faire une requte sur l'entit gre par ce repository. Donc si l'on pouvait dfinir la partie " SELECT article FROM SdzBlogBundle:Article" sans trop d'effort, a serait bien pratique car ce qui est intressant c'est le reste de la requte. Heureusement, le repository contient galement une mthode createQueryBuilder($alias) qui utilise la mthode de l'EntityManager, mais en dfinissant pour nous le SELECT et le FROM. V ous pouvez jeter un oeil cette mthode createQueryBuilder() pour comprendre. L'alias en argument de la mthode est le raccourci que l'on donne l'entit du repository. On utilise souvent la premire lettre du nom de l'entit, dans notre exemple de l'Article cela serait donc un "a". Beaucoup de thorie, passons donc la pratique ! Pour bien comprendre la diffrence QueryBuilder / Query, ainsi que la rcupration du QueryBuilder, rien de mieux qu'un exemple. Nous allons recrer la mthode findAll() dans notre repository Article

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


: Code : PHP <?php // src/Sdz/BlogBundle/Entity/ArticleRepository namespace Sdz\BlogBundle\Entity; use Doctrine\ORM\EntityRepository; /** * ArticleRepository * * This class was generated by the Doctrine ORM. Add your own custom * repository methods below. */ class ArticleRepository extends EntityRepository { public function myFindAll() { $queryBuilder = $this->createQueryBuilder('a'); // Mthode quivalente, mais plus longue : $queryBuilder = $this->_em->createQueryBuilder() ->select('a') ->from($this->_entityName, 'a'); // Dans un repository, $this>_entityName est le namespace de l'entit gre // Ici, il vaut donc Sdz\BlogBundle\Entity\Article // On a fini de construire notre requte. // On rcupre la Query partir du QueryBuilder $query = $queryBuilder->getQuery(); // On rcupre les rsultats partir de la Query $resultats = $query->getResult(); // On retourne ces rsultats return $resultats;

113/201

Cette mthode myFindAll() retourne exactement le mme rsultat qu'un findAll(), c'est--dire un ArrayCollection de toutes les entits Article dans notre base de donnes. V ous pouvez le voir, faire une simple requte est trs facile. Pour mieux le visualiser, je vous propose la mme mthode sans les commentaires et en raccourcie : Code : PHP <?php public function myFindAll() { return $this->createQueryBuilder('a') ->getQuery() ->getResult(); }

Simplissime non ? Et bien sr pour rcuprer les rsultats depuis un contrleur, le code est le suivant : Code : PHP <?php public function testAction() { $liste_articles = $this->getDoctrine() ->getEntityManager() ->getRepository('SdzBlogBundle:Article') ->myFindAll(); } // Reste de la mthode du contrleur.

Sauf que pour l'instant on a juste rcupr le QueryBuilder, on n'a pas encore jou avec lui. Il dispose de plusieurs mthodes afin de construire notre requte. Il y a une ou plusieurs mthode(s) par partie de requte : le WHERE, le ORDER BY , le FROM, etc. Elles n'ont rien de compliqu, voyez-le dans les exemples suivants. Commenons par une mthode quivalente au find($id) de base, pour nous permettre de manipuler le where() et le setParameter(). Code : PHP <?php

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


<?php // Dans un repository public function myFindOne($id) { // On passe par le QueryBuilder vide de l'EntityManager pour l'exemple $qb = $this->_em->createQueryBuilder(); $qb->select('a') ->from('SdzBlogBundle:Article', 'a') ->where('a.id = :id') ->setParameter('id', $id); return $qb->getQuery() ->getResult();

114/201

V ous connaissez le principe des paramtres, qui est le mme qu'avec PDO. On dfinit un paramtre dans la requte avec ":nom_du_parametre", puis on attribut une valeur ce paramtre avec la mthode setParameter('nom_du_parametre', $valeur). V oici un autre exemple pour utiliser le andWhere() ainsi que le orderBy(). Crons une mthode pour rcuprer tous les articles crits par un auteur avant une anne donne : Code : PHP <?php // Depuis un repository public function findByAuteurAndDate($auteur, $annee) { // On utilise le QueryBuilder cr par le repository directement pour gagner du temps // Plus besoin de faire le select() ni le from() par la suite donc $qb = $this->createQueryBuilder('a'); $qb->where('a.auteur = :auteur') ->setParameter('auteur', $auteur) ->andWhere('a.date < :annee') ->setParameter('annee', $annee) ->orderBy('a.date', 'DESC'); return $qb->getQuery() ->getResult();

Maintenant voyons un des avantages du QueryBuilder. V ous vous en souvenez, je vous avais parl d'une mthode pour centraliser une condition par exemple. V oyons donc une application de ce principe, en considrant que la condition " articles posts durant l'anne en cours" est une condition dont on va se resservir souvent. Il faut donc en faire une mthode, que voici : Code : PHP <?php // Depuis un repository public function whereCurrentYear(Doctrine\ORM\QueryBuilder $qb) { $qb->andWhere('a.date BETWEEN :debut AND :fin') ->setParameter('debut', new \Datetime(date('Y').'-01-01')) // Date entre le 1er janvier de cette anne ->setParameter('fin', new \Datetime(date('Y').'-12-31')); // Et le 31 dcembre de cette anne } return $qb;

V ous notez donc que cette mthode ne traite pas une Query, mais bien uniquement le QueryBuilder. C'est en a qu'il est trs pratique, car faire cette mthode sur une requte en texte simple est possible, mais compliqu. Il aurait fallu voir si le "WHERE" tait dj prsent dans la requte, si oui mettre un "AND" au bon endroit, etc. Bref, pas simple. Pour utiliser cette mthode, voici la dmarche : Code : PHP <?php // Depuis un repository public function myFind() { $qb = $this->createQueryBuilder('a'); // On peut rajouter ce qu'on veut avant $qb->where('a.auteur = :auteur') ->setParameter('auteur', 'winzou');

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


// On applique notre condition $qb = $this->whereCurrentYear($qb); // On peut rajouter ce qu'on veut aprs $qb->orderBy('a.date', 'DESC'); return $qb->getQuery() ->getResult();

115/201

V oil, vous pouvez dornavant appliquer cette condition n'importe laquelle de vos requtes en construction. Je ne vous ai pas list toutes les mthodes du QueryBuilder, il en existe bien d'autres. Pour cela, vous devez absolument mettre la page suivante dans vos favoris : http://www.doctrine-project.org/docs/o [...] -builder.html Ouvrez-la et gardez-la sous la main chaque fois que vous voulez faire une requte l'aide du QueryBuilder, c'est la rfrence !

La Query
V ous l'avez vu, la Query est l'objet partir duquel on extrait les rsultats. Il n'y a pas grand chose savoir sur cet objet en luimme, car il ne permet pas grand chose part rcuprer les rsultats. Il sert en fait surtout la gestion du cache des requtes. Un prochain chapitre est venir sur ce cache de requtes. Mais dtaillons tout de mme les diffrentes faon d'extraire les rsultats de la requtes. Ces diffrentes manires sont toutes matriser, car elles concernent chacune un type de requte. Mthode Explications Exemple Code : PHP <?php $entites = $qb>getQuery()>getResult(); foreach($entites as $entite) { // $entite est une instance d'Article pour notre exemple $entite>getAttribut(); }

getResult()

Excute la requte et retourne un tableau contenant les rsultats sous forme d'objet. V ous rcuprez ainsi une liste des objets, sur lequels vous pouvez faire des oprations, des modifications, etc. Mme si la requte ne retourne qu'un seul rsultat, cette mthode retourne un tableau.

Code : PHP <?php $entites = $qb>getQuery()>getArrayResult(); Excute la requte et retourne un tableau contenant les rsultats sous forme de tableau. Comme avec getResult(), vous rcuprez un tableau mme s'il n'y a qu'un seul rsultat. getArrayResult() Mais dans ce tableau, vous n'avez pas vos objets d'origine, vous avez des simples tableaux. Cette mthode est utilise lorsque vous ne voulez que lire vos rsultats, sans y apporter de modification. Elle est dans ce cas plus rapide que son homologue getResult(). foreach($entites as $entite) { // $entite est un tableau // Faire $entite>getAttribut() est impossible. Vous devez faire : $entite['attribut']; }

Heureusement Twig est intelligent : {{ entite.attribut }} excute $entite->getAttribut() si $entite est un objet, et excute $entite['attribut'] sinon. Du point de vue de Twig, vous pouvez utiliser getResult() ou getArrayResult() indiffremment. Code : PHP <?php $entites = $qb>getQuery()-

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


Excute la requte et retourne un tableau contenant les rsultats sous forme de valeur. Comme avec getResult(), vous rcuprez un tableau mme s'il n'y a qu'un seul rsultat. getScalarResult() Mais dans ce tableau, un rsultat est une valeur, non un tableau de valeur (getArrayResult) ou un objet de valeur (getResult). Cette mthode est donc utilise lorsque vous ne slectionnez qu'une seule valeur dans la requte, par exemple : SELECT COUNT(*) FROM .... Ici, la valeur est la valeur du COUNT. >getScalarResult(); foreach($entites as $valeur) { // $valeur est la valeur de ce qui a t slectionn : un nombre, un texte, etc. $valeur; // Faire $valeur>getAttribut() ou $valeur['attribut'] est impossible. }

116/201

Code : PHP <?php $entite = $qb>getQuery()>getOneOrNullResult(); // $entite est une instance d'Article dans notre exemple // ou null si la requte ne contient pas de rsultat // Et une exception a t dclenche si plus d'un rsultat

Excute la requte et retourne un seul rsultat, ou null si pas de rsultat. Cette mthode retourne donc une instance de l'entit (ou null) et non un tableau d'entits comme getResult(). getOneOrNullResult() Cette mthode dclenche une exception Doctrine\ORM\NonUniqueResultException si la requte retourne plus d'un seul rsultat. Il faut donc l'utiliser si l'une de vos requtes n'est pas cense retourner plus d'un rsultat : dclencher une erreur plutt que de laisser courir permet d'anticiper des futurs bugs !

Code : PHP <?php $entite = $qb>getQuery()>getSingleResult(); // $entite est une instance d'Article dans notre exemple // Une exception a t dclenche si plus d'un rsultat // Une exception a t dclenche si pas de rsultat

Excute la requte et retourne un seul rsultat. Cette mthode est exactement la mme que getOneOrNullResult(), sauf qu'elle dclenche une exception Doctrine\ORM\NoResultException si aucun rsultat. C'est une mthode trs utilise, car faire des requtes qui ne retourne qu'un unique rsultat est trs frquent.

getSingleResult()

Code : PHP <?php $valeur = $qb>getQuery()>getSingleScalarResult(); // $valeur est directement la valeur du COUNT dans la requte exemple. // Une exception a t dclenche si plus d'un rsultat // Une exception a t dclenche si pas de rsultat

Excute la requte et retourne une seule valeur, et dclenche des exception si pas de rsultat ou plus d'un rsultat. getSingleScalarResult() Cette mthode est trs utilise galement pour des requtes du type SELECT COUNT(*) FROM Article, qui ne retourne qu'une seule ligne de rsutlat, et une seule valeur dans cette ligne.

Code : PHP <?php // Excute un UPDATE

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


par exemple : $qb->getQuery()>execute(); // Voici deux mthodes strictement quivalentes : $resultats = $query>getArrayResult(); // Et : $resultats = $query>execute(array(), Query::HYDRATE_ARRAY); // Le premier argument de execute() est un tableau de paramtres. // Vous pouvez aussi passer par la mthode setParameter(), au choix. // Le deuxime argument de execute() est ladite mthode d'hydratation.

117/201

Excute la requte. Cette mthode est utilise principalement pour excuter des requtes qui ne retournent pas de rsultats (des UPDATE, INSERT INTO, etc.). Cependant, toutes les autres mthodes que nous venons de voir ne sont en fait que des raccourcis vers cette mthode execute(), en changeant juste le mode d'hydratation des rsultats (objet, tableau, etc.).

execute()

Pensez donc bien choisir votre faon de rcuprer les rsultats chacune de vos requtes.

Utilisation du Doctrine Query Language (DQL)


Le DQL est une sorte de SQL adapt l'ORM Doctrine2. Il permet de faire des requtes un peu l'ancienne, en crivant une requte en chane de caractres (en opposition au QueryBuilder). Pour crire une requte en DQL, il faut donc oublier le QueryBuilder, on utilisera seulement l'objet Query. Et la mthode pour rcuprer les rsultats sera la mme. Le DQL n'a rien de compliqu, et il est trs bien document.

La thorie
Pour crer une requte en utilisant du DQL, il faut utiliser la mthode createQuery() de l'EntityManager : Code : PHP <?php // Depuis un repository public function myFindAllDQL() { $query = $this->_em->createQuery('SELECT a FROM SdzBlogBundle:Article a'); $resultats = $query->getResult(); } return $resultats;

Regardons de plus prs la requte DQL en elle-mme : Code : SQL SELECT a FROM SdzBlogBundle:Article a

Tout d'abord, vous voyez que l'on utilise pas de table. On a dit qu'on pensait objet et non plus base de donnes ! Il faut donc utiliser dans les FROM et les JOIN le nom des entits. Soit en utilisant le nom raccourci comme on l'a fait, soit le namespace complet de l'entit. De plus, il faut toujours donner un alias l'entit, ici on a mis "a". On met souvent la premire lettre de l'entit, mme si ce n'est absolument pas obligatoire. Ensuite, vous imaginez bien qu'il ne faut pas slectionner un un les attributs de nos entits, cela n'aurait pas de sens. Une entit Article avec le titre renseign mais pas la date ? Ce n'est pas logique. C'est pourquoi on slectionne simplement l'alias, ici "a", ce qui slectionne en fait tous les attributs d'un Article. L'quivalent d'une toile (*) en SQL donc. Sachez qu'il est tout de mme possible de ne slectionner qu'une partie d'un objet, en faisant a.titre par exemple. Mais vous ne recevez alors qu'un tableau contenant les attributs slectionns, et non un objet. V ous ne pouvez donc pas modifier/supprimer/etc l'objet, puisque c'est un tableau. Cela sert dans des requtes particulires, mais la plupart du temps on slectionnera bien tout l'objet.

Faire des requtes en DQL n'a donc rien de compliqu. Lorsque vous les faites, gardez bien sous la main la page de la documentation sur le DQL pour en connaitre la syntaxe. En attendant, je peux vous montrer quelques exemples afin que vous ayez une ide globale du DQL.

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


Pour tester rapidement vos requtes DQL, sans avoir les implmenter dans une mthode de votre repository, Doctrine2 nous simplifie la vie grce la commande doctrine:query:dql . Cela vous permet de faire quelques tests afin de construire ou de vrifier vos requtes, utiliser sans modration donc ! Je vous invite ds maintenant excuter la commande suivante : Code : Console php app/console doctrine:query:dql "SELECT a FROM SdzBlogBundle:Article a"

118/201

Exemples
Pour faire une jointure : Code : SQL SELECT a, u FROM Article a JOIN a.utilisateur u WHERE u.age = 25

Pour utiliser une fonction SQL : Code : SQL SELECT a FROM Article a WHERE TRIM(a.auteur) = 'winzou'

Pour slectionner seulement un attribut (attention les rsultats seront donc sous forme de tableaux et non d'objets) : Code : SQL SELECT a.titre FROM Article a WHERE a.id IN(1, 3, 5)

Et bien sr vous pouvez galement utiliser des paramtres : Code : PHP <?php public function myFindDQL($id) { $query = $this->_em->createQuery('SELECT a FROM Article a WHERE a.id = :id'); $query->setParameter('id', $id); return $query->getSingleResult(); // Utilisation de getSingleResult car la requte ne doit retourner qu'un seul rsultat }

Utiliser les jointures dans nos requtes Pourquoi utiliser les jointures ?
Je vous en ai dj parl dans le chapitre prcdent sur les relations entre entits. Lorsque vous utilisez la syntaxe <?php $entiteA->getEntiteB(), Doctrine excute une requte afin de charger les entits B qui sont lies l'entit A. L'objectif est donc d'avoir la matrise sur quand charger juste l'entit A, et quand charger l'entit A avec ses entits B lies. Nous avons dj vu le premier cas, par exemple un $repositoryA->find($id) ne rcupre qu'une seule entit A sans rcuprer les entits lies. Maintenant, voyons comment raliser le deuxime cas, c'est--dire rcuprer tout d'un coup avec une jointure, pour viter une seconde requte par la suite. Tout d'abord, rappelons le cas d'utilisation principal de ces jointures. C'est surtout lorsque vous bouclez sur une liste d'entits A (par exemple des articles), et que dans cette boucle vous faites $entiteA->getEntiteB() (par exemple des commentaires). Avec 1 requte par itration dans la boucle, vous explosez votre nombre de requtes sur une seule page ! C'est donc principalement pour viter cela que nous allons faire des jointures.

Comment faire des jointures avec le QueryBuilder ?


Heureusement, c'est trs simple ! V oici tout de suite un exemple : Code : PHP <?php // Depuis le repository d'Article public function getArticleAvecCommentaires() { $qb = $this->createQueryBuilder('a') ->leftJoin('a.commentaires', 'c') ->addSelect('c'); return $qb->getQuery() ->getResult();

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


L'ide est donc trs simple : D'abord on cre une jointure avec la mthode leftJoin() (ou join() pour faire l'quivalent d'un INNER JOIN). Le premier argument de la mthode est l'attribut de l'entit principale (celle qui est dans le FROM de la requte) sur lequel faire la jointure. Dans l'exemple, l'entit Article possde un attribut "commentaires". Le deuxime argument de la mthode est l'alias de l'entit jointe. Puis on slectionne galement l'entit jointe, via un addSelect(). En effet un select() tout court aurait cras le select('a') dj fait par le createQueryBuilder(), rappelez-vous. Attention : On ne peut faire une jointure que si l'entit du FROM possde un attribut vers l'entit joindre ! Cela veut dire que soit l'entit du FROM est l'entit propritaire de la relation, soit la relation est bidirectionnelle. Dans notre exemple, la relation entre Article et Commentaire est une ManyToOne avec Commentaire le ct Many, le ct propritaire donc. Cela veut dire que pour pouvoir faire la jointure dans ce sens, la relation est bidirectionnelle, afin d'ajouter un attribut "commentaires" dans l'entit inverse Article.

119/201

Et pourquoi on n'a pas prcis la condition ON du JOIN ?

C'est une bonne question. La rponse est trs logique, pour cela rflchissez plutt la question suivante : pourquoi est-ce qu'on rajoute un ON habituellement dans nos requtes SQL ? C'est pour que MySQL (ou tout autre SGBDR) puisse savoir sur quelle condition faire la jointure. Or ici, on s'adresse Doctrine et non directement MySQL. Et bien entendu, Doctrine connait dj tout sur notre association, grce aux annotations ! Bien sr, vous pouvez toujours personnaliser la condition de jointure, en rajoutant vos conditions la suite du ON gnr par Doctrine, grce la syntaxe du WITH : Code : PHP <?php $qb->join('a.commentaires', 'c', 'WITH', 'YEAR(c.date) > 2011')

Le 3e argument est le type de condition WITH, et le 4e argument est ladite condition. WITH ? C'est quoi cette syntaxe pour faire une jointure ?

En SQL, la diffrence entre le ON et le WITH est simple : un ON dfinit la condition pour la jointure, alors qu'un WITH ajoute une condition pour la jointure. Attention, en DQL le ON n'existe pas, seul le WITH est support. Ainsi, la syntaxe prcdente avec le WITH serait quivalente la syntaxe SQL suivante base de ON : Code : SQL SELECT * FROM Article a JOIN Commentaire c ON c.article = a.id AND YEAR(c.date) > 2011

Grce au WITH, on n'a pas besoin de rcrire la condition par dfaut de la jointure, le " c.article = a.id".

Comment utiliser les jointures ?


Rponse : comme d'habitude ! V ous n'avez rien modifier dans votre code. Si vous utilisez une entit dont vous avez rcupr les entits lies avec une jointure, vous pouvez alors utiliser les getter joyeusement sans craindre de requte supplmentaire. Reprenons l'exemple de la mthode getArticleAvecCommentaires() dfinie un peu plus haut, on pourrait utiliser les rsultats comme ceci : Code : PHP <?php // Depuis un contrleur public function listeAction() { $liste_articles = $this->getDoctrine() ->getEntityManager() ->getRepository('SdzBlogBundle:Article') ->getArticleAvecCommentaires(); foreach($liste_articles as $article) { $article->getCommentaires(); // Ne dclenche pas de requte : les commentaires sont dj chargs ! // Vous pourriez faire une boucle dessus pour les afficher tous. } } // ...

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2

120/201

V oici donc comment vous devrez faire la plupart de vos requtes. En effet, vous aurez souvent besoin d'utiliser des entits lies entre elles, et faire une ou plusieurs jointures s'impose trs souvent .

Application : les entits de notre blog Plan d'attaque


On a dj un peu trait l'entit Article au dbut de cette partie. J'avais volontairement omis l'attribut auteur , qui est le pseudo de celui qui a crit l'article. Souvenez-vous, pour commencer, on n'a pas d'entit Utilisateur, on doit donc crire le pseudo de l'auteur en dur dans les articles. Ajoutez donc ( la main, vous ne pouvez plus utiliser le gnrateur) les attributs auteur , de type string , sans oublier le getter et le setter. Il manque galement l'entit Tag. On va la faire trs simple : un tag n'aura qu'un attribut utile, son nom. Crez l'entit indpendamment de la relation qu'elle aura avec Article, vous pouvez le faire avec le gnrateur. Maintenant, tablissez la relation entre Article et Tag. Je vous laisse rflchir pour identifier le type de relation, et pour dterminer qui en est le propritaire. Dfinissez la proprit de relation et le getter/setter qui va bien avec. N'oubliez pas de mettre jour la base de donnes avec les commandes php app/console doctrine:schema:update --dump-sql puis php app/console doctrine:schema:update --force . Puis, allez remplir des donnes dans la table avec phpMyAdmin, le contrleur ArticleController gnr ne pouvant pas grer de quoi ajouter des tags un article. Rappelez-vous, il n'est qu'une base pour notre code, pas la solution toute faite. Enfin, ajoutez une mthode dans l'ArticleRepository pour rcuprer tous les articles qui correspondent une liste de tags. La dfinition de la mthode est donc <?php getAvecTags(array $nom_tags) ?>, que l'on pourra utiliser comme cela par exemple : <?php $articleRepository->getAvecTags(array('sdz', 'weekend')) ?>.

vous de jouer !
Important : faites-le vous-mme ! La correction est juste en dessous, je sais, mais si vous ne faites pas maintenant l'effort d'y rflchir par vous-mme, cela vous handicapera par la suite !

Le code
Article.php : Code : PHP <?php // src/Sdz/BlogBundle/Entity/Article.php namespace Sdz\BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Doctrine\Common\Collections\ArrayCollection; use Sdz\BlogBundle\Entity\Tag; /** * Sdz\BlogBundle\Entity\Article * * @ORM\Table() * @ORM\Entity(repositoryClass="Sdz\BlogBundle\Entity\ArticleRepository") */ class Article { /** * @var integer $id * * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @var Datetime $date * * @ORM\Column(name="date", type="date") */ private $date; /** * @var string $titre * * @ORM\Column(name="titre", type="string", length=255) */ private $titre;

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


/** * @var text $contenu * * @ORM\Column(name="contenu", type="text") */ private $contenu; /** * @var string $auteur * * @ORM\Column(name="auteur", type="string", length=255) */ private $auteur; /** * @ORM\ManyToMany(targetEntity="Sdz\BlogBundle\Entity\Tag") */ private $tags; public function __construct() { $this->tags = new ArrayCollection(); } /** * Get id * * @return integer */ public function getId() { return $this->id; } /** * Set date * * @param \Datetime $date */ public function setDate(\Datetime $date) { $this->date = $date; } /** * Get date * * @return \Datetime */ public function getDate() { return $this->date; } /** * Set titre * * @param string $titre */ public function setTitre($titre) { $this->titre = $titre; } /** * Get titre * * @return string */ public function getTitre() { return $this->titre; } /** * Set contenu * * @param text $contenu */ public function setContenu($contenu) { $this->contenu = $contenu; } /** * Get contenu * * @return text */

121/201

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


public function getContenu() { return $this->contenu; } /** * Set auteur * * @param string $auteur */ public function setAuteur($auteur) { $this->auteur = $auteur; } /** * Get auteur * * @return string */ public function getAuteur() { return $this->auteur; } /** * Add tag * * @param Tag $tag */ public function addTag(Tag $tag) { $this->tags[] = $tag; } /** * Get tags * * @return ArrayCollection */ public function getTags() { return $this->tags; } }

122/201

Notez que j'ai forc la variable date tre une instance de \Datetime. En effet, c'est ce que Doctrine utilise, c'est ce qu'il va nous retourner lorsque l'on fait des requtes. Donc autant travailler avec un objet \Datetime partout dans notre code, et oublier les chaines de caractres <?php 'dd/mm/YY' ?>, a fait tellement annes 90 ! Et n'ayez crainte, depuis un template Twig, vous pourrez faire {{ article.date|date('dd/mm/YY') }} pour transformer votre \Datetime en quelque chose de lisible pour vos visiteurs. Pour plus d'informations sur la classe Datetime de PHP, je vous invite lire sa documentation. Tag.php : Code : PHP <?php // src/Sdz/BlogBundle/Entity/Tag.php namespace Sdz\BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * Sdz\BlogBundle\Entity\Tag * * @ORM\Table() * @ORM\Entity(repositoryClass="Sdz\BlogBundle\Entity\TagRepository") */ class Tag { /** * @var integer $id * * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @var string $nom * * @ORM\Column(name="nom", type="string", length=255)

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


*/ private $nom;

123/201

/** * Get id * * @return integer */ public function getId() { return $this->id; } /** * Set nom * * @param string $nom */ public function setNom($nom) { $this->nom = $nom; } /** * Get nom * * @return string */ public function getNom() { return $this->nom; } }

ArticleRepository.php : Code : PHP <?php // src/Sdz/BlogBundle/Entity/ArticleRepository.php namespace Sdz\BlogBundle\Entity; use Doctrine\ORM\EntityRepository; /** * ArticleRepository * * This class was generated by the Doctrine ORM. Add your own custom * repository methods below. */ class ArticleRepository extends EntityRepository { public function getAvecTags(array $nom_tags) { $qb = $this->createQueryBuilder('a'); // On fait une jointure sur la table des tags, avec pour alias t . $qb ->join('a.tags', 't') ->where($qb->expr()->in('t.nom', $nom_tags)); // Puis on filtre sur le nom des tags. // Enfin, on retourne le rsultat. return $qb->getQuery() ->getResult();

Que faire avec ce que retourne cette fonction ?

Comme je l'ai dit plus haut, cette fonction va retourner un ArrayCollection d'Article. Qu'est-ce que l'on veut en faire ? Les afficher. Donc la premire chose faire est de passer cet ArrayCollection Twig. Ensuite, dans Twig, vous faites un simple {% for %} pour afficher les articles. En fait, c'est simple, regardez comment fonctionne la mthode indexAction() du contrleur Article gnr, puis son template en Article:index.html.twig. a n'est vraiment pas compliqu utiliser ! Et voil, vous avez tout le code. Je n'ai qu'une chose vous dire ce stade du tutoriel : entranez-vous ! Amusez-vous faire des requtes dans tous les sens dans l'ArticleRepository ou mme dans TagRepository. Jouez avec la relation entre les deux entits, crez-en d'autres. Bref, a ne viendra pas tout seul, il va falloir travailler un peu de votre ct.

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


Ce chapitre clture la partie sur Doctrine2, le prochain tant un chapitre TP pour appliquer ce que vous avez appris. V ous savez maintenant parfaitement grer vos donnes grce un ORM, et pour cela, toutes mes flicitations ! Pour avoir sous les yeux tout ce qu'il faut savoir sur Doctrine2, je vous propose de tlcharger la cheatsheet de Elao, une agence web qui fait du Symfony2. C'est un PDF trs pratique qui rsume en 2 pages tout ce qu'il y a savoir sur Doctrine2, vous pouvez le trouver ici : http://www.elao.org/wp-content/uploads [...] sheet-all.pdf

124/201

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2

125/201

TP : Les entits de notre blog


L'objectif de ce chapitre est de mettre en application tout ce que nous avons vu au cours de cette partie sur Doctrine2. Nous allons crer les entits Article et Blog, mais galement adapter le contrleur pour nous en servir. Enfin, nous verrons quelques astuces de dveloppement Symfony2 au cours du TP. Surtout, je vous invite bien essayer de rflchir par vous mme avant de lire les codes que je donne. C'est ce mcanisme de recherche qui va vous faire progresser sur Symfony2, il serait dommage de s'en passer ! Bon TP !

Cration des entits Thorie


Entit Article
On a dj pas mal trait l'entit Article au dbut de cette partie. J'avais volontairement omis l'attribut auteur , qui est le pseudo de celui qui a crit l'article. Je vous invite donc ajouter cet attribut comme il se doit. Souvenez-vous, pour commencer, on n'a pas d'entit Utilisateur, on doit donc crire le pseudo de l'auteur en dur dans les articles. L'autre point que vous devez ajouter est la relation avec l'entit Tag. C'est une relation ManyToMany, je vous laisse donc adapter l'entit Article (qui sera la propritaire de la relation) pour la prendre en compte.

Entit Tag
On n'a pas encore fait l'entit Tag, mais elle est trs simple. On va la construire avec un seul attribut : son nom. Crez donc l'entit indpendamment de la relation qu'elle aura avec Article.

Pratique
Entit Article
Pour faire les changements ncessaires l'entit, nous allons procder comme cela : D'abord on ajoute les attributs dans la classe, ainsi que leur annotation bien entendu ; Puis on excute la commande php app/console doctrine:generate:entities SdzBlogBundle , qui permet de gnrer les getters et setters des attributs que l'on vient de rajouter, et ce pour toutes les entits du bundle prcis en argument (pour l'instant il n'y a que Article en effet ). Facile ! V ous pouvez remarquer que le gnrateur a fait une sauvegarde de l'entit Article, qu'il a plac dans le fichier Article.php~. En effet, bien qu'il ne rcrit pas les getters et setters dj existants, le gnrateur joue la scurit et cre une copie de sauvegarde avant de modifier notre entit. Un bon rflexe de sa part, et donc de notre ct vrifiez toujours rapidement le contenu du fichier modifi pour valider que le gnrateur n'a rien cass. V oici donc le rsultat obtenu : Code : PHP <?php // src/Sdz/BlogBundle/Entity/Article.php namespace Sdz\BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * Sdz\BlogBundle\Entity\Article * * @ORM\Table() * @ORM\Entity(repositoryClass="Sdz\BlogBundle\Entity\ArticleRepository") */ class Article { /** * @var integer $id * * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @var datetime $date * * @ORM\Column(name="date", type="datetime") */ private $date; /** * @var string $titre *

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


* @ORM\Column(name="titre", type="string", length=255) */ private $titre; /** * @var text $contenu * * @ORM\Column(name="contenu", type="text") */ private $contenu; /** * @var string $auteur * * @ORM\Column(name="auteur", type="string", length=255) */ private $auteur; /** * @ORM\ManyToMany(targetEntity="Sdz\BlogBundle\Entity\Tag") */ private $tags; public function __construct() { $this->tags = new \Doctrine\Common\Collections\ArrayCollection(); } /** * Get id * * @return integer */ public function getId() { return $this->id; } /** * Set date * * @param datetime $date */ public function setDate($date) { $this->date = $date; } /** * Get date * * @return datetime */ public function getDate() { return $this->date; } /** * Set titre * * @param string $titre */ public function setTitre($titre) { $this->titre = $titre; } /** * Get titre * * @return string */ public function getTitre() { return $this->titre; } /** * Set contenu * * @param text $contenu */ public function setContenu($contenu) { $this->contenu = $contenu; }

126/201

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


/** * Get contenu * * @return text */ public function getContenu() { return $this->contenu; } /** * Set auteur * * @param string $auteur */ public function setAuteur($auteur) { $this->auteur = $auteur; } /** * Get auteur * * @return string */ public function getAuteur() { return $this->auteur; } /** * Add tags * * @param Sdz\BlogBundle\Entity\Tag $tags */ public function addTag(\Sdz\BlogBundle\Entity\Tag $tags) { $this->tags[] = $tags; } /** * Get tags * * @return Doctrine\Common\Collections\Collection */ public function getTags() { return $this->tags; } }

127/201

Entit Tag
Pour l'entit Tag c'est encore plus simple, on peut passer ds le dbut par le gnrateur, grce la commande php app/console doctrine:generate:entity . On a dj vu le fonctionnement de la commande lors de la cration de l'entit Article, je ne le dtaille donc pas nouveau. V oici le rsultat : Code : PHP <?php // src/Sdz/BlogBundle/Entity/Tag.php namespace Sdz\BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * Sdz\BlogBundle\Entity\Tag * * @ORM\Table() * @ORM\Entity(repositoryClass="Sdz\BlogBundle\Entity\TagRepository") */ class Tag { /** * @var integer $id * * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /**

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


* @var string $nom * * @ORM\Column(name="nom", type="string", length=255) */ private $nom; /** * Get id * * @return integer */ public function getId() { return $this->id; } /** * Set nom * * @param string $nom */ public function setNom($nom) { $this->nom = $nom; } /** * Get nom * * @return string */ public function getNom() { return $this->nom; } }

128/201

Et bien sr...
... N'oubliez pas de mettre jour votre base de donnes ! Vrifiez les requtes avec php app/console doctrine:schema:update --dump-sql puis excutez les avec --force .

Adaptation du contrleur Thorie

Maintenant que l'on a nos entits, on va enfin pouvoir adapter notre contrleur Blog pour qu'il rcupre et modifie des vrais articles dans la base de donnes, et non plus nos articles statiques dfinis la va-vite. Pour cela, il y a trs peu de modifications raliser : voici encore un exemple du code dcoupl que Symfony2 nous permet de raliser ! En effet, il vous suffit de modifier les 4 endroits o on avait crit un article en dur dans le contrleur. Modifiez ces 4 endroits en utilisant bien le repository de l'entit Article, seules les mthodes findAll() et find() vont nous servir pour le moment. Attention je vous demande galement de faire attention au cas o l'article demand n'existe pas. Si on essaie d'aller la page /blog/article/4 alors que l'article d'id 4 n'existe pas, je veux une erreur correctement gre ! On a dj vu le dclenchement d'une erreur 404 lorsque le paramtre page de la page d'accueil n'tait pas valide, reprenez ce comportement. A la fin le contrleur ne sera pas entirement oprationnel, car il nous manque toujours la gestion des formulaires. Mais il sera dj mieux avanc !

Pratique
Il n'y a vraiment rien de compliqu dans notre nouveau contrleur, le voici : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class BlogController extends Controller { public function indexAction($page) { // On ne sait pas combien de pages il y a, mais on sait qu'une page // doit tre suprieure ou gale 1. if( $page < 1 )

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


// On dclenche une exception NotFoundHttpException, cela va afficher // la page d'erreur 404 (on pourra personnaliser cette page plus tard, d'ailleurs). throw $this->createNotFoundException('Page inexistante (page = '.$page.')'); } // Pour rcuprer la liste de tous les articles : on utilise findAll() $articles = $this->getDoctrine() ->getEntityManager() ->getRepository('SdzBlogBundle:Article') ->findAll(); // L'appel de la vue ne change pas return $this->render('SdzBlogBundle:Blog:index.html.twig', )); 'articles' => $articles {

129/201

array( }

public function voirAction($id) { // Pour rcuprer un article unique : on utilise find() $article = $this->getDoctrine() ->getEntityManager() ->getRepository('SdzBlogBundle:Article') ->find($id); // Si l'article n'existe pas, on affiche une erreur 404 if( $article == null ) { throw $this>createNotFoundException('Article[id='.$id.'] inexistant'); } array( } return $this->render('SdzBlogBundle:Blog:voir.html.twig', )); 'article' => $article

public function ajouterAction() { // La gestion d'un formulaire est particulire, mais l'ide est la suivante : if( $this->get('request')->getMethod() == 'POST' ) { // Ici, on s'occupera de la cration et de la gestion du formulaire. $this->get('session')->setFlash('info', 'Article bien enregistr'); cet article. // Puis on redirige vers la page de visualisation de

return $this->redirect( $this>generateUrl('sdzblog_voir', array('id' => 5)) ); } // Si on n'est pas en POST, alors on affiche le formulaire. return $this>render('SdzBlogBundle:Blog:ajouter.html.twig'); } public function modifierAction($id) { $article = $this->getDoctrine() ->getEntityManager() ->getRepository('SdzBlogBundle:Article') ->find($id); // Si l'article n'existe pas, on affiche une erreur 404 if( $article == null ) { throw $this>createNotFoundException('Article[id='.$id.'] inexistant'); } // Ici, on s'occupera de la cration et de la gestion du formulaire. return $this>render('SdzBlogBundle:Blog:modifier.html.twig', array( 'article' => $article

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


} ));

130/201

public function supprimerAction($id) { $article = $this->getDoctrine() ->getEntityManager() ->getRepository('SdzBlogBundle:Article') ->find($id); // Si l'article n'existe pas, on affiche une erreur 404 if( $article == null ) { throw $this>createNotFoundException('Article[id='.$id.'] inexistant'); } if( $this->get('request')->getMethod() == 'POST' ) { // Si la requte est en POST, on supprimera l'article. $this->get('session')->setFlash('info', 'Article bien supprim'); // Puis on redirige vers l'accueil. return $this->redirect( $this>generateUrl('sdzblog_accueil') ); } // Si la requte est en GET, on affiche une page de confirmation avant de supprimer. return $this>render('SdzBlogBundle:Blog:supprimer.html.twig', array( 'article' => $article )); } }

Impossible d'utiliser le crud ?


V ous voulez voir votre design avec une liste remplie d'articles mais le crud gnr prcdemment ne fonctionne plus ? En effet, comme nous avons ajout les attributs auteur et tags aprs la gnration du crud, celui ci n'est plus valide. On ne va pas traiter ici la gestion des formulaires, un chapitre entier leur est ddi, mais on va juste rapidement le remettre sur pieds. Pour cela, rien de plus simple, ouvrez le fichier Form/ArticleType.php et rajoutez juste le champ auteur dans le formulaire comme ceci : Code : PHP <?php // src/Sdz/BlogBundle/Form/ArticleType.php $builder ->add('date') ->add('titre') ->add('contenu') ->add('auteur') ;

En ajoutant juste la ligne surligne, le formulaire d'ajout d'un article sera nouveau fonctionnel. En effet, l'attribut "auteur" est obligatoire dans notre entit, et le formulaire ne le contenait pas. C'est maintenant rgl, et vous pouvez nouveau ajouter et modifier des articles de test depuis le crud ( l'adresse /blog/crud), en attendant que notre propre contrleur soit oprationnel videmment .

Amlioration du contrleur
Le contrleur qu'on vient de faire n'est pas encore parfait, on peut faire encore mieux ! Je pense notamment deux points en particulier : Le premier est l'utilisation implicite d'un ParamConverter pour viter de vrifier chaque fois l'existence d'un article (l'erreur 404 qu'on dclenche le cas chant) ; Le deuxime est la pagination des articles sur la page d'accueil.

L'utilisation d'un ParamConverter


Le ParamConverter est une notion trs simple : c'est un convertisseur de paramtre. V ous voil bien avanc n'est-ce pas ? Plus srieusement, c'est vraiment cela. Il existe un chapitre ddi sur l'utilisation du ParamConverter dans la partie astuces. Mais sachez qu'il va convertir le paramtre en entre du contrleur dans une autre forme. Typiquement, nous avons le paramtre $id en entre de certaines de nos actions, que nous voulons transformer directement en entit Article. Pour cela rien de plus simple, il faut modifier la dfinition des mthodes du contrleur comme suit :

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php // La dfinition : public function voirAction($id) // Devient : public function voirAction(Article $article)

131/201

Bien entendu, n'oubliez pas de rajouter en dbut de fichier " use Sdz\BlogBundle\Entity\Article;" afin de pouvoir utiliser Article. Et grce aux mcanismes internes de Symfony2, vous retrouvez directement dans la variable $article l'article correspondant l'id $id. Cela nous permet de supprimer l'utilisation du repository pour rcuprer l'article, mais galement le test de l'existence de l'article. Ces deux points sont fait automatiquement par le ParamConverter de Doctrine, et cela simplifie normment nos mthodes. V oyez par vous-mmes ce que devient la mthode voirAction du contrleur : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php public function voirAction(Article $article) { return $this->render('SdzBlogBundle:Blog:voir.html.twig', array( 'article' => $article )); }

Merveilleux non ? La mthode est rduit son strict minimum, sans rien enlever en termes de fonctionnalit. Essayez avec un article qui n'existe pas : /blog/article/5123123, nous avons bien une erreur 404 .

La pagination des articles sur la page d'accueil


Paginer les rsultats d'une requte n'est pas trop compliqu, il faut juste faire un peu de mathmatiques. Si on dcompose par tapes : 1. Il faut d'abord rcuprer le nombre total d'articles. Pour cela, vous devez crer une mthode getTotal() dans le repository d'Article. La mthode contient une toute petite requte, et retourne le nombre total d'articles dans la base de donnes ; 2. Ensuite, on dfinit arbitrairement un nombre d'articles par page ; 3. Puis, avec ces deux informations et avec le paramtre $page que l'on rcupre dans la mthode indexAction(), on peut calculer le nombre total de pages ainsi que le rang du premier article slectionner (l'offset). 4. Enfin, il ne reste plus qu' slectionner les articles, grce aux paramtres de la mthode findBy($criteres, $orderBy, $limite, $offset), rappelez-vous nous l'avons vu au chapitre prcdent. Avec cela vous avez tous les lments pour raliser votre pagination. Au travail ! Encore une fois, faites l'effort de la raliser de votre ct. Evidemment la solution est juste en dessous, mais si vous n'avez pas essay de chercher, vous ne progresserez pas. Courage

Il existe bien entendu diffrentes manires de le faire, mais voici le code du contrleur que je vous propose : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php public function indexAction($page) { // On rcupre le repository $repository = $this->getDoctrine() ->getEntityManager() ->getRepository('SdzBlogBundle:Article'); // On rcupre le nombre total d'articles $nb_articles = $repository->getTotal(); // On dfinit le nombre d'articles par page // (pour l'instant en dur dans le contrleur, mais par la suite on le transformera en paramtre du bundle) $nb_articles_page = 2; // On calcule le nombre total de pages

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2


$nb_pages = ceil($nb_articles/$nb_articles_page); : // On va rcuprer les articles partir du N-ime article $offset = ($page-1) * $nb_articles_page; // Ici on a chang la condition pour dclencher une erreur // lorsque la page est infrieur 1 ou suprieur au nombre

132/201

404 max.

if( $page < 1 OR $page > $nb_pages ) { throw $this->createNotFoundException('Page inexistante (page = '.$page.')'); } // On rcupre les articles qu'il faut grce findBy() : $articles = $repository->findBy( array(), // Pas de critre array('date' => 'desc'), // On tri par date dcroissante $nb_articles_page, // On slectionne $nb_articles_page articles $offset // A partir du $offset ime ); array( return $this->render('SdzBlogBundle:Blog:index.html.twig', 'articles' => $articles, 'page' => $page, // On transmet la vue la page 'nb_pages' => $nb_pages // Et le nombre total de pages.

courante, } ));

Ainsi que la mthode du repository : Code : PHP <?php // src/Sdz/BlogBundle/Entity/ArticleRepository.php class ArticleRepository extends EntityRepository { public function getTotal() { $qb = $this->createQueryBuilder('a') ->select('COUNT(a)'); // On slectionne simplement COUNT(a) return (int) $qb->getQuery() ->getSingleScalarResult(); // Utilisation de getSingleScalarResult pour avoir directement le rsultat du COUNT } }

Et enfin la vue, laquelle j'ai juste rajout l'affichage de la liste des pages possibles : Code : HTML & Django {# src/Sdz/BlogBundle/Resources/views/Blog/index.html.twig #} {# ... le reste de la vue #} <div class="pagination"> <ul> {# On utilise la fonction range(a, b) qui cre un tableau de valeurs entre a et b #} {% for p in range(1, nb_pages) %} <li{% if p == page %} class="active"{% endif %}> <a href="{{ path('sdzblog_accueil', {'page': p}) }}">{{ p }}</a> </li> {% endfor %} </ul> </div>

Et le rsultat visuel :

www.siteduzero.com

Partie 3 : Grer la base de donnes avec Doctrine2

133/201

Sachez quand mme qu'il existe un bundle pour vous aider dans la pagination de vos rsultats : il s'agit de KnpPaginatorBundle. Bien qu'il soit dlicat utiliser ce stade du tutoriel (il vous manque des notions sur les services), n'hsitez pas vous en servir lorsque vous serez prt . Et voil le premier TP du tutoriel s'achve ici. J'espre que vous avez pu exploiter toutes les connaissances que vous avez pu acqurir jusqu'ici, et qu'il vous a aid vous sentir plus l'aise. Le code tel qu'il doit tre la fin de ce chapitre est ici : https://github.com/winzou/SdzBlog/tree [...] 9b3c99e81c05b N'hsitez pas y faire un tour La prochaine partie du tutoriel va vous emmener plus loin avec Symfony2, pour connaitre tous les dtails qui vous permettront de crer votre site internet de toute pice. A bientt !

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2

134/201

Partie 4 : Allons plus loin avec Symfony2

Crer des formulaires avec Symfony2


Quoi de plus important sur un site Web que les formulaires ? En effet, les formulaires sont l'interface entre vos visiteurs et votre contenu. Chaque commentaire, chaque article de blog, etc, tous passent par l'intermdiaire d'un visiteur et d'un formulaire pour exister dans votre base de donnes. L'objectif de ce chapitre est donc, de vous donner enfin les outils pour crer efficacement ces formulaires grce la puissance du composant Form de Symfony2. Ce chapitre va de paire avec le prochain, dans lequel nous parlerons de la validation des donnes, celles que vos visiteurs vont rentrer dans vos nouveaux formulaires.

Gestion des formulaires L'enjeu des formulaires


V ous avez dj cr des formulaires en HTML et PHP, vous savez donc que c'est une vraie galre ! moins d'avoir cr vousmme un systme ddi, grer correctement des formulaires s'avre tre un peu mission impossible. Par correctement, j'entends de faon maintenable, mais surtout, rutilisable. En effet, que ceux qui pensent pouvoir rutiliser facilement leur formulaire de cration d'un article de blog dans une autre page lvent la main Personne ? C'est bien ce que je pensais. Heureusement, le composant Form de Symfony2 arrive la rescousse ! N'oubliez pas que les composants peuvent tre utiliss hors d'un projet Symfony2. V ous pouvez donc reprendre le composant Form dans votre site mme si vous n'utilisez pas Symfony2.

Un formulaire Symfony2, qu'est-ce que c'est ?


La vision Symfony2 sur les formulaires est la suivante : un formulaire se construit sur un objet existant, et son objectif est d'hydrater cet objet. Reprenons cette dfinition.

Un objet existant
Il nous faut donc des objets de base avant de crer des formulaires. Mais en fait, a tombe bien : on les a dj, ces objets ! En effet, un formulaire pour ajouter un article de blog va se baser sur l'objet Article, objet que nous avons construit lors du chapitre prcdent. Tout est cohrent. Je dis bien objet et non entit Doctrine2 . En effet, les formulaires n'ont pas du tout besoin d'une entit pour se construire, mais uniquement d'un simple objet. Heureusement, nos entits sont de simples objets avant d'tre des entits, donc elles conviennent parfaitement. Prenons un exemple pour illustrer cela : un formulaire de recherche. A priori , nous n'avons pas d'entit Recherche car cela n'a pas de sens (sauf si vous souhaitez enregistrer en base de donnes toutes les recherches effectues, pourquoi pas), et pourtant, on a bien besoin d'un formulaire de recherche ! Il suffit donc de crer un simple objet Recherche, compos d'un seul attribut requte , par exemple. Cet objet est suffisant pour construire notre formulaire, et ce n'est pas une entit Doctrine2. Pour la suite de ce chapitre, nous allons utiliser notre objet Article. C'est un exemple simple qui va nous permettre de construire notre premier formulaire. Je rappelle son code, sans les annotations pour plus de clart (et parce qu'elles ne nous regardent pas ici) : Secret (cliquez pour afficher) Code : PHP <?php namespace Sdz\BlogBundle\Entity; use Sdz\BlogBundle\Entity\Tag; class Article { private $id; private $date; private $titre; private $contenu; private $auteur; private $tags; public function __construct() { $this->tags = new ArrayCollection(); } public function getId() {

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


} return $this->id;

135/201

public function setDate($date) { $this->date = $date; } public function getDate() { return $this->date; } public function setTitre($titre) { $this->titre = $titre; } public function getTitre() { return $this->titre; } public function setContenu($contenu) { $this->contenu = $contenu; } public function getContenu() { return $this->contenu; } public function getAuteur() { return $this->auteur; } public function setAuteur($auteur) { $this->auteur = $auteur; } public function addTag(Tag $tag) { $this->tags[] = $tag; } public function getTags() { return $this->tags; }

Rappel : la convention pour le nom des getters/setters est importante : lorsque l'on parlera du champ auteur , le composant Form utilisera l'objet via les mthodes setAuteur() et getAuteur() (comme le faisait Doctrine2 de son ct). Donc si vous aviez eu " set_auteur()" ou " recuperer_auteur()", a n'aurait pas fonctionn.

Objectif : hydrater cet objet


Hydrater ? Un terme prcis pour dire que le formulaire va remplir les attributs de l'objet avec les valeurs entres par le visiteur. Faire <?php setAuteur('winzou') ?> et <?php setDate(new \Datetime()), etc., c'est hydrater l'objet Article. Le formulaire en lui-mme n'a donc comme seul objectif que d'hydrater un objet. Ce n'est qu'une fois l'objet hydrat que vous pourrez en faire ce que vous voudrez : faire une recherche dans le cas d'un objet Recherche, enregistrer en base de donnes dans le cas de notre objet Article, envoyer un mail dans le cas d'un objet Contact, etc. Le systme de formulaire ne s'occupe pas de ce que vous faites de votre objet, il ne fait que l'hydrater. Une fois que vous avez compris a, vous avez compris l'essentiel. Le reste n'est que de la syntaxe connatre.

Gestion basique d'un formulaire


Concrtement, pour crer un formulaire, il nous faut deux choses : un objet (on a toujours notre objet Article) ; un moyen pour construire un formulaire partir de cet objet, un FormBuilder, constructeur de formulaire en franais. Pour faire des tests, placez-vous dans l'action ajouterAction() de notre contrleur Blog. Modifiez-la comme suit : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


use Sdz\BlogBundle\Entity\Article; // ... public function ajouterAction() { // On cre un objet Article. $article = new Article(); // On cre le FormBuilder grce la mthode du contrleur. $formBuilder = $this->createFormBuilder($article); // On ajoute les champs de l'entit que l'on veut notre formulaire. $formBuilder ->add('date', 'date') ->add('titre', 'text') ->add('contenu', 'textarea') ->add('auteur', 'text'); // Pour l'instant, pas de tags, on les grera plus tard. // partir du formBuilder, on gnre le formulaire. $form = $formBuilder->getForm(); // On passe la mthode createView() du formulaire la vue // afin qu'elle puisse afficher le formulaire toute seule. return $this->render('SdzBlogBundle:Blog:ajouter.html.twig', array( 'form' => $form->createView(), )); }

136/201

Pour le moment, ce formulaire n'est pas oprationnel. On va pouvoir l'afficher, mais il ne se passera rien lorsqu'on va le valider. Avant cela, essayons de comprendre le code prsent. Dans un premier temps, on rcupre le FormBuilder. Cet objet n'est pas le formulaire en lui-mme, c'est un constructeur de formulaire. On lui dit : Cre un formulaire autour de l'objet $article . , puis : Ajoute les champs date , titre , contenu et auteur . Et enfin : Maintenant, donne-moi le formulaire construit avec tout ce que je t'ai dit avant . . Prenons le temps de bien faire la diffrence entre les attributs de l'objet hydrat et les champs du formulaire. Un formulaire n'est pas du tout oblig d'hydrater tous les attributs d'un objet. On pourrait trs bien ne pas utiliser le pseudo pour l'instant par exemple, et ne pas mettre de champ "auteur" dans notre formulaire. L'objet lui contient toujours l'attribut "auteur", mais il ne sera juste pas hydrat par le formulaire. Bon en l'occurrence ce n'est pas le comportement que l'on veut (on va considrer le pseudo comme obligatoire pour un article), mais sachez que c'est possible . D'ailleurs si vous avez l'oeil, vous avez remarqu qu'on n'ajoute pas de champ "id" : comme il sera rempli automatiquement par Doctrine (grce l'auto-incrmentation), le formulaire n'a pas besoin de remplir cet attribut. Enfin, c'est avec cet objet $form gnr que l'on pourra grer notre formulaire : vrifier qu'il est valide, l'afficher, etc. Par exemple, ici, on utilise sa mthode <?php $form->createView() qui permet la vue d'afficher ce formulaire. Concernant l'affichage du formulaire, j'ai une bonne nouvelle pour vous : Symfony2 nous permet d'afficher un formulaire simple en... une seule ligne HTML ! Si si, rendez-vous dans la vue Blog/formulaire.html.twig et ajoutez ces quelques lignes l o nous avions laiss un trou : Code : HTML & Django {# src/Sdz/BlogBundle/Resources/views/Blog/formulaire.html.twig #} <form method="post" {{ form_enctype(form) }}> {{ form_widget(form) }} <input type="submit" /> </form>

Ensuite, admirez le rsultat l'adresse suivante : http://localhost/Symfony/web/app_dev.php/blog/ajouter, impressionnant non ? Grce la fonction Twig form_widget() on peut afficher un formulaire entier en une seule ligne. Alors bien sr il n'est pas forcment votre gout pour le moment, mais voyez le bon ct des choses : pour l'instant on est en plein dveloppement, on veut tester notre formulaire. On s'occupera de l'esthtique plus tard . N'oubliez pas galement de rajouter les balises <form> HTML et le bouton de soumission, car cette fonction n'affiche que l'intrieur du formulaire. Bon videmment, comme je vous l'ai dit, ce code ne fait qu'afficher le formulaire. Il n'est pas encore question de grer la soumission du formulaire. mais patience, on y arrive

Ajouter des champs


V ous pouvez le voir, ajouter des champs un formulaire se fait assez facilement avec la mthode <?php $formBuilder>add() du FormBuilder. Les arguments sont les suivants : 1. Le nom du champ ; 2. Le type du champ : liste complte dans la documentation ; 3. Les options du champ, sous forme de tableau.

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


Par type de champ , il ne faut pas comprendre type HTML comme text , password ou select . Il faut comprendre type smantique . Par exemple, le type date que l'on a utilis affiche trois champs select la suite pour choisir le jour, le mois et l'anne. Il existe aussi un type timezone pour choisir le fuseau horaire. Bref, il en existe pas mal et ils n'ont rien voir avec les types HTML, ils vont bien plus loin que ces derniers ! N'oubliez pas, Symfony2 est magique ! La liste complte des types de champ se trouve dans la doc. Je vous ordonne d'y aller, elle regorge de types trs intressants !

137/201

Gestion de la soumission d'un formulaire


Afficher un formulaire c'est bien, mais faire quelque chose lorsqu'un visiteur le soumet, c'est quand mme mieux ! Pour grer l'envoi du formulaire, il faut tout d'abord vrifier que la requte est de type POST : cela signifie que le visiteur est arriv sur la page en cliquant sur le bouton submit du formulaire. Lorsque c'est le cas, on peut traiter notre formulaire. Ensuite, il faut faire le lien entre les variables de type POST et notre formulaire, pour que les variables de type POST viennent remplir les champs correspondants du formulaire. Cela se fait via la mthode bindRequest() du formulaire. Cette mthode dit au formulaire : Voici la requte d'entre (nos variables de type POST entre autres). Lis cette requte, rcupre les valeurs qui t'intressent et hydrate l'objet. Comme vous pouvez le voir, elle fait beaucoup de choses ! Enfin, une fois que notre formulaire a lu ses valeurs et hydrat l'objet, il faut tester ces valeurs pour vrifier qu'ils sont valident avec ce que l'objet attend. Il faut valider notre objet. Cela se fait via la mthode isValid() du formulaire. Ce n'est qu'aprs ces trois tapes que l'on peut traiter notre objet hydrat : sauvegarder en base de donnes, envoyer un email, etc. V ous tes un peu perdu ? C'est parce que vous manquez de code. V oici comment faire tout ce que l'on vient de dire, dans le contrleur : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php use Sdz\BlogBundle\Entity\Article; // ... public function ajouterAction() { $article = new Article; // J'ai raccourci cette partie, car plus rapide crire ! $form = $this->createFormBuilder($article) ->add('date', 'date') ->add('titre', 'text') ->add('contenu', 'textarea') ->add('auteur', 'text') ->getForm(); // On rcupre la requte. $request = $this->get('request'); // On vrifie qu'elle est de type POST . if( $request->getMethod() == 'POST' ) { // On fait le lien Requte <-> Formulaire. $form->bindRequest($request); // On vrifie que les valeurs rentres sont correctes. // (Nous verrons la validation des objets en dtail plus bas dans ce chapitre.) if( $form->isValid() ) { // On l'enregistre notre objet $article dans la base de donnes. $em = $this->getDoctrine()->getEntityManager(); $em->persist($article); $em->flush(); // On redirige vers la page d'accueil, par exemple. return $this->redirect($this>generateUrl('sdzblog_accueil')); } } // ce stade : // - soit la requte est de type GET , donc le visiteur vient d'arriver sur la page et veut voir le formulaire ; // - soit la requte est de type POST , mais le formulaire n'est pas valide, donc on l'affiche de nouveau.

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


return $this->render('SdzBlogBundle:Blog:ajouter.html.twig', array( 'form' => $form->createView(), )); }

138/201

Si le code parat long, c'est parce que j'ai mis plein de commentaires ! Prenez le temps de bien le lire et de bien le comprendre : vous verrez, c'est vraiment simple. N'hsitez pas le tester. Essayez de ne pas remplir un champ pour observer la raction de Symfony2, par exemple. V ous voyez que ce formulaire gre dj trs bien les erreurs, il n'enregistre l'article que lorsque tout va bien. N'hsitez pas tester votre formulaire en ajoutant des articles ! Il est oprationnel, et les articles que vous ajoutez sont rellement enregistrs en base de donnes

Grer les valeurs par dfaut du formulaire


L'un des besoins courant dans les formulaires, c'est de mettre des valeurs prdfinies dans les champs. a peut servir pour des valeurs par dfaut (pr-remplir la date, par exemple) ou alors lors de l'dition d'un objet dj existant (pour l'dition d'un article, on souhaite remplir le formulaire avec les valeurs de la base de donnes). Heureusement, cela se fait trs facilement. Il suffit de modifier l'instance de l'objet, ici $article, avant de le passer en argument la mthode createFormBuilder, comme ceci : Code : PHP <?php // On cre un nouvel article $article = new Article; // Ici, on pr-rempli avec la date d'aujourd'hui, par exemple. // Cette date sera donc pr-affiche dans le formulaire, cela facilite le travail de l'utilisateur. $article->setDate(new \Datetime()); // Et on construit le formBuilder avec cette instance d'article. $formBuilder = $this->createFormBuilder($article); // N'oubliez pas d'ajouter les champs comme prcdemment avec la mthode ->add()

Et si vous voulez modifier un article dj enregistr en base de donnes, alors il suffit de le rcuprer avant la cration du formulaire, comme ceci : Code : PHP <?php // Rcupration d'un article dj existant, d'id $id. $article = $this->getDoctrine() ->getRepository('Sdz\BlogBundle\Entity\Article') ->find($id); // Et on construit le formBuilder avec cette instance d'article, comme prcdemment. $formBuilder = $this->createFormBuilder($article); // N'oubliez pas d'ajouter les champs comme prcdemment avec la mthode ->add()

Personnaliser l'affichage d'un formulaire


Jusqu'ici, nous n'avons pas du tout personnalis l'affichage de notre formulaire. V oyez quand mme le bon ct des choses : on travaillait ct PHP, on a pu avancer trs rapidement sans se soucier d'crire les balises <input> la main, ce qui est long et sans intrt. Mais bon, un moment donn, il faut bien mettre la main la pte et faire des formulaires dans le mme style que son site. Pour cela, je ne vais pas m'tendre, mais voici un exemple qui vous permettra de faire peu prs tout ce que vous voudrez : Code : HTML <form action="{{ path('votre_route') }}" method="post" {{ form_enctype(form) }}> <!-- Les erreurs gnrales du formulaire. --> {{ form_errors(form) }} <div> <!-- Gnration du label. --> {{ form_label(form.titre, "Titre de l'article") }}

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


<!-- Affichage des erreurs pour ce champ prcis. --> {{ form_errors(form.titre) }} <!-- Gnration de l'input. --> {{ form_widget(form.titre) }} </div> <!-- Idem pour un autre champ. --> <div> {{ form_label(form.contenu, "Contenu de l'article") }} {{ form_errors(form.contenu) }} {{ form_widget(form.contenu) }} </div> <!-- Gnration des champs pas encore crits. Dans cet exemple, a serait date , auteur et tags , mais aussi le champ CSRF (gr automatiquement par Symfony !) et tous les champs cachs (type hidden ). --> {{ form_rest(form) }}

139/201

Plus d'informations sur le Cross Site Request Forgeries (CSRF).

Crer des types de champs personnaliss


Il se peut que vous ayez envie d'utiliser un type de champ prcis, mais que ce type de champ n'existe pas par dfaut. Heureusement, vous n'tes pas coinc, vous pouvez vous en sortir en crant votre propre type de champ. V ous pourrez ensuite utiliser ce champ comme n'importe quel autre dans vos formulaires. Imaginons par exemple que vous n'aimiez pas le rendu du champ date avec ces trois balises <select> pour slectionner le jour, le mois et l'anne. V ous prfreriez un joli datepicker en JavaScript. La solution ? Crer un nouveau type de champ ! Je ne vais pas dcrire la dmarche ici, mais sachez que a existe et que c'est bien document.

Externaliser la gestion de ses formulaires


Pour bien externaliser la gestion de ses formulaires, il nous faut deux tapes. D'abord, nous allons sortir du contrleur la dfinition du formulaire : ses champs, options, etc. Puis, nous allons sortir la gestion du formulaire : action raliser lorsqu'il est valide, etc.

Externaliser la dfinition du formulaire


V ous savez enfin crer un formulaire. Ce n'tait pas trs compliqu, nous l'avons rapidement fait et ce dernier se trouve tre assez joli. Mais vous souvenez-vous de ce que j'avais promis au dbut : nous voulions un formulaire rutilisable ; or l, tout est dans le contrleur, et je vois mal comment le rutiliser ! Pour cela, il faut dtacher la dfinition du formulaire dans une classe part, nomme ArticleType (par convention).

Dfinition du formulaire dans ArticleType


ArticleType n'est pas notre formulaire. Comme tout l'heure, c'est notre constructeur de formulaire. Par convention, on va mettre tous nos xxxType.php dans le rpertoire Form du bundle. En fait, allez voir, ce rpertoire existe dj, et le ArticleType galement ! En effet, Symfony2 l'avait gnr lorsque l'on avait cr le CRUD. V ous devriez donc vous retrouver avec ce code : Code : PHP <?php // src/Sdz/BlogBundle/Form/ArticleType.php namespace Sdz\BlogBundle\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; class ArticleType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder ->add('date') ->add('titre') ->add('contenu') ->add('auteur'); } public function getName() { return 'sdz_blogbundle_articletype'; }

Comme vous pouvez le voir, on n'a fait que dplacer la construction du formulaire, du contrleur une classe externe. Cela nous

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


permettra de rutiliser exactement le mme formulaire d'autres endroits. V oil la rutilisabilit ! Et prenez aussi ds maintenant un bon rflexe, que le gnrateur n'a malheureusement pas respect. En effet, rappelez-vous, un formulaire se construit autour d'un objet, et avec la technique du ArticleType, nous pouvons conomiser du temps en dfinissant directement dans ArticleType quel est l'objet sous-jacent. Ainsi, cela permettra d'indiquer Symfony2 l'objet utiliser dans bien des cas. La bonne pratique est donc d'ajouter trs simplement la mthode getDefaultOptions() dans notre ArticleType : Code : PHP <?php // src/Sdz/BlogBundle/Form/ArticleType.php class ArticleType extends AbstractType { // ... public function getDefaultOptions(array $options) { return array( 'data_class' => 'Sdz\BlogBundle\Entity\Article', ); }

140/201

Le but est donc d'indiquer la classe de l'objet utilis pour ce formulaire. Cet ArticleType correspond donc en fait la dfinition des champs de notre formulaire. Ainsi, si l'on utilise le mme formulaire sur plusieurs pages diffrentes, on utilisera ce mme ArticleType. Fini le copier-coller !

Le contrleur pur
Avec cet ArticleType, la construction du formulaire ct contrleur s'effectue grce la mthode createForm() du contrleur (et non plus createFormBuilder()). Cette mthode utilise le composant Form pour construire un formulaire partir du ArticleType pass en argument. Depuis le contrleur, on rcupre donc directement un formulaire, on ne passe plus par le constructeur de formulaire comme plus haut. V oyez par vous-mme : Code : PHP <?php $article = new Article; $form = $this->createForm(new ArticleType, $article);

En effet, si l'on s'est donn la peine de crer un objet l'extrieur du contrleur, c'est pour que ce contrleur soit plus simple. C'est russi. Au final, en utilisant cette externalisation et en supprimant les commentaires, voil quoi ressemble la gestion d'un formulaire dans Symfony2 : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php use Sdz\BlogBundle\Entity\Article; // N'oubliez pas de rajouter le ArticleType use Sdz\BlogBundle\Form\ArticleType; // ... public function ajouterAction() { $article = new Article; $form = $this->createForm(new ArticleType, $article); $request = $this->get('request'); if( $request->getMethod() == 'POST' ) { $form->bindRequest($request); if( $form->isValid() ) { $em = $this->getDoctrine()->getEntityManager(); $em->persist($article); $em->flush(); return $this->redirect( $this>generateUrl('sdzblog_accueil') ); } } return $this->render('SdzBlogBundle:Blog:ajouter.html.twig', array(

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


)); 'form' => $form->createView(),

141/201

Plutt simple, non ? Au final, votre code mtier, votre code qui fait rellement quelque chose se trouve l o l'on a utilis l'EntityManager. Pour l'exemple, nous n'avons fait qu'enregistrer l'article en base de donnes, mais c'est ici que vous pourrez envoyer un email, ou toute autre action dont votre site Internet aura besoin. Mais quelque chose devrait encore vous gner... si on a bien externalis la dfinition du formulaire, tout le traitement est rest dans le contrleur. Et si on devait rutiliser le mme formulaire un autre endroit dans notre code, il faudrait copier-coller le traitement. Pour ne pas faire cette horrible chose qu'est le copier-coller, nous allons maintenant externaliser la gestion de notre formulaire.

Externaliser la gestion du formulaire


V ous l'aurez compris, l'objectif maintenant est de sortir le traitement du contrleur, cela correspond aux lignes 15 23 du code cidessus : quand excuter le traitement (requte en POST, formulaire valide) et que faire comme traitement (enregistrer en base de donnes).

Traitement du formulaire dans ArticleHandler


Pour raliser cette externalisation, nous allons cr une deuxime classe dans le rpertoire Form : ArticleHandler. Cette classe va contenir trois mthodes pour l'instant, mais on pourra bien sr en ajouter d'autres si notre traitement le demande : Un constructeur : Il va nous permettre de donner au Handler les outils dont il a besoin. Pour savoir lesquels, c'est trs simple : on regarde tout ce que le contrleur utilise entre les lignes 15 23, que je vous remets ici : Code : PHP <?php $request = $this->get('request'); if( $request->getMethod() == 'POST' ) { $form->bindRequest($request); if( $form->isValid() ) { $em = $this->getDoctrine()->getEntityManager(); $em->persist($article); $em->flush();

Le contrleur se sert de 1/ la requte, 2/ le formulaire et 3/ l'EntityManager : on va donc tout simplement passer ces trois outils au constructeur. Bien entendu, si votre traitement fait appel d'autres outils (SwiftMailer pour l'envoi d'email par exemple), il faudra les rajouter en argument votre handler. V oici donc le code du constructeur : Code : PHP <?php public function __construct(Form $form, Request $request, EntityManager $em) { $this->form = $form; $this->request = $request; $this->em = $em; }

Une mthode pour savoir quand dclencher le traitement. Cela correspond aux diffrents tests que l'on a fait dans le contrleur : vrifier que la requte est en POST et que le formulaire est bien valide. Par convention, on nomme cette mthode "process", la voici donc : Code : PHP <?php public function process() { if( $this->request->getMethod() == 'POST' ) { $this->form->bindRequest($this->request); if( $this->form->isValid() ) { // On appelle la mthode onSucess qui va effectuer le traitement, on la dcrit juste en dessous. // $this->form->getData() reprsente l'objet trait par le formulaire, une instance d'Article dans notre cas. $this->onSuccess($this->form->getData()); } return true;

} }

return false;

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2

142/201

V ous pourriez mme mettre des arguments cette mthode process(), afin d'effectuer un traitement diffrents selon les cas. Une mthode qui va effectivement excuter le traitement. Cela correspond l'enregistrement de l'article en base de donnes que nous avions dans le contrleur. Par convention, on nomme cette mthode "onSucess", pour dire qu'on l'excute uniquement lorsque le formulaire vrifie les conditions dfinies dans la mthode process. V oici son code : Code : PHP <?php public function onSuccess(Article $article) { $this->em->persist($article); $this->em->flush(); }

Bien entendu et comme je vous l'ai dj dit, vous pouvez faire tous les traitements que vous voulez ici. On s'est limit pour l'exemple enregistrer l'article en base de donnes. Pour rsumer, crez notre ArticleHandler avec ce code complet : Code : PHP <?php // src/Sdz/BlogBundle/Form/ArticleHandler.php namespace Sdz\BlogBundle\Form; use use use use Symfony\Component\Form\Form; Symfony\Component\HttpFoundation\Request; Doctrine\ORM\EntityManager; Sdz\BlogBundle\Entity\Article;

class ArticleHandler { protected $form; protected $request; protected $em; public function __construct(Form $form, Request $request, EntityManager $em) { $this->form = $form; $this->request = $request; $this->em = $em; } public function process() { if( $this->request->getMethod() == 'POST' ) { $this->form->bindRequest($this->request); if( $this->form->isValid() ) { $this->onSuccess($this->form->getData()); } return true;

} }

return false;

public function onSuccess(Article $article) { $this->em->persist($article); $this->em->flush(); }

Le contrleur trs pur


V oici maintenant notre contrleur final. Nous avons externalis la dfinition, puis la gestion du formulaire. V ous devez vous y attendre : il ne reste plus grand chose dans le contrleur ! Tout fait, mais c'tait bien l'objectif initial. V oyez par vous-mmes : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller;

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


// ... vos uses use Sdz\BlogBundle\Entity\Article; use Sdz\BlogBundle\Form\ArticleType; // N'oubliez pas de rajouter le ArticleHandler use Sdz\BlogBundle\Form\ArticleHandler; class BlogController extends Controller { // ... d'autres mthodes public function ajouterAction() { $article = new Article; // On cre le formulaire $form = $this->createForm(new ArticleType, $article); // On cre le gestionnaire pour ce formulaire, avec les outils dont il a besoin $formHandler = new ArticleHandler($form, $this>get('request'), $this->getDoctrine()->getEntityManager()); // On excute le traitement du formulaire. S'il retourne true, alors le formulaire a bien t trait if( $formHandler->process() ) { return $this->redirect( $this>generateUrl('sdzblog_voir', array('id' => $article->getId())) ); } // Et s'il retourne false alors la requte n'tait pas en POST ou le formulaire non valide. // On raffiche donc le formulaire. return $this->render('SdzBlogBundle:Blog:ajouter.html.twig', array( 'form' => $form->createView(), )); } }

143/201

Les formulaires imbriqus Intrts de l'imbrication


En fait, pourquoi imbriquer des formulaires ? C'est souvent le cas lorsque vous avez des relations entre vos objets : vous souhaitez ajouter un objet, mais en mme temps un autre qui sera li au premier. Exemple concret : vous voulez ajouter un client votre application, votre Client est li une Adresse, mais vous avez envie d'ajouter l'adresse sur la mme page que votre client, depuis le mme formulaire. S'il fallait deux pages pour ajouter client puis adresse, votre site ne serait pas trs ergonomique. V oici donc toute l'utilit de l'imbrication des formulaires !

Un formulaire est un champ


Eh oui, voici tout ce que vous devez savoir pour imbriquer des formulaires entre eux. Considrez un de vos formulaires comme un champ, et appelez ce simple champ depuis un autre formulaire ! O.K., facile dire, mais il faut savoir le faire derrire. D'abord, crez le TagType pour notre entit Tag. Essayez de le faire vous-mme, c'est vraiment simple, et une fois fini, vrifiez votre code avec le mien. Inspirez-vous de ArticleType, nul besoin de faire du par cur. Secret (cliquez pour afficher) Code : PHP <?php // src/Sdz/BlogBundle/Form/TagType.php namespace Sdz\BlogBundle\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; class TagType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add('nom'); // Notez ici que l'on n'a pas prcis le type de champ : c'est parce que // le composant Form sait le reconnatre depuis nos annotations Doctrine ! } public function getName()

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


return 'sdz_blogbundle_tagtype'; changer le nom du formulaire. } { // N'oubliez pas de

144/201

public function getDefaultOptions(array $options) { return array( 'data_class' => 'Sdz\BlogBundle\Entity\Tag', // Ni de modifier la classe ici. ); } }

Ensuite, il existe deux faons d'imbriquer des formulaires : avec une relation simple o un seul formulaire est imbriqu dans un autre. C'est le cas le plus courant, celui de notre Client avec une seule Adresse ; avec une relation multiple, o vous voulez imbriquer plusieurs fois le mme formulaire dans un formulaire parent. C'est le cas de notre Article, par exemple, qui peut contenir plusieurs objets Tag.

Relation simple : imbriquer un seul formulaire


C'est le cas le plus courant, mais qui ne correspond malheureusement pas notre exemple de l'article et ses tags. Pour imbriquer un seul formulaire en tant cohrent avec une entit, il faut qu'elle dispose d'une relation OneToOne ou ManyToOne. Du coup, suivez bien, nous allons modifier temporairement notre entit Article pour transformer la relation ManyToMany en ManyToOne, et remplacer tous les tags en tag : comme si l'on ne pouvait en fait attacher qu'un seul tag un article. V oici ce que donne la modification : Secret (cliquez pour afficher) Code : PHP <?php // src/Sdz/BlogBundle/Entity/Article.php namespace Sdz\BlogBundle\Entity; use Sdz\BlogBundle\Entity\Tag; class Article { private $id; private $date; private $titre; private $contenu; private $auteur; /** * videmment, nous devons modifier la dfinition de la relation : on passe un ManyToOne ! * @ORM\ManyToOne(targetEntity="Sdz\BlogBundle\Entity\Tag") */ private $tag; // ... public function setTag(Tag $tag) // Attention, ici, c'est setTag et non plus addTags. { $this->tag = $tag; } public function getTag() { return $this->tag; } }

N'oubliez pas de mettre jour la base de donnes avec la commande php app/console doctrine:schema:update --force !

V oil, maintenant, nous pouvons imbriquer nos formulaires. C'est vraiment simple : allez dans ArticleType et rajoutez un champ tag (du nom de la proprit de notre entit), de type TagType, bien sr ! Code : PHP

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


<?php // src/Sdz/BlogBundle/Form/ArticleType.php public function buildForm(FormBuilder $builder, array $options) { $builder ->add('date', 'date') ->add('titre', 'text') ->add('contenu', 'textarea') ->add('auteur', 'text') ->add('tag', new TagType) ; }

145/201

Et voil ! Allez sur la page d'ajout : http://localhost/Symfony/web/app_dev.p [...] outer/article. Le formulaire est dj jour, avec une partie Tag o l'on peut remplir le seul champ de ce formulaire, le champ nom. C'tait d'une facilit dconcertante, n'est-ce pas ? Essayez d'ajouter un Article avec son Tag. Alors ? a ne fonctionne pas, videmment. Essayons de rflchir un peu Dans le ArticleHandler, nous avons dit l'EntityManager de persister l'article, mais on ne lui a rien prcis sur le tag contenu dans l'article (accessible via <?php $article->getTag(), tout simplement). C'est pour cela que Symfony2 nous dit : Citation : Symfony2 A new entity was found through the relationship 'Sdz\BlogBundle\Entity\Article#tag' ... Explicitly persist the new entity On va l'couter et ajouter un persist() sur notre tag dans la mthode onSuccess du ArticleHandler : Code : PHP <?php // src/Sdz/BlogBundle/Form/ArticleHandler.php // ... public function onSuccess(Article $article) { $this->em->persist($article); $this->em->persist($article->getTag()); ce persist(). $this->em->flush(); }

// Ici, on rajoute

Et voil, cette fois-ci, le formulaire est pleinement oprationnel ; lancez-vous dans les tests ! Pour vrifier que cela fonctionne, rendez-vous sur la page de modification d'un article : http://localhost/Symfony/web/app_dev.p [...] _de_l_article. C'est fini pour l'imbrication simple d'un formulaire dans un autre. Passons maintenant l'imbrication multiple.

Relation multiple : imbriquer un mme formulaire plusieurs fois


On va donc revenir notre vrai cas o l'on peut ajouter plusieurs tags un seul article. Tout d'abord, modifiez de nouveau l'entit Article pour remettre au pluriel les tags : Secret (cliquez pour afficher) Code : PHP <?php // src/Sdz/BlogBundle/Entity/Article.php namespace Sdz\BlogBundle\Entity; use Sdz\BlogBundle\Entity\Tag; class Article { private $id; private $date; private $titre; private $contenu; private $auteur; /** * @ORM\ManyToMany(targetEntity="Sdz\BlogBundle\Entity\Tag") */ private $tags; public function __construct() { $this->tags = new ArrayCollection(); // N'oubliez pas

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


l'ArrayCollection. $this->date = new \Datetime(); } // ... public function addTag(Tag $tag) { $this->tags[] = $tag; } public function getTags() { return $this->tags; }

146/201

Et bien sr, mettez jour la base de donnes.

Notre TagType ne va pas changer d'un poil. Un tag est un tag, qu'il soit ajout une fois ou mille dans un article ne change rien pour lui. V oici un bel exemple du code dcoupl que nous permet de faire Symfony2 ! Par contre, notre ArticleType, lui, va changer. On va changer le champ tag de type TagType, en champ tags (toujours du nom de la proprit), de type collection . En fait, oui, a pourrait tre une liste (collection ) de n'importe quoi, mme de champs de type "string" ! On va se servir des options du champ pour signaler que ce doit tre une liste de plusieurs TagType. V oici ce que a donne : Code : PHP <?php // src/Sdz/BlogBundle/Form/ArticleType.php class ArticleType extends AbstractType { public function buildForm(FormBuilder $builder, array { $builder ->add('date') ->add('titre') ->add('contenu') /* * Rappel : ** - 1er argument : nom du champ ; ** - 2e argument : type du champ ; ** - 3e argument : tableau d'options du champ. */ ->add('tags', 'collection', array('type' TagType, 'prototype' 'allow_add' ; } // ... }

$options)

=> new => true, => true))

Encore une fois, c'est vraiment tout ce qu'il y a modifier ct formulaire. V ous pouvez d'ores et dj observer le rsultat. Pour cela, actualisez la page d'ajout d'un article. Ah mince, Tags est bien inscrit, mais il n'y a rien en dessous. Ce n'est pas un bug, c'est bien voulu par Symfony2. En effet, partir du moment o vous pouvez ajouter plusieurs champs ou en supprimer, vous avez besoin de JavaScript pour le faire. Donc Symfony2 part du principe que de toute faon, vous grerez a avec du code JavaScript. O.K., a ne nous fait pas peur ! D'abord, affichez la source de la page et regardez l'trange balise <div> que Symfony2 a rajoute : Code : HTML <div id="sdz_blogbundle_articletype_tags" dataprototype="&lt;div&gt; &lt;label class=&quot; required&quot;&gt;$$name$$&lt;/label&gt; &lt;div id=&quot;sdz_blogbundle_articletype_tags_$$name$$&quot;&gt; &lt;div&gt;&lt;label for=&quot;sdz_blogbundle_articletype_tags_$$name$$_nom&quot; class=&quot; required&quot;&gt;Nom&lt;/label&gt; &lt;input type=&quot;text&quot; id=&quot;sdz_blogbundle_articletype_tags_$$name$$_nom&quot; name=&quot;sdz_blogbundle_articletype[tags][$$name$$][nom]&quot; required=&quot;required&quot; maxlength=&quot;255&quot; value=&quot;&quot; /&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;"> </div>

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


V ous connaissez l'attribut data-prototype ? C'est en fait un attribut (au nom arbitraire) rajout par Symfony2 et qui contient ce quoi doit ressembler le code HTML pour ajouter un tag. Essayez de le lire, vous voyez qu'il contient les balises <label> et <input> (tout ce qu'il faut pour notre champ nom , en fait). Du coup, on le remercie, car grce ce template, ajouter des champs en JavaScript est un jeu d'enfant. Je vous propose un script JavaScript rapidement fait qui emploie la librairie jQuery et qui est mettre dans notre fichier Blog/formulaire.html.twig : Code : HTML <!-- Ajout d'un lien pour ajouter un champ tag supplmentaire. --> <a href="#" id="add_tag">Ajouter un tag</a> <!-- On charge la librairie jQuery. Ici, je la prends depuis le site jquery.com, mais si vous l'avez en local, changez simplement l'adresse. --> <script src="http://code.jquery.com/jquery-1.6.2.min.js"></script> <script type="text/javascript"> $(document).ready(function() { // On rcupre la balise <div> en question qui contient l'attribut data-prototype qui nous intresse. var $container = $('#sdz_blogbundle_articletype_tags'); // On dfinit une fonction qui va ajouter un champ. function add_tag() { // On dfinit le numro du champ (en comptant le nombre de champs dj ajouts). index = $container.children().length; // On ajoute la fin de la balise <div> le contenu de l'attribut data-prototype , // aprs avoir remplac la variable $$name$$ qu'il contient par le numro du champ. $container.append( $($container.attr('dataprototype').replace(/\$\$name\$\$/g, index)) ); } // On ajoute un premier champ directement s'il n'en existe pas dj un. if($container.children().length == 0) { add_tag(); } // On ajoute un nouveau champ chaque clic sur le lien d'ajout. $('#add_tag').click(function() { add_tag(); }); }); </script>

147/201

Appuyez sur F5 sur la page d'ajout. V oil qui est mieux ! Cependant, comme tout l'heure, pour que le formulaire soit pleinement oprationnel, il nous faut adapter un peu la mthode onSuccess de notre ArticleHandler pour qu'il persiste tous nos tags : Code : PHP <?php // src/Sdz/BlogBundle/Form/ArticleHandler.php // ... public function onSuccess(Article $article) { $this->em->persist($article); // On persiste tous les tags de l'article. foreach($article->getTags() as $tag) { $this->em->persist($tag); } } $this->em->flush();

Et voil, votre formulaire est maintenant oprationnel ! V ous pouvez vous amuser crer des articles contenant plein de tags en mme temps. Vrifiez galement depuis la page de modification, vous voyez que vous pouvez modifier les tags en direct.

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


Vraiment pratique ! Pour information, ce champ de type collection que l'on vient de voir s'utilise avec n'importe quel autre type de champ, pas forcment un formulaire. Dans l'ArticleType, la place du <?php new TagType, vous auriez pu mettre text ou file ou n'importe quel autre type de champ classique.

148/201

Application : les formulaires de notre blog Thorie


Nous n'avons qu'un seul formulaire pour l'instant, celui pour crer et modifier l'objet Article. C'est un mme formulaire qui est utilis sur deux pages. Comme nous avons dj notre ArticleType et notre ArticleHandler, vous n'avez plus qu' vous occuper de la gestion du formulaire au niveau de notre BlogController dans les mthodes ajouterAction() et modifierAction(). Au boulot ! Essayez d'implmenter vous-mme la gestion du formulaire dans les actions correspondantes. Ensuite seulement, lisez la suite de ce paragraphe pour avoir la solution.

Pratique
Entit Article
J'ai modifi rapidement notre entit Article, afin de pr-remplir la date directement dans le constructeur lorsque l'on instancie l'objet : Code : PHP <?php // src/Sdz/BlogBundle/Entity/Article.php namespace Sdz\BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Doctrine\Common\Collections\ArrayCollection; use Symfony\Component\Validator\Constraints as Assert; /** * Sdz\BlogBundle\Entity\Article * * @ORM\Table() * @ORM\Entity(repositoryClass="Sdz\BlogBundle\Entity\ArticleRepository") */ class Article { // ... public function __construct() { $this->tags = new ArrayCollection(); $this->date = new \Datetime(); } } // ...

Ainsi, en faisant <?php $article = new Article; la valeur $article->getDate() vaut dj la date d'aujourd'hui.

Le ArticleType
On l'a dj fait au cours de ce chapitre, mais le voici en rappel : Code : PHP <?php // src/Sdz/BlogBundle/Form/ArticleType.php namespace Sdz\BlogBundle\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; class ArticleType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder ->add('date', 'date') ->add('titre', 'text') ->add('contenu', 'textarea') ->add('auteur', 'text')

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


TagType, true, true)) } ; ->add('tags', 'collection', array('type' => new

149/201

'prototype' => 'allow_add' =>

public function getName() { return 'sdz_blogbundle_articletype'; } public function getDefaultOptions(array $options) { return array( 'data_class' => 'Sdz\BlogBundle\Entity\Article', ); }

Le TagType lui n'a pas chang du tout.

Le ArticleHandler
Code : PHP <?php // src/Sdz/BlogBundle/Form/ArticleHandler.php namespace Sdz\BlogBundle\Form; use use use use Symfony\Component\Form\Form; Symfony\Component\HttpFoundation\Request; Doctrine\ORM\EntityManager; Sdz\BlogBundle\Entity\Article;

class ArticleHandler { protected $form; protected $request; protected $em; public function __construct(Form $form, Request $request, EntityManager $em) { $this->form = $form; $this->request = $request; $this->em = $em; } public function process() { if( $this->request->getMethod() == 'POST' ) { $this->form->bindRequest($this->request); if( $this->form->isValid() ) { $this->onSuccess($this->form->getData()); } return true;

} }

return false;

public function onSuccess(Article $article) { $this->em->persist($article); // On persiste tous les tags de l'article. foreach($article->getTags() as $tag) { $this->em->persist($tag); } } $this->em->flush();

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


L'action ajouter du contrleur
Ici pas de changement, on l'a galement fait au cours du chapitre : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php public function ajouterAction() { $article = new Article; $form = $this->createForm(new ArticleType, $article); $formHandler = new ArticleHandler($form, $this>get('request'), $this->getDoctrine()->getEntityManager()); if($formHandler->process()) { return $this->redirect( $this>generateUrl('sdzblog_voir', array('id' => $article->getId())) ); } array( } return $this->render('SdzBlogBundle:Blog:ajouter.html.twig', )); 'form' => $form->createView(),

150/201

L'action modifier du contrleur


V oil l'action que vous deviez faire tout seul. Pas de pige particulier : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php public function modifierAction($id) { $em = $this->getDoctrine()->getEntityManager(); // On vrifie que l'article d'id $id existe bien, sinon, erreur 404. if( ! $article = $em>getRepository('Sdz\BlogBundle\Entity\Article')->find($id) ) { throw $this>createNotFoundException('Article[id='.$id.'] inexistant'); } // On passe l'$article rcupr au formulaire $form = $this->createForm(new ArticleType, $article); $formHandler = new ArticleHandler($form, $this>get('request'), $em); if($formHandler->process()) { return $this->redirect( $this>generateUrl('sdzblog_voir', array('id' => $article->getId())) ); } return $this>render('SdzBlogBundle:Blog:modifier.html.twig', array( 'form' => $form->createView(), )); } V oil ! Ce chapitre important n'tait pas si compliqu dans le fond ! Bien entendu, vous ne pouvez pas vous arrter en si bon chemin. Maintenant que vos formulaires sont oprationnels, il faut bien vrifier un peu ce que vos visiteurs vont y mettre comme donnes ! C'est l'objectif du prochain chapitre, qui traite de la validation des donnes justement. Il vient complter le chapitre actuel, continuez donc la lecture !

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2

151/201

Validez vos donnes


Au chapitre prcdent nous avons vu comment crer des formulaires avec Symfony2. Mais qui dit formulaire dit vrification des donnes rentres ! Symfony2 contient un composant Validator qui, comme son nom l'indique, s'occupe de grer tout cela. Attaquons-le donc !

Pourquoi valider des donnes ? Never Trust User Input


Ce chapitre introduit la validation des objets avec le composant Validator de Symfony2. En effet, c'est normalement un des premiers rflexes avoir lorsque l'on demande l'utilisateur de remplir des informations : vrifier ce qu'il a rempli ! Il faut toujours considrer que soit il ne sait pas remplir un formulaire, soit c'est un petit malin qui essaie de trouver la faille. Bref, ne jamais faire confiance ce que l'utilisateur vous donne. La validation et les formulaires sont bien sr lis, dans le sens o les formulaires ont besoin de la validation. Mais l'inverse n'est pas vrai ! Dans Symfony2, le validator est un service indpendant et n'a nul besoin d'un formulaire pour exister. Ayez-le en tte, avec le validator, on peut valider n'importe quel objet, entit ou non, le tout sans avoir besoin de formulaire.

L'intrt de la validation
L'objectif de ce chapitre est donc d'apprendre dfinir qu'une entit est valide ou non. Plus concrtement, il nous faudra tablir des rgles prcises pour dire que tel attribut (le nom d'utilisateur par exemple) doit faire 3 caractres minimum, que tel autre attribut (l'ge par exemple) doit tre compris entre 7 et 77 ans, etc. En vrifiant les donnes avant de les enregistrer en base de donnes, on est certain d'avoir une base de donnes cohrente, dans laquelle on peut avoir confiance !

La thorie de la validation
La thorie, trs simple, est la suivante. On dfinit des rgles de validation que l'on va rattacher une classe. Puis on fait appel un service extrieur pour venir lire un objet (instance de ladite classe) et ses rgles, et dfinir si oui ou non l'objet en question respecte ces rgles. Simple et logique !

Dfinir les rgles de validation Les diffrentes formes de rgles


Pour dfinir ces rgles de validation, ou contraintes, il existe deux moyens : Le premier est d'utiliser les annotations, vous les connaissez maintenant. Leur avantage est d'tre situ au sein mme de l'entit, et juste ct des annotations du mapping Doctrine2 si vous les utilisez galement pour votre mapping . Le deuxime est d'utiliser le Yaml, XML ou PHP. V ous placez donc vos rgles de validation hors de l'entit, dans un fichier spar. Les deux moyens sont parfaitement quivalents en termes de fonctionnalits. Le choix se fait donc selon vos prfrences. Dans la suite du tutoriel, j'utiliserai les annotations car je trouve extrmement pratique de centraliser rgles de validation et mapping Doctrine au mme endroit. Facile lire et modifier .

Dfinir les rgles de validation


Prparation
Nous allons prendre l'exemple de notre entit Article pour construire nos rgles. La premire tape consiste dterminer les rgles que nous voulons avec des mots, comme ceci : La date doit tre une date valide ; Le titre doit faire au moins 10 caractres de long ; Le contenu ne doit pas tre vide ; Le pseudo doit faire au moins 2 caractres de long ; Les tags lis doivent tre valides selon les rgles attaches l'objet Tag ; A partir de cela, nous pourrons convertir ces mots en annotations.

Annotations
Pour dfinir les rgles de validation, nous allons donc utiliser les annotations. La premire chose savoir est le namespace des annotations utiliser. Souvenez-vous, pour le mapping Doctrine c'tait @ORM, ici nous allons utiliser @Assert, donc le namespace complet est le suivant : Code : PHP <?php use Symfony\Component\Validator\Constraints as Assert;

Ce use est rajouter au dbut de l'objet que l'on va valider, notre entit Article en l'occurrence. En ralit, vous pouvez dfinir l'alias autre chose qu'Assert. Mais c'est une convention qui s'est installe, donc autant la suivre pour avoir un code plus facilement lisible pour les autres dveloppeurs. Ensuite, il ne reste plus qu' ajouter les annotations pour traduire les rgles que l'on vient de lister. Sans plus attendre, voici donc la syntaxe respecter. Exemple avec notre objet Article :

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


Code : PHP <?php namespace Sdz\BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; // N'oubliez pas de rajouter ce use , il dfinit le namespace pour les annotations de validation. use Symfony\Component\Validator\Constraints as Assert; use Doctrine\Common\Collections\ArrayCollection; use Sdz\BlogBundle\Entity\Tag; /** * Sdz\BlogBundle\Entity\Article * * @ORM\Table() * @ORM\Entity(repositoryClass="Sdz\BlogBundle\Entity\ArticleRepository") */ class Article { /** * @var integer $id * * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @var date $date * * @ORM\Column(name="date", type="date") * @Assert\DateTime() */ private $date; /** * @var string $titre * * @ORM\Column(name="titre", type="string", length=255) * @Assert\MinLength(10) */ private $titre; /** * @var text $contenu * * @ORM\Column(name="contenu", type="text") * @Assert\NotBlank() */ private $contenu; /** * @var string $auteur * * @ORM\Column(name="auteur", type="string", length=255) * @Assert\MinLength(2) */ private $auteur; /** * @ORM\ManyToMany(targetEntity="Sdz\BlogBundle\Entity\Tag") * @Assert\Valid() */ private $tags; } // ...

152/201

Vraiment pratique d'avoir les mtadonnes Doctrine et les rgles de validation au mme endroit, n'est-ce pas ? J'ai pris l'exemple ici d'une entit car a sera souvent le cas. Mais n'oubliez pas que vous pouvez mettre des rgles de validation sur n'importe quel objet, qui n'est pas forcment entit.

Syntaxe
Revenons un peu sur les annotations que l'on a ajout. Nous avons utilis la forme simple, qui est construite comme ceci : Code : Autre

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


@Assert\Contrainte(valeur de l'option par dfaut)

153/201

Avec : La Contrainte, qui peut tre, comme vous l'avez vu, NotBlank ou MinLength , etc. Nous voyons plus loin toutes les contraintes possibles. La Valeur entre parenthses, qui est la valeur de l'option par dfaut. En effet chaque contrainte plusieurs options, dont une par dfaut souvent intuitive. Par exemple, l'option par dfaut de MinLength (longueur minimale en franais) est videmment la valeur de la longueur minimale que l'on veut appliquer, 10 pour le titre. Mais on peut aussi utiliser la forme tendu qui permet de personnaliser la valeur de plusieurs options en mme temps, comme ceci : Code : Autre @Assert\Contrainte(option1="valeur1", option2="valeur2", ...)

Les diffrentes options diffrent d'une contrainte une autre, mais voici un exemple avec la contrainte MinLength : Code : Autre @Assert\MinLength(limit=10, message="Le titre doit faire au moins {{ limit }} caractres.")

Oui, vous pouvez utilisez {{ limit }}, qui est en fait le nom de l'option que vous voulez faire apparatre dans le message.

Bien entendu, vous pouvez mettre plusieurs contraintes sur un mme attribut. Par exemple pour un attribut numrique telle une note, on pourrait mettre les deux contraintes suivantes : Code : PHP <?php /** * @Assert\Min(0) * @Assert\Max(20) */ private $note

V ous savez tout ! Il n'y a rien de plus connaitre sur les annotations. A part les contraintes existantes et leurs options videmment.

Liste des contraintes existantes


V oici un tableau qui regroupe la plupart des contraintes, avoir sous la main lorsque vous dfinissez vos rgles de validation ! Elles sont bien entendu toutes documentes, donc n'hsitez pas vous rfrer la documentation officielle pour toute information supplmentaire. Toutes les contraintes disposent de l'option "message", qui est le message afficher lorsque la contrainte est viole. Je n'ai pas rpt cette option dans les tableaux suivants, mais sachez qu'elle existe bien chaque fois. Contraintes de base : Contrainte NotBlank Blank Rle La contrainte NotBlank vrifie que la valeur soumise n'est ni une chaine de caractres vide, ni NULL. La contrainte Blank fait l'inverse. La contrainte True vrifie que la valeur vaut true, 1 ou "1". La contrainte False vrifie que la valeur vaut false, 0 ou "0". La contrainte Type vrifie que la valeur est bien du type donn en argument. Options

True False

Type

type (Option par dfaut) : Le type duquel doit tre la valeur, parmis array, bool , int , object , etc.

Contraintes sur des chaines de caractres : Contrainte Email Rle La contrainte Email vrifie que la valeur est une adresse email valide. Options checkMX (Dfaut : false) : Si dfinit true, Symfony2 va vrifier les MX de l'email via la fonction checkdnsrr.

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


La contrainte MinLength vrifie que la valeur MinLength donne fait au moins X caractres de long. MaxLength MaxLength, au plus X caractres de long. limit (Option par dfaut) : Le seuil de longueur, en nombre de caractres. charset (Dfaut : UTF-8) : Le charset utiliser pour calculer la longueur. protocols (Dfaut : array('http', 'https')) : Dfinit les protocoles considrs comme valide. Si vous voulez accepter les URL en ftp://, ajoutez-le cette option. pattern (Option par dfaut) : La regex faire correspondre. match (Dfaut : true) : Dfinit si la valeur doit (true) ou ne doit pas (false) correspondre la regex. type (Dfaut : 4) : Version de l'IP considrer. 4 pour IPv4, 6 pour IPv6, all pour toutes les versions, et d'autres. -

154/201

Url

La contrainte Url vrifie que la valeur est une adresse URL valide.

Regex

La contrainte Regex vrifie la valeur par rapport une regex. La contrainte Ip vrifie que la valeur est une adresse IP valide. La contrainte Language vrifie que la valeur est un code de langage valide selon la norme. La contrainte Locale vrifie que la valeur est une locale valide. Exemple : fr ou fr_FR. La contrainte Country vrifie que la valeur est un code pays en 2 lettres valide. Exemple : fr.

Ip Language Locale Country

Contraintes sur les nombres : Contrainte Max Min Rle La contrainte Max vrifie que la valeur ne dpasse pas X. Min, que la valeur dpasse ce X. Options limit (Option par dfaut) : La valeur seuil. invalidMessage : Message d'erreur lorsque la valeur n'est pas un nombre.

Contraintes sur les dates : Contrainte Date Time DateTime Rle La contrainte Date vrifie que la valeur est un objet de type Datetime, ou une chane de caractres du type 'YYYY-MM-DD'. La contrainte Time vrifie que la valeur est un objet de type Datetime, ou une chane de caractres du type 'HH:MM:SS'. La contrainte Datetime vrifie que la valeur est un objet de type Datetime, ou une chane de caractres du type 'YYYY-MM-DD HH:MM:SS'. Options -

Les noms de contraintes sont sensible la casse. Cela signifie que la contrainte DateTime existe, mais que Datetime ou datetime n'existent pas ! Soyez attentif ce petit dtail pour viter des erreurs inattendues

Crer ses propres contraintes


Sachez que vous pouvez crer vos propres contraintes en suivant la documentation ce sujet. Nous verrons galement plus loin comment raliser des " contraintes dynamiques" en utilisant une mthode de l'objet valid, trs pratique !

Dclencher la validation Le service Validator


Comme je l'ai dit plus haut, ce n'est pas l'objet qui se valide tout seul, on doit dclencher la validation nous-mme. Ainsi, vous pouvez tout fait assigner une valeur non valide un attribut sans qu'aucune erreur ne se dclenche. Par exemple, vous pouvez faire <?php $article->setTitre('abc') alors que ce titre a moins de 10 caractres, il est invalide. Pour valider l'objet, on passe par un acteur externe : le service validator. Ce service s'obtient comme n'importe quel autre service : Code : PHP <?php // Depuis un contrleur $validator = $this->get('validator');

Ensuite, on doit demander ce service de valider notre objet. Cela se fait grce la mthode validate du service. Cette mthode retourne un tableau qui est soit vide si l'objet est valide, soit rempli des diffrentes erreurs lorsque l'objet n'est pas valide. Pour bien comprendre, excutez cette mthode dans un contrleur : Code : PHP

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


<?php // ... public function testAction() { $article = new Article; $article->setDate(new \Datetime()); $article->setTitre('abc'); incorrect : moins de 10 caractres. //$article->setContenu('blabla'); // incorrect : on ne le dfinit pas. $article->setAuteur('A'); incorrect : moins de 2 caractres. // On rcupre le service validator. $validator = $this->get('validator'); // On dclenche la validation. $liste_erreurs = $validator->validate($article); // Si le tableau n'est pas vide, on affiche les erreurs. if(count($liste_erreurs) > 0) { return new Response(print_r($liste_erreurs, true)); } else { return new Response("L'article est valide !"); } // Champ date O.K. // Champ titre Champ contenu // Champ auteur

155/201

V ous pouvez vous amuser avec le contenu de l'entit Article pour voir comment ragit le validateur

La validation automatique sur les formulaires


V ous devez savoir qu'en pratique, on ne se servira que trs peu du service validator nous-mmes. En effet, le formulaire de Symfony2 le fait notre place ! Nous venons de voir le fonctionnement du service validator pour comprendre comment l'ensemble marche, mais en ralit on l'utilisera trs peu de cette manire. Rappelez-vous le code pour la soumission d'un formulaire : Code : PHP <?php // Dans un handler if( $request->getMethod() == 'POST' ) { $form->bindRequest($request); if( $form->isValid() ) { // ...

Avec la ligne 7, le formulaire $form va lui-mme faire appel au service validator, et valider l'objet qui vient d'tre hydrat par le formulaire. Derrire cette ligne se cache donc le code que nous avons vu au paragraphe prcdent. Les erreurs sont assignes au formulaires, et sont affiches dans la vue. Nous n'avons rien faire, pratique !

Conclusion
Cette sous-partie a pour objectif de vous faire comprendre ce qu'il se passe dj lorsque vous utilisez la mthode isValid d'un formulaire. De plus, vous savez qu'il est possible de valider un objet indpendamment de tout formulaire, en mettant la main la patte.

Encore plus de rgles de validation Valider depuis un Getter


V ous le savez, un getter est une mthode qui commence le plus souvent par "get", mais qui peut galement commencer par "is". Le composant Validation accepte les contraintes sur les attributs, mais galement sur les getters ! C'est trs pratique, car vous pouvez alors mettre une contrainte sur une fonction, avec toute la libert que cela vous apporte. Tout de suite un exemple d'utilisation : Code : PHP <?php class Article { // ...

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


/** * @Assert\True() */ public function isArticleValid() { return false; } }

156/201

Cet exemple vraiment basique considre l'article comme non valide, car l'annotation @Assert\True() attend que la mthode retourne true alors qu'elle retourne false. V ous pouvez l'essayer dans votre formulaire, vous verrez le message "Cette valeur doit tre vraie" (message par dfaut de l'annotation True()) s'affiche en haut du formulaire. C'est donc une erreur qui s'applique l'ensemble du formulaire. Mais il existe un moyen de dclencher une erreur lie un champ en particulier, ainsi l'erreur s'affichera juste ct de ce champ. Il suffit de nommer le getter " is + le nom d'un attribut " : par exemple "isTitre" si l'on veut valider le titre. Essayez par vous-mme le code suivante : Code : PHP <?php class Article { // ... /** * @Assert\True() */ public function isTitre() { return false; } }

V ous verrez que l'erreur "Cette valeur doit tre vraie" s'affiche bien ct du champ titre. Bien entendu, vous pouvez faire plein de traitements et de vrifications dans cette mthode, ici j'ai juste mis " return false" pour l'exemple . Je vous laisse imaginer les possibilits.

Valider intelligemment un attribut objet


Derrire ce titre se cache une problmatique toute simple : lorsque je valide un objet A, comment valider un objet B en attribut, d'aprs ses propres rgles de validation ? Il faut utiliser la contrainte Valid, qui va dclencher la validation du sous objet B selon les rgles de validation de cet objet B. Prenons un exemple : Code : PHP <?php class A { /** * @Assert\MinLength(5) */ private $titre; /** * @Assert\Valid() */ private $b; } class B { /** * @Assert\Min(10) */ private $nombre; }

Avec cette rgle, lorsqu'on dclenche la validation sur l'objet A, le service validator va valider l'attribut titre selon le MinLength(), puis va aller chercher les rgles de l'objet B pour valider l'attribut nombre de B selon le Min(). N'oubliez pas cette contrainte, car valider un sous-objet n'est pas le comportement par dfaut : sans cette rgle dans notre exemple, vous auriez pu sans problme ajouter une instance de B qui ne respecte pas la contrainte de 10 minimum pour son attribut nombre. V ous pourriez donc rencontrer des petits soucis de logique si vous l'oubliez .

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2

157/201

Valider depuis un Callback


L'objectif de la contrainte Callback est d'tre personnalisable souhait. En effet, des fois vous pouvez avoir besoin de valider des donnes selon votre propre logique, qui ne rentre pas dans un Maxlength. L'exemple classique est la censure de mots non dsirs dans un attribut texte. Reprenons notre Article, et considrons que l'attribut contenu ne peut pas contenir les mots "chec" et "abandon". V oici comment mettre en place une rgle qui va rendre invalide le contenu s'il contient l'un de ces mots : Code : PHP <?php namespace Sdz\BlogBundle\Entity; use use use use Doctrine\ORM\Mapping as ORM; Symfony\Component\Validator\Constraints as Assert; Doctrine\Common\Collections\ArrayCollection; Sdz\BlogBundle\Entity\Tag;

// On rajoute ce use pour le context : use Symfony\Component\Validator\ExecutionContext; /** * @ORM\Table() * @ORM\Entity(repositoryClass="Sdz\BlogBundle\Entity\ArticleRepository") * * @Assert\Callback(methods={"contenuValide"}) */ class Article { // ... public function contenuValide(ExecutionContext $context) { $mots_interdits = array('chec', 'abandon'); // On vrifie que le contenu ne contient pas l'un des mots if(preg_match('#'.implode('|', $mots_interdits).'#', $this>getContenu())) { // On dit o est l'erreur avec le ".contenu" $propertyPath = $context->getPropertyPath().'.contenu'; $context->setPropertyPath($propertyPath); // La rgle est viole, on dfinit l'erreur et son message $context->addViolation('Contenu invalide car il contient un mot interdit.', array(), null); } } }

V ous auriez mme pu aller plus loin en comparant des attributs entre eux, par exemple pour interdire le pseudo dans un mot de passe. L'avantage du Callback par rapport une simple contrainte sur un getter, c'est de pouvoir ajouter plusieurs erreur la fois, en dfinissant sur quel attribut chacun se trouve grce au propertyPath (en mettant " .contenu" ou " .titre", etc). Souvent la contrainte sur un getter suffira, mais pensez ce Callback pour les fois o vous serez limit .

Valider un champ unique


Il existe une dernire contrainte trs pratique : UniqueEntity. Cette contrainte permet de valider que la valeur d'un attribut est unique parmi toutes les entits existantes. Pratique pour vrifier qu'une adresse email n'existe pas dj dans la base de donnes par exemple. V ous avez bien lu, j'ai parl d'entit. En effet c'est une contrainte un peu particulire car elle ne se trouve pas dans le composant Validator, mais dans le Bridge entre Doctrine et Symfony2 (ce qui fait le lien entre ces deux librairies). On n'utilisera donc pas @Assert\UniqueEntity mais simplement @UniqueEntity. Il faut bien sr en contrepartie faire attention de rajouter ce use chaque fois que vous l'utilisez : Code : Autre use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

V oici comment on pourrait, dans notre exemple avec Article, contraindre nos titres tre tous diffrents les uns des autres : Code : PHP <?php namespace Sdz\BlogBundle\Entity;

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


use use use use Doctrine\ORM\Mapping as ORM; Symfony\Component\Validator\Constraints as Assert; Doctrine\Common\Collections\ArrayCollection; Sdz\BlogBundle\Entity\Tag;

158/201

// On rajoute ce use pour la contrainte : use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** * @ORM\Table() * @ORM\Entity(repositoryClass="Sdz\BlogBundle\Entity\ArticleRepository") * * @UniqueEntity(fields="titre", message="Un article existe dj avec ce titre.") */ class Article { // ... Les autres contraintes ne changent pas, pas mme celle(s) sur l'attribut titre // Mais pour tre logique, il faudrait aussi mettre la colonne titre en Unique pour Doctrine : /** * @ORM\Column(name="titre", type="string", length=255, unique=true) */ private $titre; } V ous savez maintenant valider dignement vos donnes, flicitations ! Le formulaire tait le dernier point que vous aviez vraiment besoin d'apprendre. partir de maintenant, vous pouvez crer un site Internet en entier avec Symfony2, il ne manque plus que la scurit aborder, car pour l'instant, sur notre blog, tout le monde peut tout faire. Rendez-vous au prochain chapitre pour rgler ce petit dtail.

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2

159/201

Scurit et gestion des utilisateurs


Dans ce chapitre, nous allons apprendre la scurit avec Symfony2. C'est un chapitre assez technique, mais indispensable : la fin nous aurons un espace membre fonctionnel et scuris ! Nous allons avancer en deux tapes : la premire sera consacre la thorie de la scurit sous Symfony2. Ncessaire, elle nous permettra d'aborder la deuxime tape : l'installation du bundle FOSUserBundle, qui viendra complter notre espace membre. Bonne lecture !

Authentification et autorisation
La scurit sous Symfony2 est trs pousse, vous pouvez la contrler trs finement, mais surtout trs facilement. Pour atteindre ce but, Symfony2 a bien spar deux mcanismes diffrents : l'authentification et l'autorisation. Prenez le temps de bien comprendre ces deux notions pour bien attaquer la suite du cours.

Les notions d'authentification et d'autorisation


L'authentification
L'authentification est le processus qui va dfinir qui vous tes, en tant que visiteur. L'enjeu est vraiment trs simple : soit vous ne vous tes pas identifi sur le site et vous tes un anonyme, soit vous vous tes identifi (via le formulaire d'identification ou via un cookie "Se souvenir de moi") et vous tes un membre du site. C'est ce que la procdure d'authentification va dterminer. Ce qui gre l'authentification dans Symfony2 s'appelle un firewall . Ainsi vous pourrez scuriser des parties de votre site internet juste en forant le visiteur tre un membre authentifi. Si le visiteur l'est, le firewall va le laisser passer, sinon, le firewall va le rediriger sur la page d'identification. Cela se fera donc dans les paramtres du firewall , nous les verrons plus en dtails par la suite.

L'autorisation
L'autorisation est le processus qui va dterminer si vous avez le droit de faire l'action que vous voulez faire. Il agit donc aprs le firewall . Ce qui gre l'autorisation dans Symfony2 s'appelle l'Access Control . Par exemple, un membre identifi lambda aura accs la liste de sujets d'un forum, mais ne pourra pas supprimer de sujet. Seuls les membres disposant des droits administrateurs le peuvent, ce que l'Access Control va vrifier.

Exemples
Pour bien comprendre la diffrence, je reprends ici l'exemple de la documentation officielle, qui est je trouve trs bien illustr. Dans ces exemples, vous distinguerez bien les diffrents acteurs de la scurit.

Accs une page anonymement et sans droits particuliers

Sur ce schma, vous distinguez bien le firewall d'un ct, et l'Access Control de l'autre. C'est trs clair, mais reprenons le ensemble pour bien comprendre : 1. Le visiteur n'est pas identifi, et tente d'accder la page /foo ; 2. Le Firewall est configur d'une manire qu'il n'est pas ncessaire d'tre identifi pour accder la page /foo. Il laisse donc passer notre visiteur ; 3. L'Access Control regarde s'il y a besoin de droit particulier pour accder la page /foo : non il n'y en a pas. Il laisse donc passer notre visiteur ; 4. Le visiteur a atteint notre application, et il reoit la page /foo en retour.

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


Accs une page anonymement mais qui ncessite des droits

160/201

Dans ce cas prcis, la page /admin/foo pourrait tre visit de faon anonyme (le firewall ne bloque pas l'utilisateur), mais il faut par contre possder les droits admin, reprsent par le rle ROLE_ADMIN. Usuellement on le verra, on bloque l'administration directement via le firewall, mais c'est un exemple. V oici le process pas pas : 1. Le visiteur n'est pas identifi, et tente d'accder la page /admin/foo ; 2. Le Firewall est configur d'une manire qu'il n'est pas ncessaire d'tre identifi pour accder la page /admin/foo. Il laisse donc passer notre visiteur ; 3. L'Access Control regarde s'il y a besoin de droit particulier pour accder la page /admin/foo : oui, il faut le rle ROLE_ADMIN. Le visiteur n'a pas ce rle, donc l'Access Control lui bloque l'accs la page /admin/foo ; 4. Le visiteur n'a donc pas atteint la page qu'il cherchait, et se fait renvoyer sur la page d'identification.

Accs une page de faon identifi mais sans les droits suffisants
Cet exemple est le mme que prcdemment, sauf que notre visiteur est identifi, il s'appelle Ryan.

1. Ryan s'identifie (via un cookie par exemple), et il tente d'accder la page /admin/foo ; D'abord, le firewall confirme l'authentification de Ryan (c'est son rle !). Visiblement c'est bon. Ensuite, on ne sait pas ici de quelle manire est configur le firewall pour la page /admin/foo, mais comme Ryan est identifi, il ne le bloque pas ; 2. L'Access Control regarde s'il y a besoin de droit particulier pour accder la page /admin/foo : oui, il faut le rle ROLE_ADMIN, que Ryan n'a pas. Il bloque donc l'accs la page /admin/foo Ryan ;

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


3. Ryan n'a pas atteint la page qu'il cherchait, et se fait renvoyer sur une page d'erreur lui disant qu'il n'a pas les droits suffisant ("Accs interdit").

161/201

Les diffrents acteurs de la scurit Symfony2 Le fichier de configuration de la scurit


Maintenant qu'on a vu le processus global, il nous faut tudier plus en dtail comment fonctionne chaque acteur. Pour cela, ouvrez le fichier app/config/security.yml, car tous les acteurs sont paramtrs dans ce fichier de configuration. Si vous ne l'avez pas encore modifi, voici quoi il ressemble : Code : Autre # app/conig/security.yml security: encoders: Symfony\Component\Security\Core\User\User: plaintext role_hierarchy: ROLE_ADMIN: ROLE_USER ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] providers: in_memory: users: user: { password: userpass, roles: [ 'ROLE_USER' ] } admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] } firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false login: pattern: ^/demo/secured/login$ security: false secured_area: pattern: ^/demo/secured/ form_login: check_path: /demo/secured/login_check login_path: /demo/secured/login logout: path: /demo/secured/logout target: /demo/ #anonymous: ~ #http_basic: # realm: "Secured Demo Area" access_control: #{ path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https } #{ path: ^/_internal, roles: IS_AUTHENTICATED_ANONYMOUSLY, ip: 127.0.0.1 }

Maintenant, voyons un un les acteurs et leur configuration.

Les acteurs de l'authentification


Les firewalls
Comme on l'a vu, un firewall cherche vrifier que vous tes bien celui que vous prtendez tre. Je dis bien un firewall car on peut en dfinir plusieurs, selon l'URL sur votre site. V ous pouvez en distinguer trois dans le fichier de configuration : "dev", "login" et "secured_area" (les noms sont arbitraires). Intrt ? Exemple : j'ai un firewall "main" pour tout mon site, et un firewall "admin" pour la partie administration de mon site, dont l'URL commence par /admin. Cette multiplicit des firewalls est importante, car elle vous permettra de dfinir plusieurs zones de scurit votre site. Enfin, chaque firewall a besoin d'tre li une source d'utilisateurs. Ils ont besoin de savoir o aller chercher les utilisateurs, pour vrifier qu'ils sont bien identifi. Ces sources d'utilisateurs peuvent tre multiples : inscrits directement dans le fichier de configuration, depuis une entit Doctrine2, ou mme depuis n'importe quelle autre source. Ces sources s'appellent des providers, nous en reparlons plus loin. Comme vous pouvez un peu le deviner depuis le fichier de configuration, un firewall se dfinit par : Un pattern, qui est une expression rgulire sur les URL protger avec ce firewall. Des informations de connexion, dfinit par form_login dans le fichier de configuration. En effet un firewall a besoin de quelques informations pour la connexion de nos membres :

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


l'URL de la page de connexion (login_path), c'est l'URL sur laquelle se trouve le formulaire de connexion pour ce firewall. Cette page n'est pas gre par Symfony2, il faut soit la faire la main soit utiliser un bundle qui va la faire pour nous. Nous utiliserons le bundle FOSUserBundle. l'URL cible du formulaire de connexion (check_path), c'est l'URL laquelle sera envoy le formulaire de connexion, et qui vrifiera les identifiants renseigns par l'utilisateur. Cette page, elle, est dj gre par le firewall , nous n'aurons pas besoin de l'implmenter. Par contre, pour que Symfony2 sache o sont nos utilisateurs, il faut le lui dire via l'option provider. Un provider, c'est l'origine de nos utilisateurs. Ici cette option n'est pas renseign, Symfony2 va donc prendre le seul provider dfinit dans la section providers un peu plus haut dans le fichier. On reparle de ces providers juste aprs, mais sachez qu'au mme titre que les firewalls, on pourra avoir plusieurs providers (un par firewall par exemple). Et d'autres options que nous verrons plus loin. Ne vous inquitez pas de la syntaxe prcise de chacune de ces options, on fait une synthse du fichier security.yml plus loin.

162/201

Les providers
Comme on l'a dj vu, un provider est une source d'utilisateurs, l o les firewalls peuvent aller chercher les utilisateurs pour les identifier. Pour l'instant vous pouvez le voir dans le fichier, un seul provider est dfini, nomm "in_memory" (encore une fois, le nom est arbitraire). C'est un provider assez particulier dans le sens o les utilisateurs sont directement lists dans ce fichier de configuration, il s'agit des utilisateurs "user" et "admin". V ous l'aurez compris, c'est un provider de dveloppement, pour tester firewalls et autres sans avoir besoin d'une quelconque base de donnes derrire. Heureusement il existe d'autres formes de provider : La premire est de donner une entit Symfony2, et le firewall saura aller chercher en base de donnes les utilisateurs en utilisant son repository. Nous n'utiliserons pas cette mthode mais sachez juste qu'elle existe, et elle est documente. La deuxime est d'utiliser ce qu'on appelle un UserProvider. C'est un service qui permet de personnaliser comment les firewalls accdent vos utilisateurs. V ous pouvez crire ce service vous-mme, mais sachez qu'il en existe un dans le bundle FOSUserBundle et qu'on n'aura pas le faire. Concrtement, il ne contient en gros qu'une seule mthode qui retourne un utilisateur en fonction d'un nom d'utilisateur (via une requte en base de donnes par exemple). Un utilisateur est un objet quelconque qui implmente l'interface Symfony\Component\Security\Core\User\UserInterface. Pour en savoir plus sur les UserProviders, je vous invite lire le cookbook de la documentation officielle, il est vraiment intressant !

L'encoder
L'encoder est un service qui va encoder les mots de passe des utilisateurs. Il permet de personnaliser la faon dont vous voulez hasher les mots de passe. V ous pouvez deviner dans le fichier que l'encoder utilis, "plaintext", n'encode en ralit rien du tout. Il stocke les mots de passe en clair, c'est pourquoi les mots de passe du provider "in_memory" sont en clair . Par la suite nous dfinirons un encoder du type sha512, une mthode sre !

Les acteurs de l'autorisation


Les rles
Les rles sont des acteurs passifs de l'autorisation. La notions de rle et autorisation est trs simple : lorsqu'on va limiter l'accs certains pages, on va se baser sur les rles de l'utilisateur. Ainsi, limiter l'accs au panel d'administration revient limiter cet accs aux utilisateurs disposant du rle ROLE_ADMIN (par exemple). Le nom des rles n'a pas d'importance, si ce n'est qu'il doit commencer par "ROLE_". Commenons par dfinir les rles de l'application. Prenez un papier et listez tous les rles dont vous aurez besoin. Par exemple pour notre blog, on aurait : ROLE_ADMIN, pour les administrateurs du blog ROLE_AUTEUR, pour les auteurs, ceux qui peuvent ajouter des articles au blog ROLE_MODERATEUR, pour ceux qui peuvent modrer les commentaires V ous pouvez en avoir d'autres videmment ! Maintenant l'ide est de crer une hirarchie entre ces rles. Ainsi, on peut sans difficult dire que le ROLE_ADMIN comprend les ROLE_AUTEUR et ROLE_MODERATEUR. Pour limiter l'accs certaines pages dans notre code, on ne va donc pas faire " Si l'utilisateur a ROLE_AUTEUR ou s'il a ROLE_ADMIN, alors il peut crire un article ". Mais grce la dfinition de la hirarchie, on peut faire simplement " Si l'utilisateur a ROLE_AUTEUR ". Car un utilisateur qui dispose du ROLE_ADMIN dispose galement du ROLE_AUTEUR, c'est une inclusion. Ce sont ces relations, et uniquement ces relations, que nous allons inscrire dans le fichier app/config/security.yml.

L'Access Control
Comme on l'a vu, l'Access Control va s'occuper de savoir si le visiteur a les bons droits pour accder la page demande. Il y a diffrents moyens d'utiliser l'access control : Soit depuis le fichier de configuration security.yml, en appliquant des rgles sur des URL. V ous pouvez le voir dans le fichier, les deux dernires lignes commentes servent protger des URL. On peut bien sr utiliser des expressions

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


rgulires, ce qui permet d'appliquer une rgle un ensemble d'URL. Soit directement depuis les contrleurs, en appliquant des rgles sur des mthodes. On peut ainsi appliquer des rgles diffrentes selon des paramtres, c'est libre. Il existe d'ailleurs le bundle JMSSecurityExtraBundle, inclut par dfaut dans Symfony2, qui permet de dfinir ces rgles via le systme d'annotations. La diffrence entre ces deux moyens d'utiliser la mme protection est importante, et offre en fait une flexibilit intressante.

163/201

Installation du bundle FOSUserBundle


Comme vous avez pu le voir, la scurit fait intervenir de nombreux acteurs et demande un peu de travail. C'est normal, c'est un point sensible d'un site internet. Heureusement, d'autres dveloppeurs talentueux ont russi nous faciliter la tche en crant un bundle qui gre une partie de la scurit ! Ce bundle s'appelle FOSUserBundle, il est trs utilis par la communaut Symfony2 car vraiment bien fait, et surtout rpondant un besoin vraiment basique d'un site internet : l'authentification des membres. Mettre en place ce bundle est par contre vraiment facile, rien voir avec une mthode maison . Je vous propose donc d'installer ce bundle dans la suite de cette sous-partie.

Tlcharger FOSUserBundle
Le bundle FOSUserBundle est hberg sur www.github.com, comme beaucoup de bundles et projets Symfony2. Sa page est ici : https://github.com/FriendsOfSymfony/FOSUserBundle Pour le tlcharger rien de plus simple, cliquez sur le lien Download et choisissez "Download as zip" (ou en tar.gz suivant vos prfrences). Une fois l'archive tlcharge, il faut l'extraire dans le rpertoire vendor/bundles/FOS/UserBundle. Crez-le, priori il n'existe pas encore. Vrifiez bien que vous avez le fichier FOSUserBundle.php (le fichier principal du bundle, rappelez-vous) dans le rpertoire que je vous ai dit.

Activer le bundle
Si vos souvenirs sont bons, vous devriez savoir qu'un bundle ne s'active pas tout seul, il faut aller l'enregistrer dans le noyau de Symfony2. Pour cela, ouvrez d'abord le fichier app/autoload.php pour enregistrer le namespace FOS : Code : PHP <?php // app/autoload.php $loader->registerNamespaces(array( // ... 'FOS' => __DIR__.'/../vendor/bundles', ));

Puis ouvrez le fichier app/AppKernel.php pour enregistrer le bundle : Code : PHP <?php // app/AppKernel.php public function registerBundles() { $bundles = array( // ... new FOS\UserBundle\FOSUserBundle(), ); }

Inutile d'essayer d'accder votre application Symfony2 maintenant, elle ne marchera pas. Il faut en effet faire un peu de configuration et de personnalisation avant de pouvoir tout remettre en marche.

Personnaliser FOSUserBundle
Ce bundle est un bundle gnrique videmment, car il doit pouvoir s'adapter tout type d'utilisateur de n'importe quel site internet. V ous imaginez bien que cela est impossible "out-of-the-box" ! Il faut donc s'atteler faire un peu de personnalisation afin de faire correspondre le bundle nos besoins. Cette personnalisation passe par la cration d'un bundle qui va hriter de FOSUserBundle.

Crer notre propre SdzUserBundle


On va pour cela crer notre propre bundle, qui va hriter de FOSUserBundle, et on va ainsi pouvoir personnaliser peu prs tout. Crer un petit bundle est vraiment simple, et vous devez savoir le faire ce stade. Attention ici on n'utilise pas le gnrateur, car il gnre trop de fichiers par rapport ce que l'on veut ici. On va donc le faire totalement la main, voici les tapes : Crez le rpertoire src/Sdz/UserBundle ; Mettez y un fichier SdzUserBundle.php, dans lequel vous mettez un copier-coller de notre SdzBlogBundle.php (dans src/Sdz/BlogBundle/SdzBlogBundle.php) ; Le namespace Sdz est dj enregistr, pas besoin de modifier le app/autoload.php ; Par contre il faut enregistrer le bundle dans app/AppKernel.php.

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2

164/201

Pourquoi crer notre propre bundle au lieu de modifier FOSUserBundle directement ? Parce qu'il ne faut jamais modifier des fichiers dans le rpertoire vendor/ ! En effet, vous perdriez toutes vos modifications la prochaine mise jour du bundle. Et puis a ne serait pas trs beau, tout ce qui nous concerne doit se trouver dans src/ et pas autre part. Cela prend certes un peu plus de temps, mais c'est ncessaire pour garder un code propre et dcoupl . Afin de dire Symfony2 que notre nouveau bundle SdzUserBundle doit hriter de FOSUserBundle, il faut simplement modifier le SdzUserBundle.php comme suit : Code : PHP <?php // src/Sdz/UserBundle/SdzUserBundle.php namespace Sdz\UserBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; class SdzUserBundle extends Bundle { public function getParent() { return 'FOSUserBundle'; } }

Et c'est tout ! On a juste rajout cette mthode getParent(), et Symfony2 va savoir grer le reste

Cration de l'entit User


Bon pour l'instant on a beaucoup jou mais on n'a pas fait grand chose hein. Attaquons donc la vraie personnalisation du bundle. La seule chose obligatoire est de crer sa propre entit User, l'entit qui va reprsenter nos membres. Encore une fois c'est trs simple, crez un fichier User.php dans le rpertoire src/Sdz/UserBundle/Entity avec : Code : PHP <?php // src/Sdz/UserBundle/Entity/User.php namespace Sdz\UserBundle\Entity; use FOS\UserBundle\Entity\User as BaseUser; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Table(name="sdz_user") */ class User extends BaseUser { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ protected $id; }

Alors c'est joli, mais pourquoi est-ce que l'on a fait a ? En fait, le bundle FOSUserBundle ne dfinit pas vraiment l'entit User, il dfinit une "Mapped Superclass" ! Un nom un peu barbare juste pour dire que c'est une entit abstraite, et qu'il faut en hriter pour faire une vraie entit. C'est donc ce que nous venons juste de faire. Cela permet en fait de garder la main sur notre entit. On peut ainsi lui rajouter des attributs, en plus de ceux dj dfinis. Pour info les attributs qui existent dj sont : username : nom d'utilisateur avec lequel l'utilisateur va s'identifier email : l'adresse email enabled : true ou false suivant que l'inscription de l'utilisateur a t valid ou non (dans le cas d'une confirmation par email par exemple) password : le mot de passe de l'utilisateur lastLogin : la date de la dernire connexion locked : si vous voulez dsactiver des comptes expired : si vous voulez que les comptes expirent au dela d'une certaine dure Je vous en passe certains qui sont plus un usage interne. Sachez tout de mme que vous pouvez tous les retrouver dans la dfinition de la mapped-superclass, qui se trouve ici : https://github.com/FriendsOfSymfony/FO [...] /User.orm.xml V ous pouvez rajouter ds maintenant des attributs votre entit User, comme vous l'avez fait au chapitre Doctrine2.

Configuration du bundle

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


Ensuite, nous devons dfinir certains paramtres obligatoire au fonctionnement de FOSUserBundle. Ouvrez votre app/config/config.yml et rajoutez la section suivante : Code : Autre # app/config/config.yml # ...

165/201

fos_user: db_driver: orm # Le type de BDD utiliser, nous utilisons l'ORM Doctrine depuis firewall_name: main # Le nom du firewall duquel on utilisera ces utilisateurs user_class: Sdz\UserBundle\Entity\User # La classe de l'entit User que nous venons de dfinir

Et voil, on a bien install FOSUserBundle ! Avant d'aller plus loin, crons la table User et ajoutons quelques membres pour les tests.

Cration de la table User


Pour cela rien de plus simple, ressortez votre console et tapez la commande php app/console doctrine:schema:update --force . Et voil, votre table est cre ! Il existe galement une commande fournie par le bundle qui permet d'ajouter des utilisateurs, on l'utilise un peu plus bas. On a fini d'initialiser le bundle. Bon bien sr pour l'instant Symfony2 ne l'utilise pas encore, il manque un peu de configuration, attaquons-la.

Configuration de la scurit avec FOSUserBundle


Maintenant on va reprendre tous nos acteurs et les configurer un un en utilisant le bundle ds que l'on peut. Reprenez le security.yml sous la main, et c'est parti !

Configuration de la scurit
L'encoder
L'encoder est celui qui va hasher les mots de passe avant de les stocker. On utilise couramment la mthode sha512. Dans la configuration, on peut dterminer un encoder par interface utilise pour l'objet User. Dfinissez donc l'encoder suivant : Code : Autre # app/config/security.yml security: encoders: "FOS\UserBundle\Model\UserInterface": sha512

Le UserProvider
Je vous l'ai dit galement, FOSUserBundle inclut un UserProvider qui va fournir les utilisateurs de type User qu'on a dfinit plus haut. Pour cela, dfinissez le UserProvider comme suit : Code : Autre # app/config/security.yml security: # ... providers: fos_userbundle: id: fos_user.user_manager

Dans cette configuration, "fos_userbundle" est le nom du provider dfinit, et "fos_user.user_manager" est le nom du service fournit par le bundle FOSUB qui agit comme un provider. Si vous voulez changer de provider, vous devrez donc crer votre propre service, et changer le nom du service utilis ici dans la configuration. Dans la partie du firewall , on utilisera donc "fos_userbundle" comme UserProvider.

Les firewalls
Laissez la sous-partie dev, elle correspond la dsactivation de toute scurit pour l'accs au profiler (la barre d'information en bas), ainsi qu'aux ressources css, image et js, jusque l tout va bien . Par contre supprimez les deux autres parties login et secured_area, on va dfinir notre propre firewall. L'ide est vraiment simple, copiez-collez les firewalls "login" et "main" suivant : Code : Autre # app/config/security.yml

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


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

166/201

# Firewall pour les pages de connexion, inscription, et rcupration de mot de passe login: pattern: ^/(login$|register|resetting) # Les adresses de ces pages sont login, register et reset anonymous: true # On autorise bien videmment les anonymes sur ces pages # Firewall principal main: pattern: ^/ form_login: provider: remember_me: remember_me: key: anonymous: logout: pour le reste de notre site # # # # ^/ On On On = tout ce qui commence par / = tout notre site dfinit notre mthode d'authentification lit l'authentification au provider dfinit plus haut active la possibilit du "Se souvenir de moi" (dsactiv par

fos_userbundle true %secret% true true

# On dfinit la cl pour le remember_me (%secret% est un parametre # On autorise les utilisateurs anonymes (non identifis) # On autorise la dconnexion manuelle (dsactiv par dfaut)

Je vous ai dj expliqu tous les paramtres, et bien comment le fichier. Ce n'est vraiment que de la syntaxe connaitre, je n'ai pas besoin de vous le dtailler plus. Le choix du firewall appliquer la requte en cours se fait comme pour le choix des routes : premier arriv premier servi ! Ainsi, lorsqu'un utilisateur veut accder la page /login, le composant Security de Symfony2 va d'abord voir si le firewall "dev" correspond, grce son paramtre pattern. En l'occurence non. Il passe donc au suivant, le firewall "login", et refait le test. Cette fois-ci c'est le bon, il applique les paramtres de ce firewall (en l'occurence, anonymes accepts). Le firewall "main" n'est donc pas utilis pour cette requte. D'o l'importance de l'ordre des firewalls, gardez-le en tte .

Configuration de la scurit : check


Et voil votre site est prt tre scuris ! En effet on a fini la configuration des firewalls et de tous les autres acteurs. Alors videmment il n'y a pas d'impact sur votre site car pour l'instant on a autoris les anonymes, donc le firewall main ne refuse personne. Essayez de changer le paramtres anonymous du firewall main en false, et regardez l'effet. Ah oui, pas de route pour /login hein ? C'est dj une bonne nouvelle, Symfony2 a remarqu qu'on tait un utilisateur anonyme, essayant de se connecter une page qui les refuse. Il nous a redirig vers la page d'identification, c'est une bonne chose ! Rassurez-vous le formulaire de connexion, comme bien d'autres pages (inscription, rcupration de mot de passe, etc.) sont inclus dans le bundle. On a juste paramtrer les routes. Ici soyez bien conscient de la transition : nous avons fini de configurer la couche scurit de Symfony2, mais il nous manque un peu de configuration pour le bundle FOSUserBundle.

Configuration du bundle FOSUserBundle


Configuration des routes
En plus de grer les mcanismes internes, le bundle FOSUserBundle gre aussi les pages classiques comme la page de connexion, celle d'inscription, etc. Pour toutes ces pages, il faut videmment enregistrer les routes correspondantes. Les dveloppeurs du bundle ont volontairement clat toutes les routes dans plusieurs fichiers pour pouvoir personnaliser facilement toutes ces pages. Pour l'instant, on veut juste les rendre disponible, on les personnalisera plus tard. Rajoutez donc dans votre app/config/routing.yml les imports suivant la suite du notre : Code : Autre # app/config/routing.yml # ... fos_user_security: resource: "@FOSUserBundle/Resources/config/routing/security.xml" fos_user_profile: resource: "@FOSUserBundle/Resources/config/routing/profile.xml" prefix: /profile fos_user_register: resource: "@FOSUserBundle/Resources/config/routing/registration.xml" prefix: /register fos_user_resetting: resource: "@FOSUserBundle/Resources/config/routing/resetting.xml" prefix: /resetting fos_user_change_password: resource: "@FOSUserBundle/Resources/config/routing/change_password.xml" prefix: /change-password

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2

167/201

V ous remarquez que les routes sont dfinies en XML et non en YML comme on en a l'habitude dans ce tutoriel. En effet je vous en avais parl tout au dbut, Symfony2 permet d'utiliser plusieurs mthodes pour les fichiers de configuration : YML, XML et mme PHP, au choix du dveloppeur. Ouvrez ces fichiers de routes pour voir quoi ressemblent des routes en XML. C'est quand mme moins lisible qu'en YML, c'est pour cela qu'on a choisit YML au dbut . Ouvrez vraiment ces fichiers pour connaitre les routes qu'ils contiennent. V ous saurez ainsi faire des liens vers toutes les pages que gre le bundle. Inutile de rinventer la roue ! V oici quand mme un extrait de la commande php app/console router:debug pour les routes qui concernent ce bundle : Code : Autre fos_user_security_login fos_user_security_check fos_user_security_logout fos_user_profile_show fos_user_profile_edit fos_user_registration_register fos_user_registration_check_email fos_user_registration_confirm fos_user_registration_confirmed fos_user_resetting_request fos_user_resetting_send_email fos_user_resetting_check_email fos_user_resetting_reset fos_user_change_password password ANY ANY ANY GET ANY ANY GET GET GET GET POST GET GET|POST GET|POST /login /login_check /logout /profile/ /profile/edit /register/ /register/check-email /register/confirm/{token} /register/confirmed /resetting/request /resetting/send-email /resetting/check-email /resetting/reset/{token} /change-password/change-

Tester l'authentification
Maintenant que la route pour le formulaire de connexion est configure, vous pouvez tester l'authentification ! Pour pouvoir la tester, on va ajouter ds maintenant 1 ou 2 utilisateurs. N'ouvrez surtout pas PhpMyAdmin, FOSUserBundle dispose de commandes en console qui vont nous faciliter la vie . Excutez donc la commande php app/console fos:user:create et laissez-vous guider pour ajouter facilement un utilisateur. V ous pouvez recommencer pour en ajouter d'autres. V ous pouvez par contre aller voir le rsultat dans la table via PhpMyAdmin, le bundle a bien tout rempli, hash le mot de passe, etc., trs pratique ! Maintenant qu'on a des utilisateurs, actualisez l'application... Et voil ! Le firewall vous a redirig sur la page /login (sinon vrifiez bien que vous avez mis le paramtre anonymous du firewall false), et si vous rentrez le nom d'utilisateur et le mot de passe que vous venez de dfinir, vous pourrez vous connectez !

Il reste quelques petits dtails grer comme... la page de login qui n'est pas la plus sexy, sa traduction, et aussi un bouton "Dconnexion" parce que changer manuellement l'adresse en /logout, c'est pas super user-friendly !

Personnalisation esthtique du bundle


Heureusement tout cela est assez simple. Attention, la personnalisation esthtique que nous allons faire ne concerne en rien la scurit proprement parler. Soyez bien conscient de la diffrence !

Intgrer le formulaire de connexion dans notre layout


FOSUserBundle utilise un layout volontairement simpliste, parce qu'il a vocation tre cras par le notre. Le layout actuel est le suivant : https://github.com/FriendsOfSymfony/FO [...] out.html.twig On va donc tout simplement le remplacer par un template Twig qui va tendre notre layout nous (qui est dans app/Resources/views/layout.html.twig rappelez-vous). Pour "remplacer" le layout de FOSUB, on va utiliser l'un des avantages d'avoir hrit de ce bundle dans le notre. Placez-vous dans notre bundle, dans src/Sdz/UserBundle/Resources/views (crez le rpertoire), et crez le fichier layout.html.twig suivant : Code : HTML & Django {# src/Sdz/UserBundle/Resources/views/layout.html.twig #} {# On tend notre layout nous #} {% extends "::layout.html.twig" %} {# Dans notre layout, il faut dfinir le block body #} {% block body %} {# On y place le contenu du layout par dfaut de FOSUB : https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/views/layout.html.twig #} <div> {% if is_granted("IS_AUTHENTICATED_REMEMBERED") %}

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


{{ 'layout.logged_in_as'|trans({'%username%': app.user.username}, 'FOSUserBundle') }} | <a href="{{ path('fos_user_security_logout') }}"> {{ 'layout.logout'|trans({}, 'FOSUserBundle') }} </a> {% else %} <a href="{{ path('fos_user_security_login') }}">{{ 'layout.login'|trans({}, 'FOSUserBundle') }}</a> {% endif %} </div> {% for key, message in app.session.getFlashes() %} <div class="{{ key }}"> {{ message|trans({}, 'FOSUserBundle') }} </div> {% endfor %} <div> {% block fos_user_content %} {% endblock fos_user_content %} </div> {% endblock %}

168/201

Et voil, si vous actualisez la page /login (aprs vous tes dconnect via /logout videmment), vous verrez que le formulaire de connexion est parfaitement intgr dans notre design ! V ous pouvez galement tester la page d'inscription sur /register, qui est bien intgre aussi. V otre layout n'est pas pris en compte ? N'oubliez jamais d'excuter la commande php app/console cache:clear lorsque vous avez des erreurs qui vous tonnent !

Traduire les messages


FOSUB tant un bundle international, le texte est gr par le composant de traduction de Symfony2. Par dfaut, celui-ci est dsactiv. Pour traduire le texte il suffit donc de l'activer, direction app/config/config.yml et dcommentez une des premires ligne dans framework : Code : Autre # app/config/config.yml framework: translator: { fallback: %locale% }

O %locale% est un paramtre dfini dans app/config/parameters.ini, et que vous pouvez mettre 'fr' si ce n'est pas dj fait. Ainsi, tous les messages utiliss par FOSUserBundle seront traduits. Mais il y a un autre type de message traduire, celui du composant Scurit de Symfony2. C'est lui qui dit que le nom d'utilisateur ou le mot de passe est invalide, lorsque vous rentrez des mauvais identifiants dans le formulaire de connexion. Pour traduire ce message " Bad credentials ", nous devons travailler un peu plus. Dans un premier temps, crons le fichier de traduction placer dans Resources/translations/, et appelons-le SdzUserBundle.fr.yml. Le nom " SdzUserBundle" est libre, mais la convention est d'y mettre le nom du bundle courant. Le " .fr" par contre est une obligation qui permet Symfony2 de choisir le fichier de langue suivant le paramtre %locale% (que nous avons dfini 'fr' !). Le ".yml" est toujours le format de configuration, que vous pouvez librement choisir. Dans ce fichier, il faut simplement dfinir les traductions comme ceci : Code : Autre # src/Sdz/UserBundle/Resources/translations/SdzUserBundle.fr.yml "Bad credentials": "Pseudo ou mot de passe incorrect"

Mais ce n'est pas suffisant. En effet, le template par dfaut utilis par FOSUserBundle ne demande pas au composant Translator de Symfony2 de traduire ce message. Il faut donc qu'on le lui dise. Copiez-collez la vue du formulaire de connexion (Security/login.html.twig) depuis FOSUserBundle dans votre propre SdzUserBundle (comme on vient de le faire pour le layout ), puis modifiez-la comme cela : Code : HTML & Django {# src/Sdz/UserBundle/Resources/view/Security/login.html.twig #} {% extends "FOSUserBundle::layout.html.twig" %} {% block fos_user_content %} {% if error %} <div>{{ error|trans([], 'SdzUserBundle') }}</div> {% endif %} {# ... le reste de la vue #}

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2

169/201

La seule modification qu'on a faite c'est d'appliquer le filtre Twig " trans" la variable error. Cela permet de traduire la variable selon le fichier de traductions donn en argument, le SdzUserBundle, qui ici n'est pas le nom du bundle mais le nom du fichier (videmment, ce sont les mmes dans notre cas). Et voil, maintenant le message du composant Security est bien traduit !

Mettre une barre utilisateur


Pour afficher le nom de l'utilisateur courant, et un petit lien dconnexion, le mieux est de se servir dans le layout de FOSUB. Je vous dcortique la structure : Code : HTML & Django {# app/Resources/views/layout.html.twig #} {# IS_AUTHENTICATED_REMEMBERED correspond un utilisateur qui s'est identifi en rentrant son mot de passe, ou un utilisateur qui s'est identifi automatiquement grce son cookie remember_me #} {# On pourrait utiliser IS_AUTHENTICATED_FULLY qui correspond un utilisateur qui s'est forcment identifi en rentrant son mot de passe (utile pour les oprations sensibles comme changer d'email #} {% if is_granted("IS_AUTHENTICATED_REMEMBERED") %} Connect en tant que {{ app.user.username }} - <a href="{{ path('fos_user_security_logout') }}">Dconnexion</a> {% else %} <a href="{{ path('fos_user_security_login') }}">Connexion</a> {% endif %}

Adaptez et mettez ce code dans votre layout, effet garanti

Pour connaitre le nom des routes de FOSUserBundle, je n'ai rien invent, il faut juste aller dans ses fichiers de routing (dans vendor/bundles/FOS/UserBundle/Resources/config/routing/*) comme je vous ai dit. Je vous laisse faire toute cette personnalisation, vous avez maintenant toutes les cls en main .

Gestion des autorisations avec les rles


Dans cette sous-partie nous allons nous occuper de la deuxime phase de la scurit : l'autorisation. C'est une phase bien plus simple grer heureusement, il suffit juste de demander tel(s) droit(s) l'utilisateur courant (identifi ou non). Encore une fois, on va se servir d'un bundle trs pratique pour nous simplifier la vie. Celui-ci est dj intgr dans la distribution standard de Symfony2, il s'agit de JMSSecurityExtraBundle.

Dfinition des rles


Rappelez-vous, on a crois les rles dans le fichier security.yml. La notions de rle et autorisation est trs simple, lorsqu'on va limiter l'accs certains pages, on va se baser sur les rles de l'utilisateur. Ainsi, limiter l'accs au panel d'administration revient limiter cet accs aux utilisateurs disposant du rle ROLE_ADMIN (par exemple). Maintenant l'ide est de crer une hirarchie entre ces rles. Ainsi, en reprenant nos rles dfinis plus haut, on peut sans difficult dire que le ROLE_ADMIN comprend les ROLE_AUTEUR et ROLE_MODERATEUR. Ainsi, pour limiter l'accs certaines pages dans notre code, on ne va pas faire " Si l'utilisateur a ROLE_AUTEUR ou s'il a ROLE_ADMIN, alors il peut crire un article". Mais grce la dfinition de la hirarchie, on peut faire simplement " Si l'utilisateur a ROLE_AUTEUR ". Car un utilisateur qui dispose du ROLE_ADMIN dispose galement du ROLE_AUTEUR, c'est une inclusion. Ce sont ces relations, et uniquement ces relations, que nous allons inscrire dans le fichier app/config/security.yml. Nous avions dj mis quelques rles et leurs relations, vous pouvez donc l'adaptez aux nouveaux rles que vous venez de dfinir. V oici ce que cela donne avec nos quelques rles d'exemple : Code : Autre # app/config/security.yml

security: role_hierarchy: ROLE_ADMIN: [ROLE_AUTEUR, ROLE_MODERATEUR] # On a dit que l'admin hritait des droits d'aute ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] # On garde ce rle superadmin, il nous resservira

Remarquez que j'ai supprim le rle ROLE_USER, qui n'est pas toujours utile. Avec cette hirarchie, voici des exemples de tests que l'on peut faire : Si l'utilisateur a le rle ROLE_AUTEUR, alors il peut crire un article. Les auteurs et les admins peuvent donc le faire. Si l'utilisateur a le rle ROLE_ADMIN, alors il peut supprimer un article. Seuls les admins peuvent donc le faire. Si l'utilisateur est identifi (via le firewall ou via le rle spcial IS_AUTHENTICATED_REMEMBERED comme on l'a vu), alors il peut lire les articles. Tous ces tests nous permettrons de limiter l'accs nos diffrentes pages.

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2

170/201

Tester les rles de l'utilisateur


L'ide maintenant est de pouvoir tester si l'utilisateur courant dispose de tel ou tel rle. Ce afin de lui permettre l'accs la page, ou de lui afficher ou non un certain lien, etc. Libre cours votre imagination . Il existe trois mthodes pour faire ce test : les annotations, le service security.context, ou Twig. Ce sont trois faons de faire exactement la mme chose.

Utiliser directement le service security.context


Ce n'est pas le moyen le plus court, mais c'est celui par lequel passe les deux autres mthodes. Il fallait donc que je vous en parle ! Depuis votre contrleur ou n'importe quel autre service, il vous faut accder au service security.context et appeler la mthode isGranted, tout simplement. Par exemple dans notre contrleur BlogController : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php // ... // Pensez rajouter ce use pour l'exception use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; // ... public function ajouterAction() { // On teste que l'utilisateur dispose bien du rle ROLE_AUTEUR if( ! $this->get('security.context')>isGranted('ROLE_AUTEUR') ) { // Sinon on dclenche une exception "Accs Interdit" throw new AccessDeniedHttpException('Accs limit aux auteurs'); } // Ici le code d'ajout d'un article qu'on a dj fait array( } return $this->render('SdzBlogBundle:Blog:ajouter.html.twig', )); 'form' => $form->createView(),

C'est tout ! V ous pouvez aller sur /blog, mais impossible d'atteindre la page d'ajout d'un article sur /blog/ajouter/article, car vous ne disposez pas (encore !) du rle ROLE_AUTEUR.

Utiliser les annotations dans un contrleur


Pour faire exactement ce qu'on vient de faire avec le service security.context, il existe un moyen bien plus rapide et joli : les annotations ! C'est ici qu'intervient le bundle JMSSecurityExtraBundle. Pas besoin d'explication c'est vraiment simple, regardez le code : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php // Plus besoin de rajouter le use de l'exception dans ce cas // Mais par contre il faut le use pour les annotations du bundle : use JMS\SecurityExtraBundle\Annotation\Secure; // ... /** * @Secure(roles="ROLE_AUTEUR") */ public function ajouterAction() { // Ici le code d'ajout d'un article qu'on a dj fait array( } return $this->render('SdzBlogBundle:Blog:ajouter.html.twig', )); 'form' => $form->createView(),

Et voil ! Grce l'annotation @Secure, on a scuris notre mthode en une seule ligne, vraiment pratique. Sachez que vous pouvez demander plusieurs rles en mme temps, en faisant @Secure(roles="ROLE_AUTEUR, ROLE_MODERATEUR), qui

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


demandera le rle AUTEUR et le rle MODERATEUR (ce n'est pas un ou !). Pour vrifier simplement que l'utilisateur est authentifi, vous pouvez utiliser le rle spcial IS_AUTHENTICATED_REMEMBERED. Sachez qu'il existe d'autres vrifications possibles avec l'annotation @Secure, je vous invite jeter un oeil la documentation de JMSSecurityExtraBundle.

171/201

Depuis une vue Twig


On l'a dj crois plus haut lorsqu'on a affich soit le bouton dconnexion soit le bouton connexion. Par exemple, chaque fois que vous avez un lien qui ne doit apparatre que pour certains visiteurs, il faut utiliser la fonction is_granted(), comme cela : Code : HTML & Django {% if is_granted('ROLE_AUTEUR') %} <a href="{{ path('sdzblog_ajouter') }}">Ajouter un article</a> {% endif %}

Grer les rles des utilisateurs


Pour l'instant vous n'avez pas pu tester les accs limit car vos utilisateurs n'ont pas de rle particulier. Pour grer les rles il y a deux moyens :

Ajouter des rles depuis la console


Cette mthode est videmment uniquement des fin de dveloppement. a tombe bien c'est notre cas ! Ouvrez donc la console et excutez la commande php app/console fos:user:promote . Suivez les instructions en mettant un nom d'utilisateur, puis un rle (testez avec ROLE_AUTEUR par exemple). Une fois la commande excute, il faut vous dconnecter et vous reconnecter afin de prendre en compte les changements. Ensuite, vous pouvez tester que vous avez bien accs la page d'ajout d'un article . Sachez au passage qu'il existe quelques autres commandes bien pratiques dans le bundle FOSUserBundle, qui peuvent bien vous faciliter la vie. Elles se trouvent toutes sur la documentation, comme d'habitude : https://github.com/FriendsOfSymfony/FO [...] line_tools.md

Grer les rles depuis le code


L'entit User de FOSUserBundle dispose de mthodes pour grer les rles. N'ayez pas peur de jeter un oeil au fichier de l'entit : https://github.com/FriendsOfSymfony/FO [...] odel/User.php Notez particulirement pour les rles les mthodes : addRole($role) pour ajouter un rle un utilisateur. Exemple : $user->addRole('ROLE_AUTEUR') ; removeRole($role) pour supprimer un rle simplement. Exemple : $user->removeRole('ROLE_AUTEUR') ; getRoles() pour rcuprer les rles d'un utilisateur. Exemple $roles = $user->getRoles(), $role est un tableau avec les diffrent(s) rle(s). Ces mthodes vous permettront de grer les rles de vos utilisateurs sans passer par la console (qui n'est qu'un moyen de dveloppement).

Manipuler les utilisateurs


Nous allons voir dans cette dernire sous-partie les moyens pour manipuler vos utilisateurs au quotidien.

Manipuler les utilisateurs


Si les utilisateurs sont grs par FOSUserBundle, ils ne restent que des entits Doctrine2 des plus classiques. Ainsi, vous pourriez trs bien vous crer un Repository comme vous savez le faire. Cependant, profitons du fait que le bundle intgre un UserManager, c'est une sorte de repository avanc. Ainsi, voici les principales manipulations que vous pouvez faire avec : Code : PHP <?php // Dans un contrleur : // Pour rcuprer le service UserManager du bundle $userManager = $this->get('fos_user.user_manager'); // Pour charger un utilisateur $user = $userManager->findUserBy(array('username' => 'winzou')); // Pour modifier un utilisateur $user->setEmail('cetemail@nexiste.pas'); $userManager->updateUser($user); // Pas besoin de faire un flush avec l'entityManager, cette mthode le fait toute seule ! // Pour supprimer un utilisateur $userManager->deleteUser($user); // Pour rcuprer la liste de tous les utilisateurs $users = $userManager->findUsers();

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2

172/201

Si vous avez besoin de plus de fonctions, vous pouvez parfaitement faire un repository perso, et le rcuprer comme d'habitude via <?php $this->getDoctrine()->getEntityManager()->getRepository('SdzBlog:User') Et si vous voulez en savoir plus sur ce que fait le bundle dans les coulisses, n'hsitez pas aller voir le code des contrleurs du bundle.

Rcuprer l'utilisateur courant


V oici le dernier dtail savoir. Pour rcuprer l'utilisateur courant, il faut encore utiliser le service security.context, voici comment : Code : PHP <?php // Dans un contrleur $user = $this->container->get('security.context')->getToken()>getUser(); // Et pour vrifier que l'utilisateur est authentifi (et non un anonyme) if( ! is_object($user) ) { throw new AccessDeniedException('Vous n\'tes pas authentifi.'); }

N'oubliez pas de vrifier si l'utilisateur est authentifi ou non, sauf si vous tes sous un firewall qui interdit les anonymes videmment. Car si l'utilisateur est anonyme, $user est une chane de caractres qui vaut "anon.". Donc si vous essayez de faire $user->getUsername(), vous aurez videmment une belle erreur. V ous avez accs plus facilement l'utilisateur directement depuis Twig. V ous savez que Twig dispose de quelques variables globales via la variable {{ app }}, et bien l'utilisateur en fait parti, via {{ app.user }} : Code : Autre Bonjour {{ app.user.username }} - {{ app.user.email }}

Encore une fois, attention ne pas utiliser {{ app.user }} lorsque l'utilisateur n'est pas authentifi, sous peine d'erreur fatale. Ce chapitre se termine ici pour l'instant. V ous avez maintenant tous les outils en main pour construire votre espace membre, avec un systme d'authentification performant et scuris, et des accs limits pour vos pages suivant des droits prcis. Sachez que tout ceci n'est qu'une introduction la scurit sous Symfony2. Les processus complets sont trs puissants mais videmment plus complexes. Si vous souhaitez aller plus loin pour faire des oprations plus prcises, n'hsitez pas vous rfrer la documentation officielle sur la scurit. Allez jeter un oeil galement la documentation de FOSUserBundle, qui explique comment personnaliser au maximum le bundle, ainsi que l'utilisation des groupes. Pour information je compte complter ce chapitre l'avenir, pour parler des ACLs. C'est un systme qui vous permet de dfinir des droits bien plus finement que les rles. Par exemple, pour autoriser l'dition d'un article si l'on est admin OU si l'on en est l'auteur. Affaire suivre ! N'oubliez pas de remettre le paramtre "anonymous" true dans le firewall , sauf si vous souhaitez vraiment interdire tous les anonymes sur votre site !

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2

173/201

Les services, thorie et cration


V ous avez souvent eu besoin d'excuter une certaine fonction plusieurs endroits diffrents dans votre code ? Comment vrifier une condition sur toutes les pages ? Alors ce chapitre et le prochain sont faits pour vous ! Nous allons dcouvrir ici une importante fonctionnalit de Symfony : le systme de services. V ous le verrez, les services sont utiliss partout dans Symfony2, et sont une fonctionnalit incontournable pour commencer dvelopper srieusement un site internet sous Symfony2. Commenons !

Qu'est ce qu'un service ?


Un service est simplement un objet PHP qui remplit une fonction. Cette fonction peut-tre simple : envoyer des emails, vrifier qu'un texte n'est pas un spam, etc. Mais elle peut aussi tre bien plus complexe : grer une base de donnes (le service Doctrine !), etc. Un service est donc un objet PHP qui a pour vocation d'tre accessible depuis n'importe o dans votre code. Pour chaque fonctionnalit dont vous aurez besoin dans toute votre application, vous pourrez crer un ou plusieurs services (et donc une ou plusieurs classes). Il faut vraiment bien comprendre cela : un service est une simple classe. Prenons pour exemple l'envoi d'e-mails. On pourrait alors crer une classe avec comme nom Mailer et la dfinir comme un service. Elle deviendrait alors utilisable n'importe o. Pour ceux qui connaissent, le concept de service est un bon moyen d'viter d'utiliser trop souvent mauvaise escient le pattern Singleton (utiliser une mthode statique pour rcuprer l'objet depuis n'importe o).

La persistance des services


Dans Symfony, chaque service est " persistant". Cela signifie simplement que la classe est instancie une fois ( la premire rcupration du service) puis cette instance est la mme par la suite. Cette persistance permet de manipuler trs facilement les services tout au long du processus .

Le service container
Mais alors, si un service est juste une classe, pourquoi appeler celle-ci un service ? Et pourquoi utiliser les services ?

L'intrt rel des services rside dans leurs associations avec un "conteneur de services". Ce conteneur de services (" services container" en anglais) est une sorte de super-objet qui gre tous les services. Ainsi, pour accder un service, il faut passer par le conteneur. L'intrt principal du conteneur est de pouvoir utiliser une classe sans connaitre son nom. Par exemple : je cherche envoyer un e-mail. Je demande au service container de me retourner le service nomm "Mailer", bien que je ne connaisse pas quelle classe est derrire ce nom. Le conteneur me renvoie une instance de la classe associe (SwiftMailer par exemple), que je peux utiliser pour envoyer mon e-mail. Cela permet de pouvoir changer de classe pour un service donn, sans pour autant changer le code de tous les endroits o le service est utilis. V oil un schma explicatif du positionnement du conteneur de services :

Fonctionnement du Service Container Dans Symfony le ServiceContainer est vraiment un lment central, d'o ce chapitre !

En pratique
Continuons sur notre exemple d'e-mail. Dans Symfony, il existe un composant appel Swiftmailer, qui permet d'envoyer des emails simplement. Il est prsent par dfaut dans Symfony, sous forme de service. Pour accder un service dj enregistr, il suffit d'utiliser la mthode get($identifiantService) du conteneur. Par exemple :

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


Code : PHP <?php $container->get('mailer');

174/201

Pour avoir la liste des services disponibles, utilisez la commande Code : Console php app/console container:debug

Et comment j'accde $container moi ?!

En effet, la question est importante. Dans Symfony il existe un classe nomme ContainerAware qui possde un attribut $container. Le cur de Symfony alimente ainsi les classes du framework en utilisant la mthode setContainer(). Donc pour toute classe de Symfony hritant de ContainerAware, on peut faire ceci : Code : PHP <?php $this->container->get('mailer');

Heureusement pour nous, la classe de base des contrleurs nomme Controller hrite de cette classe ContainerAware, on peut appliquer ceci aux contrleurs : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; // Cette classe tend ContainerAware class BlogController extends Controller { public function indexAction() { $swiftmailer = $this->container->get('mailer'); // On a donc accs au conteneur var_dump($swiftmailer); exit; } return $this->render('SdzBlogBundle:Blog:index.html.twig');

Il se peut que vous ayez dj rencontr, depuis un contrleur, l'utilisation de get() sans passer par l'attribut container, comme ceci : $this->get('mailer'). C'est parce que la classe Controller fournit un raccourci, la mthode $this->get() faisant simplement appel la mthode $this->container->get(). Donc dans un contrleur, $this->get('...') est strictement quivalent $this->container->get('...').

Crer un service Cration de la classe du service


Maintenant que nous savons utiliser un service, apprenons le crer. Comme un service n'est qu'une classe, il suffit de crer un fichier n'importe o et de crer une classe dedans. La seule convention respecter, de faon gnrale dans Symfony, c'est de mettre notre classe dans un namespace correspondant au dossier o est le fichier. Par exemple, la classe Sdz\BlogBundle\Service\SdzAntispam doit se trouver dans le rpertoire src/Sdz/BlogBundle/Service/SdzAntispam.php. C'est ce que nous faisons depuis le dbut du tutoriel . Je vous propose, pour suivre notre fil rouge du blog, de crer un systme anti-spam. Notre besoin : dtecter les spams partir d'un simple texte. Comme c'est une fonction part entire, et qu'on aura besoin d'elle plusieurs endroits (pour les articles et pour les commentaires), faisons-en un service. Ce service devra tre rutilisable simplement dans d'autre projets Symfony : il ne devra pas tre dpendant d'un lment de notre blog. Je nommerais ce service SdzAntispam, mais vous pouvez le nommer

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


devra pas tre dpendant d'un lment de notre blog. Je nommerais ce service SdzAntispam, mais vous pouvez le nommer comme vous le souhaitez. Il n'y a pas de rgle prcise ce niveau, mise part que l'utilisation des underscores (_) est fortement dconseille. Si votre service est relativement indpendant (il n'y a pas d'autres services autour du mme thme), il est bon de mettre ce service dans un dossier Service de votre bundle, mais vrai dire vous pouvez faire comme vous le souhaitez. Crons donc le fichier src/Sdz/BlogBundle/Service/SdzAntispam.php , avec ce code pour l'instant : Code : PHP <?php // src/Sdz/BlogBundle/Service/SdzAntispam.php namespace Sdz\BlogBundle\Service; /** * Un anti-spam simple pour Symfony2. * * @author Leglopin */ class SdzAntispam { }

175/201

C'est tout ce qu'il faut pour avoir un service. Il n'y a vraiment rien d'obligatoire, vous y mettez ce que vous voulez. Pour l'exemple, faisons un rapide anti-spam : considrons qu'un message est un spam s'il contient au moins 3 liens ou adresse e-mail. V oici ce que j'obtiens : Code : PHP <?php // src/Sdz/BlogBundle/Service/SdzAntispam.php namespace Sdz\BlogBundle\Service; /** * Un anti-spam simple pour Symfony2. * * @author Leglopin */ class SdzAntispam { /** * Vrifie si le texte est un spam ou non. * Un texte est considr comme spam partir de 3 liens * ou adresses e-mails dans son contenu. * * @param string $text */ public function isSpam($text) { if( ($this->countLinks($text) + $this->countMails($text)) >= 3 ) { return true; } else { return false; } } /** * Compte les URL de $text. * * @param string $text */ private function countLinks($text) { preg_match_all( '#(http|https|ftp)://([A-Z0-9][A-Z0-9_-]*(?:.[A-Z0-9][AZ0-9_-]*)+):?(d+)?/?#i', $text, $matches); } return count($matches[0]);

/** * Compte les e-mails de $text. * * @param string $text */ private function countMails($text) {

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


preg_match_all( '#[a-z0-9._-]+@[a-z0-9._-]{2,}\.[a-z]{2,4}#i', $text, $matches); } return count($matches[0]);

176/201

Cration de la configuration du service


Maintenant que nous avons cr notre service, il faut le signaler Symfony. Un service se dfinit par sa classe ainsi que sa configuration. Pour cela, nous pouvons utiliser le fichier src/Sdz/BlogBundle/Ressources/config/services.yml . Si vous avez gnr votre bundle avec le generator en rpondant "oui" pour crer toute la structure du bundle, alors ce fichier est charg automatiquement. Vrifiez-le en confirmant que le rpertoire DependencyInjection de votre bundle existe, il devrait contenir le fichier SdzBlogExtension.php. Si ce n'est pas le cas, vous devez crer le fichier DependencyInjection/SdzBlogExtension.php (adaptez votre bundle videmment) avec le contenu suivant : Code : PHP <?php namespace Sdz\BlogBundle\DependencyInjection; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Config\FileLocator; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\DependencyInjection\Loader; class SdzBlogExtension extends Extension { public function load(array $configs, ContainerBuilder $container) { $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.yml'); } }

Ouvrez ou crez le fichier Ressources/config/services.yml de votre bundle, et ajoutez-y la configuration pour notre service : Code : Autre services: sdz_blog.antispam: class: Sdz\BlogBundle\Service\SdzAntispam

Dans cette configuration : "sdz_blog.antispam" est le nom de notre service frachement cr. De cette manire, le service sera accessible via <?php $container->get('sdz_blog.antispam');. Essayez de respecter cette convention de prfixer le nom de vos services par "nomApplication_nomBundle". Pour notre bundle Sdz\BlogBundle, on a donc prfix notre service de "sdz_blog.". "class" est un attribut obligatoire de notre service, il dfinit simplement le namespace complet de la classe du service. Il existe bien sr d'autres attributs pour affiner la dfinition de notre service, nous les verrons dans le prochain chapitre. Sachez galement que le conteneur de Symfony2 permet de stocker aussi bien des services (des classes) que des paramtres (des variables). Pour dfinir un paramtre, la technique est la mme que pour un service, dans le fichier services.yml : Code : Autre parameters: mon_parametre: ma_valeur services: # ...

Et pour accder ce paramtre, la technique est la mme galement, sauf qu'il faut utiliser la mthode <?php $container->getParameter($identifiant); au lieu de get().

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2

177/201

Utilisation du service
Maintenant que notre classe est dfinie, et notre configuration dclare, nous pouvons nous servir du service. V oici un exemple simple de l'utilisation que l'on pourrait en faire : Code : PHP <?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class BlogController extends Controller { public function indexAction() { $antispam = $this->container->get('sdz_blog.antispam'); if( $antispam->isSpam($text) ) { exit('Votre message a t dtect comme spam !'); } } return $this->render('SdzBlogBundle:Blog:index.html.twig');

Et voil, vous avez cr et utilis votre premier service ! Les services sont extrmement utiliss dans Symfony2. On n'a pas encore vu tout leur potentiel jusque maintenant, mais ils auront un aspect beaucoup plus intressant au prochaine chapitre, o nous allons utiliser les services pour modifier le comportement de Symfony2 ! Afin de bien comprendre les mcanismes vu dans ce chapitre, je vous conseille de lire ces deux tutoriels : Introduction l'injection de dpendances en PHP de vincent1870 Les designs patterns : l'injection de dpendance de vyk12 Ces deux tutoriels prsentent l'injection de dpendance (l'injection de dpendance est lie au principe de service) de manire concise. Cependant, comme vous pourrez le voir au prochain chapitre, Symfony va bien plus loin . Tout ne fait que commencer !

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2

178/201

Les services, utilisation pousse


Cette deuxime partie est rserv aux fonctionnalits les plus intressantes des services. Maintenant que les bases vous sont acquises, nous allons pouvoir dcouvrir des fonctionnalits trs puissantes de Symfony.

Les arguments
La plupart du temps, vos services ne fonctionneront pas seuls, et vont ncessiter l'utilisation d'autres services, de paramtres ou de variables. Il a donc fallu trouver un moyen propre et efficace pour pallier ce problme. Pour passer des arguments votre service, il faut utiliser le fichier de configuration : Code : Autre parameters: services: sdz_blog.antispam: class: Sdz\BlogBundle\Service\SdzAntispam arguments: [] # Tableau d'arguments

Les arguments peuvent tre : Des valeurs normales en YAML (des boolens, des chaines de caractres, des nombres, etc.) Des paramtres (dfinis dans le parameters.ini par exemple) : l'identifiant du paramtre est encadr de signes % : %parametre_id% Des services : l'identifiant du service est prcd d'un arobase : @service_id

Par exemple : Code : Autre parameters: services: sdz_blog.antispam: class: Sdz\BlogBundle\Service\SdzAntispam arguments: [@doctrine, %locale%, 3]

Dans cet exemple, notre service utilise @doctrine : le service Doctrine (pour utiliser la base de donnes), %locale% : le paramtre locale (pour rcuprer la langue), 3 : et le nombre 3 (qu'importe son utilit !). Une fois vos arguments dfinis dans la configuration, il vous suffit de les rcuprer avec votre constructeur. Les arguments de la configuration et ceux du constructeur vont donc de paire. Si vous modifiez l'un, n'oubliez pas d'adapter l'autre. V oici donc le constructeur adapt notre nouvelle configuration : Code : PHP <?php // src/Sdz/BlogBundle/Service/SdzAntispam.php namespace Sdz\BlogBundle\Service; use Symfony\Bundle\DoctrineBundle\Registry; /** * Un anti-spam simple pour Symfony2. * * @author Leglopin */ class SdzAntispam { /** * @var Symfony\Bundle\DoctrineBundle\Registry */ protected $doctrine; protected $locale; protected $nbFoundedForSpam; public function __construct(Registry $doctrine, $locale, $nbFoundedForSpam) { $this->doctrine = $doctrine; $this->locale = (string) $locale; $this->nbFoundedForSpam = (int) $nbFoundedForSpam; }

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


} public function isSpam($text) { if( ($this->countLinks($text) + $this->countMails($text)) >= $this->nbFoundedForSpam ) // Nous utilisons ici l'argument $nbFoundedForSpam { return true; } else { return false; } } } // ...

179/201

V ous pouvez remarquer que les arguments permettent de transmettre des services d'autres services : c'est pour cette raison qu'on appelle ce concept l'injection de dpendance (dependency injection en anglais). Au lieu de faire appel directement dans le code des classes, on fait appel un conteneur de services qui cre alors des dpendances dynamiques . La boucle est boucle !

Les calls
Cette fonctionnalit est assez simple : elle permet d'appeler directement aprs la construction du service certaines fonctions. Ainsi, pour faire exactement la mme chose que prcdemment, vous pouvez configurer votre service comme ceci : Code : Autre parameters: services: sdz_blog.antispam: class: Sdz\BlogBundle\Service\SdzAntispam arguments: [@doctrine, 3] calls: - [ setLocale, [ %locale% ] ]

V ous pouvez dfinir plusieurs calls, en rajoutant des lignes tiret. Le premier argument (ici, setLocale) est le nom de la mthode excuter. Le deuxime argument (ici, [ %locale% ]) est le tableau des arguments transmettre la mthode excute. En adaptant bien sr le service comme ceci : Code : PHP <?php // src/Sdz/BlogBundle/Service/SdzAntispam.php namespace Sdz\BlogBundle\Service; /** * Un anti-spam simple pour Symfony2. * * @author Leglopin */ class SdzAntispam { protected $doctrine; protected $locale; protected $nbFoundedForSpam; public function __construct(Registry $doctrine, $nbFoundedForSpam) { $this->doctrine = $doctrine; $this->nbFoundedForSpam = $nbFoundedForSpam; } // C'est cette mthode qui va tre excute par le call public function setLocale($locale) { $this->locale = $locale; } } // ...

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2

180/201

L'utilit des calls est surtout remarquable pour l'intgration des libraires externes (Zend Framework, Geshi, etc.).

Les tags
Une fonctionnalit trs importante des services est la possibilit d'utiliser les tags . Les tags sont en fait des options ajoutes un service pour lui donner la capacit d'intervenir diffremment avec Symfony. Par exemple, il existe un tag twig.extension qui signale Symfony que le service est une extension Twig. Nous allons ici parler des tags les plus importants, ainsi que comment les mettre en place.

Extension Twig
Pour l'exemple, nous allons transformer notre SdzAntispam pour lui ajouter la possibilit de vrifier un texte de la vue. Adaptons la configuration du service : Code : Autre parameters: services: sdz_blog.antispam: class: Sdz\BlogBundle\Service\SdzAntispam tags: - { name: twig.extension }

Notre service sera donc trait comme une extension Twig. Cependant, un service implantant ce tag doit tendre Twig_Extension. Adaptons le code du service en implmentant les mthodes getFunctions() et getName() obligatoire : Code : PHP <?php // src/Sdz/BlogBundle/Service/SdzAntispam.php namespace Sdz\BlogBundle\Service; /** * Un anti-spam simple pour Symfony2. * * @author Leglopin */ class SdzAntispam extends \Twig_Extension { // La mthode getName(), obligatoire public function getName() { return 'SdzAntispam'; } // La mthode getFunctions(), qui retourne un tableau avec les fonctions qui peuvent tre appeles depuis cette extension public function getFunctions() { return array( 'antispam_check' => new \Twig_Function_Method($this, 'isSpam') ); // 'antispam_check' est le nom de la fonction qui sera disponible sous Twig // 'new \Twig_Function_Method($this, 'isSpam') ' est la faon de dire que cette fonction va excuter notre mthode isSpam ci-dessous } /** * Vrifie si le texte est un spam ou non. * Un texte est considr comme spam partir de 3 liens * ou adresses e-mails dans son contenu. * * @param string $text */ public function isSpam($text) { if( ($this->countLinks($text) + $this->countMails($text)) >= 3 ) { return true; } else { return false; }

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


} } // ...

181/201

V ous pouvez maintenant utiliser la fonction dans Twig : Code : Autre {{ antispam_check('Texte') }}

Pour plus d'informations propos de la cration d'extensions pour Twig, lisez ceci : http://twig.sensiolabs.org/doc/extensions.html.

Les vnements du cur


Les services peuvent tre utiliss avec le gestionnaire d'vnements. Un chapitre sera bientt disponible ce sujet. V ous pouvez en attendant lire la documentation ce sujet.

Les types de champs de formulaire


Cette fonctionnalit est trs pratique, mais comme elle n'est pas documente, je ne connais que ce que j'ai dcouvert par moimme, et il se peut qu'il manque quelques fonctionnalits qui pourraient tre intressantes. N'hsitez pas faire des recherches . Pour dclarer un type de champ, il faut utiliser le tag form.type et indiquer un alias. Par exemple, pour un champ ckeditor (un diteur WYSIWWYG) : Code : Autre services: sdz_blog.ckeditor: class: Sdz\BlogBundle\Form\Extension\CkeditorType tags: - { name: form.type, alias: ckeditor }

La classe CkeditorType contient alors : Code : PHP <?php // src/Sdz/BlogBundle/Form/Extension/CkeditorType.php /** * Type de champ de formulaire pour CKEditor. * * @author Leglopin */ namespace Sdz\BlogBundle\Form\Extension; use Symfony\Component\Form\AbstractType; class CkeditorType extends AbstractType { public function getParent(array $options) { return 'textarea'; } public function getName() { return 'ckeditor'; } public function getDefaultOptions(array $options) { $defaultOptions = parent::getDefaultOptions($options); $defaultOptions['attr']['class'] = 'ckeditor'; } return $defaultOptions;

V ous venez de dclarer le type ckeditor (nom de l'alias). Ce type hrite de toutes les fonctionnalits d'un textarea (grce la

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


mthode getParent()) tout en disposant de la classe CSS ckeditor (dfinie dans la mthode getDefaultOptions()) vous permettant, en ajoutant CKEditor votre site, de transformer vos textarea en diteur WYSIWYG. Pour l'utiliser, modifiez vos FormType pour utiliser 'ckeditor' la place de 'textarea'. Par exemple, dans notre ArticleType : Code : PHP <?php namespace Sdz\BlogBundle\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; class ArticleType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add('date') ->add('titre') // On remplace simplement le type textarea par le type ckeditor ->add('contenu', 'ckeditor') ->add('pseudo') ->add('tags', 'collection', array( 'type' => new TagType, 'allow_add' => true, 'allow_delete' => true ));

182/201

public function getName() { return 'sdz_blogbundle_articletype'; } public function getDefaultOptions(array $options) { return array( 'data_class' => 'Sdz\BlogBundle\Entity\Article', ); }

Pour en savoir plus, vous pouvez regarder le fichier vendor\symfony\src\Symfony\Component\Form\AbstractType.php.

Les autres tags


Il existe beaucoup d'autres tags. V ous pouvez trouver la liste des tags par dfaut dans Symfony ici : http://symfony.com/doc/2.0/reference/dic_tags.html, mais il faut savoir que cette liste n'est pas complte car d'autres bundles ajoutent leur propre tags . La liste officielle n'tant pas trs pratique pour comprendre comment utiliser certains tags, je compte faire ici une liste avec une description plus dtaille de chacun d'entre eux : Identifiant Description Exemple

Ce tag indique Symfony que votre classe permet d'utiliser un nouveau moteur de templates. Par exemple, Twig possde sa classe TwigEngine. Pour dclarer un templating.engine moteur de templates, utilisez le tag templating.engine. V ous devez ensuite crer votre classe Engine sur le modle de l'interface EngineInterface Utilise votre service en tant que Helper de vue. Un helper de vue est diffrent d'une extension Twig car il n'est utilisable que dans les templates PHP. V ous devez dfinir un alias pour utiliser ce dernier dans votre vue. Une fois dclar, vous pouvez accder votre helper ainsi : <?php

Code : Autre services: templating.engine.your_engine_name: class: Fully\Qualified\Engine\Class\Name tags: - { name: templating.engine }

Code : Autre services: templating.helper.your_helper_name: class: Fully\Qualified\Helper\Class\Name tags: -

templating.helper

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


ainsi : <?php $view['alias_name']; ?>, soit par exemple pour SdzAntispam : <?php $view['antispam']>isSpam(/* ... */); ?> { name: templating.helper, alias: alias_name }

183/201

Code : Autre services: routing.loader.your_loader_name: class: Fully\Qualified\Loader\Class\Name tags: - { name: routing.loader }

Interface : Secret (cliquez pour afficher) Code : PHP <?php interface RouterLoader { /** * Charge les routes depuis une ressource. * Cette mthode doit retourner une collection RouteCollection. * * @param mixed $string La ressource * @param string $type Le type de ressource * * @return RouteCollection * * @api */ public function load($string, $type = null); /** * Cette mthode est utilise pour savoir si le Loader est capable de charger la ressource. * * @param mixed $resource La ressource * @param string $type Le type de ressource * * @return Boolean Retourne TRUE si il est possible d'utiliser ce Loader, FALSE sinon * * @api */ public function supports($string, $type = null); }

routing.loader

Utilise votre service en tant que classe de chargement de routes. Cette fonctionnalit permet d'ajouter le support de plus de formats de configuration de routes. Par exemple, il existe des loaders PhpFileLoader, XmlFileLoader et YamlFileLoader. Pour dclarer votre classe de chargement, utilisez le tag routing.loader. V otre classe de chargement doit correspondre l'interface ci-contre :

Utilise votre service en tant que classe de chargement de traductions, sur le mme principe que routing.loader. Cette fonctionnalit permet d'ajouter le support de plus de formats de configuration de traductions. Par exemple, il existe un loader translation.loader YamlFileLoader. Pour dclarer votre classe de chargement, utilisez le tag translation.loader avec un alias qui doit correspondre l'extension que peut comprendre votre loader. V otre classe de chargement doit correspondre l'interface LoaderInterface.

Code : Autre services: routing.loader.your_loader_name: class: Fully\Qualified\Loader\Class\Name tags: { name: translation.loader, alias: ini }

Les services courants de Symfony2 Les services courants de Symfony

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2

184/201

Certains d'entre vous ont peut-tre dj remarqu qu'ils avaient dj utilis des services auparavant. En effet, Symfony fournit par dfaut pas mal de services, dont appartiennent par exemple Doctrine, Request ou Swiftmailer. Je vous propose d'tudier plus en dtails certains services qui pourront vous tre trs utiles par la suite ! Identifiant Description Ce service est l'instance de l'EntityManager de Doctrine ORM. V ous avez probablement dj utilis Doctrine en tant que service, mais peut-tre sans le savoir car la classe Controller dfinit une mthode appele getDoctrine() qui renvoi le service directement. Rcuprer l'EntityManager dans un service est extrmement pratique pour ensuite effectuer des oprations en base de donnes, rcuprer un repository, etc. Ce service donne accs l'event dispatcher. Cet lment est un peu trop complexe pour le rduire ces quelques lignes, mais disons que c'est un objet pour grer les vnements et les fonctions qui ragissent ces vnements. Un chapitre sur le gestionnaire d'vnements est en cours de rdaction . Ce service vous donne accs au coeur de Symfony. Grce lui, vous pouvez localiser des bundles, rcuprer le chemin de base du site, etc. V oyez le fichier Kernel.php pour connaitre toutes les possibilits. Ce service est la classe de logs. Grce lui, vous pouvez utiliser des fichiers de logs trs simplement. Symfony utilise Monolog pour grer ses logs. V oyez les fichiers vendor\symfony\src\Symfony\Bridge\Monolog\Logger.php et vendor\monolog\src\Monolog\Logger.php pour en savoir plus. Ce service vous renvoi par dfaut une instance de SwiftMailer, vous permettant d'envoyer des e-mails. V ous pouvez consulter la documentation de SwiftMailer pour en savoir plus. Ce service est trs important : il vous donne une instance de Request qui reprsente la requte du client. V ous pouvez par exemple : Rcuprer la session en cours : <?php $this->get('request')->getSession() Rcuprer l'IP du client : <?php $this->get('request')->getClientIp() Rcuprer la mthode de la requte (comme dans les formulaires) : <?php $this->get('request')>getMethod() : POST, GET, PUT, etc. Savoir si c'est une requte en AJAX : <?php $this->get('request')->isXmlHttpRequest() Et bien d'autres choses ... Je vous laisse dcouvrir tout ceci : Symfony\Component\HttpFoundation\Request.php Ce service vous donne accs au routeur (Symfony\Component\Routing\Router). C'est cet objet qui gnre vos routes et qui les transforme en URL. V ous pouvez par exemple gnrer une route : Code : PHP <?php $this->get('router')->generate('homepage');

doctrine.orm.entity_manager

event_dispatcher

kernel

logger

mailer

request

router

Ou trouver une route correspondant une URL : Code : PHP <?php $this->get('router')->match($url);

La classe Controller a aussi un raccourci pour gnrer une route : vous pouvez gnrer une URL dans un contrleur avec <?php $this->generateUrl($nomRoute, $parametres, $estAbsolu); Ce service est surtout utile car il permet de rcuprer l'utilisateur courant : Code : PHP <?php $this->get('security.context')->getToken()->getUser();

security.context

Ce code fonctionne seulement si l'utilisateur est connect. Pour le vrifier, il faut vrifier que <?php $this>get('security.context')->getToken(); n'est pas nul. service_container session twig Ce service vous renvoi le service container. Comme vous pouvez vous en douter, on ne l'utilise pas dans les contrleurs. Il est surtout utile dans les autres services, o on le passe en argument pour accder au conteneur . Ce service reprsente les sessions. V oyez le fichier Symfony\Component\HttpFoundation\Session\Session.php pour en savoir plus. Ce service reprsente une instance de Twig_Environment. Il permet d'afficher une vue. V ous pouvez en savoir plus en lisant la documentation de Twig. Ce service peut tre utile pour modifier l'environnement de Twig depuis lextrieur (lui ajouter des

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2


extensions, etc.).

185/201

Ce service reprsente le moteur de templates. Cela peut tre Twig, PHP ou tout autre moteur utilisable. Ce service montre l'intrt de l'injection de dpendances : on russit faire un code valide pour plusieurs moteurs de templates en mme temps, et changer de moteur ne ncessite pas de modifier le code. La classe Controller fait rfrence ce service pour afficher une vue. Quand vous utilisez la mthode $this->render() d'un contrleur, vous utilisez en fait le service templating. V oyez vous-mme le contenu de cette mthode render() : Code : PHP <?php // vendor\symfony\src\Symfony\Bundle\FrameworkBundle\Controller\Controller.php templating /** * Renders a view. * * @param string $view The view name * @param array $parameters An array of parameters to pass to the view * @param Response $response A response instance * * @return Response A Response instance */ public function render($view, array $parameters = array(), Response $response = null) { return $this->container->get('templating')->renderResponse($view, $parameters, $response); }

J'espre avoir russi vous clairer propos de la gestion des services dans Symfony. V ous pouvez maintenant vous rendre compte que grce eux, de nouvelles possibilits s'offrent vous : les extensions Twig, l'intgration de bibliothque tierces, etc. Ces deux chapitres sur les services ont t crits l'origine par Titouan Galopin (Leglopin).

www.siteduzero.com

Partie 4 : Allons plus loin avec Symfony2

186/201

Partie 5 : Astuces et points particuliers


Cette partie recense diffrentes astuces et points particuliers de Symfony2, qui vous permettent de raliser des choses prcises dans votre projet. Comme ces petites et moyennes astuces ls couvrent souvent plusieurs notions en mme temps, il est impossible de les intgrer dans le cours du tutoriel, c'est pourquoi une partie leur est ddie. Je vous invite donc en lire une de temps en temps, car ce sont souvent des points trs pratiques connatre ! Bon code.

Rcuprer directement des entits Doctrine dans son contrleur


L'objectif de cette astuce, comme de beaucoup d'autres, est de vous faire gagner du temps et des lignes de code. Sympa, non ?

Thorie : pourquoi un ParamConverter ? Rcuprer des objets Doctrine avant mme le contrleur
Sur la page d'affichage d'un article de blog, par exemple, n'tes-vous pas fatigu de toujours devoir vrifier l'existence de l'article demand et de l'instancier vous-mme ? N'avez-vous pas l'impression d'crire toujours et encore les mmes lignes ? Code : PHP <?php public function voirAction($id) { if( ! $article = $this->get('doctrine')->getEntityManager()>getRepository('Sdz\BlogBundle\Entity\Article')->find($id) ) { throw new NotFoundHttpException(sprintf('L\'article id:"%s" n\'existe pas.', $id)); } // votre vrai code return $this->render('SdzBlogBundle:Blog:voir.html.twig', array('article' => $article)); }

Pour enfin vous concentrer sur votre code mtier, Symfony a videmment tout prvu !

Les ParamConverters
V ous pouvez crer ou utiliser des ParamConverters qui vont agir juste aprs le routeur. Les ParamConverters vont, comme leur nom l'indique, convertir les paramtres de votre route au format que vous prfrez. En effet, depuis la route, vous ne pouvez pas tellement agir sur vos paramtres. Tout au plus, vous pouvez leur imposer des contraintes via une regex. Les ParamConverter pallient cette limitation en agissant aprs le routeur.

Un ParamConverter utile : DoctrineParamConverter


V ous l'aurez devin, ce ParamConverter va nous convertir nos paramtres directement en Entity Doctrine ! L'ide : dans le contrleur, la place de la variable <?php $id, on souhaite rcuprer directement une variable <?php $article qui correspond l'article portant l'id <?php $id. Le bonus : on veut galement que, si l'article portant l'id <?php $id n'existe pas, une exception 404 soit leve. Aprs tout, c'est comme si l'on mettait dans la route : " requirements: Article exists" !

Un peu de thorie sur les ParamConverter


Comment fonctionne un ParamConverter ? Ce n'est en fait qu'un simple listener qui coute l'vnement kernel.controller. Cet vnement est dclench lorsque le contrleur sait quel contrleur appeler (aprs le routeur, donc), mais avant d'excuter effectivement le contrleur. Ainsi, lors de cet vnement, le ParamConverter va lire la mthode de votre contrleur pour trouver soit l'annotation, soit le type de variable que vous souhaitez. Fort de ces informations, il va se permettre de modifier le paramtre de la requte (il y a accs). Ainsi, depuis votre contrleur, vous n'avez plus le paramtre original tel que dfini dans la route, mais un paramtre modifi par votre ParamConverter qui s'est excut avant votre contrleur.

Pratique : utilisation de DoctrineParamConverter Utiliser DoctrineParamConverter


Ce ParamConverter fait partie du bundle Sensio\FrameworkBundle. C'est un bundle activ par dfaut sous Symfony2. Vrifiez juste que vous ne l'avez pas dsactiv.

www.siteduzero.com

Partie 5 : Astuces et points particuliers


V ous pouvez ensuite vous servir de DoctrineParamConverter. La faon la plus simple de s'en servir est la suivante.

187/201

1. Ct route
V otre route ne change pas, vous gardez un paramtre {id} l'ancienne. Exemple (tir de src/sdz/BlogBundle/Resources/config/routing.yml) : Code : Autre # src/Sdz/BlogBundle/Resources/config/routing.yml sdzblog_voir: pattern: /voir/{id} defaults: { _controller: SdzBlogBundle:Blog:voir }

2. Ct contrleur
C'est dans le contrleur que tout se joue et que la magie opre. Il vous suffit de changer la dclaration de votre action (dans src/Sdz/BlogBundle/Controller/BlogController.php). Avant : Code : PHP <?php public function voirAction($id) Aprs : Code : PHP <?php // En haut du fichier use Sdz\BlogBundle\Entity\Article; // Et dans la classe public function voirAction( Article $article )

V oici le code complet du contrleur : Code : PHP <?php use Sdz\BlogBundle\Entity\Article; // ... public function voirAction( Article $article ) { // Rcuprons le titre de l'article. $article->getTitre(); return $this>render('SdzBlogBundle:Blog:voir.html.twig', array('article' => $article)); }

Et voil ! V ous pouvez maintenant utiliser directement <?php $article->getTitre() ou tout autre code utilisant <? php $article dans votre contrleur. Pratique, n'est-ce pas ? V oil donc encore une astuce qui va vous permettre d'conomiser des lignes de code ! Symfony2 nous aide vraiment : fini les lignes redondantes juste pour vrifier qu'un article existe.

www.siteduzero.com

Partie 5 : Astuces et points particuliers

188/201

Personnaliser les pages d'erreur


Avec Symfony2, lorsqu'une exception est dclenche, le noyau l'attrape. Cela lui permet ensuite d'effectuer l'action adquate. Le comportement par dfaut du noyau consiste appeler un contrleur particulier intgr Symfony2 : TwigBundle:Exception:show. Ce contrleur rcupre les informations de l'exception, choisit le template adquat (un template diffrent par type d'erreur), passe les informations au template et envoie la rponse gnre par ce template. partir de l, il est facile de personnaliser ce comportement : TwigBundle tant un bundle, on peut le modifier pour l'adapter nos besoins ! Mais ce n'est pas le comportement que nous voulons changer, c'est juste l'apparence de nos pages d'erreur. Il suffit donc de crer nos propres templates et de dire Symfony2 d'utiliser nos templates et non ceux par dfaut.

Thorie : remplacer les templates d'un bundle


Il est trs simple de remplacer les templates d'un bundle quelconque par les ntres. Il suffit de crer le rpertoire app/Resources/NomDuBundle/views/ et d'y placer nos templates ! Nos templates doivent porter exactement les mmes noms que ceux qu'ils doivent remplacer. Ainsi, si notre bundle utilise un template situ dans (namespace)/ RpertoireDuBundle/Resources/views/ Hello/salut.html.twig , alors nous devons crer le template app/Resources/ NomDuBundle/views/ Hello/salut.html.twig . Attention, le NomDuBundle en bleu correspond bien au nom du bundle, savoir au nom du fichier que vous pouvez trouver sa racine. Par exemple : SdzBlogBundle est le nom du bundle, mais il se trouve dans (src)/Sdz/BlogBundle.

Symfony2, pour chaque template qu'il charge, regarde d'abord dans le rpertoire app/Resources/ s'il trouve le template correspondant. S'il ne le trouve pas, il va ensuite voir dans le rpertoire du bundle.

Pratique : remplacer les templates Exception de TwigBundle


Maintenant qu'on sait le faire, il ne reste plus qu' le faire. Crez donc le rpertoire app/Resources/TwigBundle/views/. En l'occurrence, les templates des messages d'erreur se trouvent dans le rpertoire Exception, crons donc le rpertoire app/Resources/TwigBundle/views/Exception. Et au sein de ce rpertoire, le bundle utilise la convention suivante pour chaque nom de template : il vrifie d'abord l'existence du template error[code_erreur].html.twig, par exemple, error404.html.twig dans le cas d'une page introuvable (erreur 404) ; si ce template n'existe pas, il vrifie l'existence du template error.html.twig, une sorte de page d'erreur gnrique.

V ous pouvez crer error404.html.twig pour les pages non trouves et error500.html.twig pour les erreurs internes, ce sont deux des plus utilises. Mais n'oubliez pas de crer error.html.twig galement, sinon, vous aurez des pages d'erreur dpareilles en cas d'erreur (401, par exemple, pour un accs refus).

Le contenu d'une page d'erreur


Pour savoir quoi mettre dans ces templates, je vous propose de jeter un il celui qui existe dj, error.html (il se trouve dans vendor\symfony\src\Symfony\Bundle\TwigBundle\Resources\views\Exception) : Code : HTML <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>An Error Occurred: {{ status_text }}</title> </head> <body> <h1>Oops! An Error Occurred</h1> <h2>The server returned a "{{ status_code }} {{ status_text }}".</h2> <div> Something is broken. Please e-mail us at [email] and let

us know

what you were doing when this error occurred. We will fix it as soon as possible. Sorry for any inconvenience caused. </div> </body> </html>

V ous pouvez y voir les diffrentes variables que vous pouvez utiliser : {{ status_text }} et {{ status_code }}. Fort de a, vous pouvez crer la page d'erreur que vous souhaitez : vous avez toutes les cls. Soyons d'accord : cette page d'erreur que l'on vient de personnaliser, c'est la page d'erreur gnre en mode prod !

Remplacer la page d'erreur du mode dev n'a pas beaucoup d'intrt : vous seul la voyez, et elle est dj trs complte. Cependant, si vous souhaitez quand mme la modifier, alors cela n'est pas le template error.html.twig qu'il faut crer mais le template exception.html.twig . Celui-ci se trouve aussi dans le rpertoire Exception/.

www.siteduzero.com

Partie 5 : Astuces et points particuliers


Retenez deux astuces en une : modifier les templates d'un bundle quelconque est trs pratique, votre site garde ainsi une cohrence dans son design, et ce, que ce soit sur votre bundle vous comme sur les autres ; personnaliser les pages d'erreur, a n'est pas la priorit lorsque l'on dmarre un projet Symfony2, mais c'est impratif avant de l'ouvrir nos visiteurs.

189/201

www.siteduzero.com

Partie 5 : Astuces et points particuliers

190/201

Utiliser Assetic pour grer les codes CSS et JS de votre site


La gestion des ressources CSS et JavaScript dans un site Internet est trs importante et n'est pas si vidente ! Pour vous aider les grer, Symfony2 intgre un bundle nomm Assetic qui va s'occuper de tout cela votre place. V ous verrez, ce bundle est presque magique !

Thorie : entre vitesse et lisibilit, pourquoi choisir ? propos du nombre de requtes HTTP d'une page Web
V ous devez srement vous demander ce que vient faire la vitesse d'une page dans un article qui traite de code CSS. C'est une bonne question et nous allons y rpondre. Le temps de chargement ressenti d'une page par un visiteur comprend tout le process du clic au rendu de la page par le navigateur. Ainsi, on y inclut : le temps d'envoi de la requte au serveur lors du clic. On ne peut pas y faire grand-chose, malheureusement ; le temps d'excution de la page ct serveur, le temps PHP, donc. Pour a, il faut bien penser son script et essayer de l'optimiser un peu ; le temps d'envoi du code HTML par le serveur vers le navigateur. On ne peut pas y faire grand-chose non plus ; mais l ce n'est pas tout : partir de cette page HTML que le navigateur reoit, ce dernier doit tout recommencer pour chaque fichier CSS, chaque JavaScript et chaque image ! Donc si votre page contient 5 fichiers CSS, 3 JavaScript et 15 images, a fait un total de 23 requtes HTTP traiter par votre navigateur pour vous afficher l'intgralit de la page ! Et pour ces 23 requtes, il y a les temps d'envoi et de rception qui sont incompressibles et qui prennent du temps. Au final, s'il faut bien sr optimiser le code PHP ct serveur, la partie front-end qui comprend codes HTML, CSS et JavaScript ainsi que fichiers image est bien celle qui prend le plus de temps se charger, vu du visiteur.

Comment optimiser le front-end ?


L'ide est de rduire les temps incompressibles. Comme ils sont justement incompressibles, il faut que l'on en diminue le nombre. La seule solution est donc de grouper ces fichiers. Donc au lieu d'avoir cinq fichiers CSS, on va mettre tout notre code CSS dans un seul fichier. Comme a, on aura une seule requte au lieu de cinq. Super ! Mais le problme, c'est que si l'on avait trois fichiers et non un seul, ce n'tait pas pour rien. Chaque fichier concernait une partie de votre site, c'tait bien plus lisible. Tout regrouper vous gnerait dans le dveloppement de vos fichiers CSS (idem pour les fichiers JavaScript, bien sr). C'est l qu'Assetic va intervenir : il va grouper lui-mme les fichiers et va vous permettre de garder votre sparation !

Il est aussi possible d'amliorer le temps de chargement !


En effet, transmettre votre unique fichier CSS de plusieurs centaines de lignes, a prend du temps (temps qui varie en fonction de la connexion de votre serveur, de celle du visiteur, etc.). On peut amliorer ce temps en diminuant la taille du fichier. C'est possible grce un outil Java appel YUI compressor , un outil dvelopp par Yahoo!. Ce script permet de diminuer la taille de vos fichiers CSS mais surtout de vos fichiers JavaScript, en supprimant les commentaires, les espaces, en raccourcissant le nom des variables, etc. On dit qu'il minifie les fichiers (il ne les compresse pas comme un fichier zip). Le code devient bien sr compltement illisible ! Mais c'est l qu'Assetic intervient de nouveau : il vous permet de garder votre version claire, mais compresse les fichiers pour vos visiteurs ! V oici un exemple de diffrence entre un fichier minifi et un fichier normal, avec la clbre librairie jQuery en JavaScript : version normale, 229 Ko : http://code.jquery.com/jquery-1.7.1.js ; version minifie , 89 Ko : http://code.jquery.com/jquery-1.7.1.min.js (le commentaire de licence en dbut de fichier a t gard). La taille du fichier a t divise par 2,5 !

Conclusion
Grce Assetic, on peut optimiser trs facilement nos scripts CSS/JS. Par exemple, nous pouvons passer de nos huit requtes pour 500 Ko seulement deux requtes (1 CSS + 1 JS) pour 200 Ko. Le temps d'affichage de la page pour nos visiteurs sera donc bien plus rapide tout en conservant la lisibilit du code ct dveloppeur !

Pratique : Assetic la rescousse ! Utilisation simple


Assetic est dj activ par dfaut dans Symfony2, donc vous n'avez rien faire de ce ct, vous pouvez l'utiliser directement. L'objectif d'Assetic est de faire regrouper nos fichiers CSS et Javascript comme nous venons d'en parler. Allez donc dans le template du layout , nous allons y dclarer nos fichiers CSS et JavaScript. V oici un block stylesheets classique : Code : HTML & Django {% block stylesheets %}

www.siteduzero.com

Partie 5 : Astuces et points particuliers


<link rel="stylesheet" href="{{ asset('css/bootstrap.css') }}" type="text/css" /> {% endblock %}

191/201

Modifiez-le de la manire suivante : Code : HTML & Django {% block stylesheets %} {% stylesheets output='style.css' filter='cssrewrite' 'css/*' 'bundles/sdzblog/css/*' %} <link rel="stylesheet" href="{{ asset_url }}" type="text/css" /> {% endstylesheets %} {% endblock %}

Et bien sr il en va de mme pour les fichiers Javascript : Code : HTML & Django {% block javascripts %} {% javascripts output='main.js' 'js/*' 'bundles/sdzblog/js/*' %} <script type="text/javascript" src="{{ asset_url }}"></script> {% endjavascripts %} {% endblock %}

Pensez bien mettre les fichiers Javascript en toute fin du code HTML. C'est parce que lorsque le navigateur intercepte une dclaration de fichier JavaScript, il arrte le rendu de la page, charge le fichier, l'excute, et ensuite seulement continue le rendu de la page. En effet, le navigateur ne peut pas savoir d'avance si le script JavaScript veut changer quelque chose l'apparence de la page, il est donc oblig de tout bloquer avant de continuer. Si vos scripts JavaScript sont bien faits, vous pouvez sans problme les dclarer en fin de page. Ainsi, le navigateur se bloquera pour vos fichiers JavaScript une fois que le rendu de la page sera termin ! Ce qui fait que c'est transparent pour vos visiteurs : ils peuvent dj profiter de la page pendant que les scripts JS se chargent en arrire plan.

Le principe des balises {% stylesheets %} et {% javascripts %}


Ces balises demandent en paramtre : Le paramtre output : il dfinit l'URL que devra avoir le fichier unique gnr. J'ai mis style.css et main.js mais vous pouvez mettre ce que vous voulez ; Le paramtre filter : il dfinit les filtres appliquer sur vos fichiers, nous en reparlons plus bas ; Les fichiers regrouper entre quotes et sans sparateur, part un espace (ou un retour la ligne) : ce sont les fichiers qu'Assetic va aller chercher. V ous pouvez utiliser le joker ( * ) pour prendre tout, mais on aurait pu spcifier le chemin des fichiers CSS exacts utiliser. V ous pouvez mettre autant de fichiers que vous voulez, plusieurs bundles, etc. Et ces balises vous donnent en sortie la variable {{ asset_url }} qui est le chemin vers le fichier unique que vous pouvez utiliser dans une balise <link> ou <script>. V ous pouvez remarquer que j'ai regroup tous les fichiers css du rpertoire /web/css/* avec tous ceux du rpertoire /web/bundles/sdzblog/css/*. Si vous n'avez aucun fichier CSS dans le bundle, il faut en rajouter un maintenant pour comprendre la suite. Ajoutez un fichier arbitrairement nomm src/Sdz/BlogBundle/Resources/public/css/blog.css, avec juste ces quelques lignes pour vrifier qu'il se charge bien : Code : CSS h1 { color: red; }

Bien entendu, lorsque vous modifiez vos ressources dans le rpertoire du bundle src/App/Bundle, vrifiez que celles-ci sont bien jour dans le rpertoire /web/bundles/appbundle. Si vous avez publi vos ressources au dbut avec l'option --symlink, le rpertoire /web/bundles/appbundle est un raccourci vers /src/App/Bundle/Resources/public et tout est bon pour vous. Sinon, excutez la commande suivante : php app/console assets:install --symlink web qui crera les raccourcis pour vous.

La particularit du mode dev

www.siteduzero.com

Partie 5 : Astuces et points particuliers


La particularit du mode dev
J'espre que vous avez dj essay le code ci-dessus dans votre page. Mais... en regardant la source, vous pouvez voir que vos fichiers ne sont pas du tout regroups en un fichier unique ! C'est parce que vous essayez en mode dev , le mode qui vous facilite la vie lors du dveloppement et du dbogage . Du coup, il ne vous regroupe pas les fichiers, car impossible de dboguer un script JavaScript quand tout est au mme endroit ! Assetic ne regroupe les fichiers que pour le mode prod , mais attention pour ce mode les choses sont un peu diffrentes. Il ncessite encore quelques manipulations, que nous ferons plus bas dans ce chapitre, patience .

192/201

Appliquer des filtres


Pour appliquer des filtres sur vos fichiers avant qu'Assetic ne les regroupe, il faut utiliser le paramtre filter que j'ai utilis pour les feuilles de style CSS dans mon code au-dessus. La liste complte des filtres disponibles se trouve dans la documentation : https://github.com/kriswallsmith/assetic. Comme ils sont tous dans la doc, je ne vais pas les prsenter ici, sauf les deux plus importants : cssrewrite et yui .

Le filtre cssrewrite
Ce filtre permet de rsoudre un problme qui apparat lors du regroupement de vos CSS. Prenons l'exemple d'un fichier CSS situ dans /web/bundles/appbundle/css/style.css qui fait appel une image, comme cela : Code : CSS h1 { background: url('../img/trip.png') !important; }

L'image "trip.png" se trouve bien dans le rpertoire /web/bundles/appbundle/img/trip.png. Si depuis votre page HTML vous faites appel au fichier CSS /web/bundles/appbundle/css/style.css, tout se passera bien. En effet, le navigateur en lisant la ligne background du CSS, ira chercher l'image au bon endroit. Seulement, grce Assetic tous nos CSS seront regroups l'adresse /web/style.css (le chemin que vous avez prcis dans le paramtre "output"). Or, depuis l'URL /web/style.css, votre navigateur ira chercher l'image dans le rpertoire /web/../img/trip.png... sauf qu'elle n'a pas chang d'endroit cette image ! C'est l qu'intervient le filtre cssrewrite. Il va modifier votre css pour transformer la ligne du background comme cela : Code : CSS background: url('bundles/sdzblog/img/trip.png')

Ce qui est bien ce que votre navigateur attend !

Les filtres yui_css et yui_js


Ces filtres sont trs utiles, ce sont ceux qui minifient les fichiers avec YUICompressor. Pour utiliser l'outil YUI compressor , il faut que vous l'ayez, videmment : rendez-vous ici pour le tlcharger manuellement. Copiez le fichier yuicompressor-2.4.7.jar (du rpertoire build de l'archive) dans le rpertoire app/java, par exemple. Maintenant, direction app/config/config.yml pour dire Symfony2 o nous l'avons mis. Rendez-vous la partie assetic: et modifiez-la comme suit : Code : Autre # app/config/config.yml # Assetic Configuration assetic: debug: %kernel.debug% use_controller: false # java: /usr/bin/java filters: cssrewrite: ~ yui_js: jar: %kernel.root_dir%/java/yuicompressor-2.4.7.jar yui_css: jar: %kernel.root_dir%/java/yuicompressor-2.4.7.jar

(Vrifiez les numros de version, suivant que vous ayez une version plus rcente que ma version 2.4.7.) Le %kernel.root_dir% reprsente le rpertoire de votre application, savoir le rpertoire app/

Si vous tes sous Windows ou que votre excutable Java ne se trouve pas dans /usr/bin/java, il faut alors

www.siteduzero.com

Partie 5 : Astuces et points particuliers


dcommenter le paramtre "java" dans la configuration d'Assetic. Changez sa valeur en C:\Program Files\Java\jre6\bin\java.exe (par exemple, vous de voir ou est votre fichier java.exe).

193/201

V oil, nous venons d'activer les filtres yui_js et yui_css , on peut maintenant les utiliser depuis nos templates. Ajoutez ce filtre dans vos balises : Code : HTML & Django {% stylesheets output='style.css' filter='cssrewrite, yui_css'

Et de mme pour les fichiers Javascript : Code : HTML & Django {% javascripts output='main.js' filter='yui_js'

Testez le rendu ! Mais on est toujours en mode dev et les fichiers sont devenus illisibles pour un ventuel dbogage ! Heureusement, vous avez la possibilit de dire qu'un filtre ne s'applique pas en mode dev . Il suffit de mettre un point d'interrogation devant : {% javascripts output='js/main.js' filter='?yui_js' ..... Ainsi, le filtre ne s'appliquera qu'en mode prod , tout comme le groupement des fichiers en un seul. Au final, notre mode dev n'a pas chang d'un poil, on garde nos fichiers et ces derniers sont lisibles, mais le mode prod a reu toutes les optimisations : regroupement des fichiers ainsi que minification .

Gestion du mode prod


Si vous n'avez pas encore test le rendu en mode prod , faites-le. a ne fonctionne pas ? V os fichiers CSS et JS ne sont pas chargs ? C'est normal. Nous n'avons pas fini notre mise en place.

Comprendre Assetic
Pour comprendre pourquoi la gestion du mode prod demande un effort supplmentaire, vous devez comprendre la manire dont Assetic fonctionne. Lorsque l'on utilise la balise {% stylesheets %} ou {% javascripts %}, on fait appel depuis notre code HTML un fichier /css/style.css (regardez la source de vos pages HTML). Or ce fichier n'existe pas ! En fait, en mode dev , Assetic cre une route qui intercepte l'URL /css/style.css, et le Kernel excute le contrleur d'Assetic qui fait tout le travail en direct. videmment, minifier et regrouper des fichiers la vole et pour chaque requte, cela prend beaucoup de temps. Et si en mode dev on peut se le permettre, on ne peut pas en mode prod ! Du coup, l'astuce est d'exporter en dur les fichiers CSS et JS dont on a besoin. Ainsi, en mode prod , le fichier /css/style.css existera en dur, Assetic n'interceptera pas l'URL, et votre serveur Web (souvent Apache) va envoyer directement le contenu du fichier vos visiteurs.

Exporter ses fichiers CSS et JS


Pour faire cet export en dur, il faut utiliser une simple commande d'Assetic : Code : Console php app/console --env=prod assetic:dump

Cette commande devrait vous sortir un rsultat de ce type : Code : Console D:\www\Symfony>php app/console --env=prod assetic:dump Dumping all prod assets. Debug mode is on. [dir+] D:\www\Symfony\app/../web/css [file+] D:\www\Symfony\app/../web/css/style.css [dir+] D:\www\Symfony\app/../web/js [file+] D:\www\Symfony\app/../web/js/main.js

Cette commande va lire tous vos templates pour y trouver les balises {% stylesheets %} et {% javascripts %}, puis va exporter en dur dans les fichiers /web/css/style.css et /web/js/main.js (dans notre cas, car c'est ce que nous avons mis dans le paramtre output ). Et voil, maintenant, nos fichiers existent rellement. Testez nouveau le rendu en mode prod : c'est bon ! Attention : exporter nos fichiers en dur est ponctuel. Ainsi, chaque fois que vous modifiez vos fichiers d'origine, vous devez excuter la commande assetic:dump pour mettre jour vos fichiers pour la production !

www.siteduzero.com

Partie 5 : Astuces et points particuliers

194/201

Heureusement, Assetic a pens tout, et il existe un moyen pour pallier cette limitation (excuter la mme commande chaque modification). Excutez donc plutt la commande suivante : Code : Console php app/console --env=prod assetic:dump --watch

Et regardez le rsultat. C'est une commande qui ne se termine jamais ! En fait, elle surveille les modifications que vous faites sur vos fichiers. la moindre modification d'un de vos fichiers, elle rgnre les fichiers compils. Essayez, modifiez un fichier et regarder l'invite de commande, vous voyez la rgnration s'oprer. Trs pratique en dveloppement (attention, on parle bien du mode prod ici, c'est lorsque vous voulez vrifier que tout fonctionne bien en mode prod ). Et voil le rsultat : grce Assetic, on peut dlivrer nos visiteurs du contenu unique et minifi , mais garder notre organisation et nos fichiers en tant que dveloppeurs !

www.siteduzero.com

Partie 5 : Astuces et points particuliers

195/201

Utiliser la console directement depuis le navigateur !


La console est un outil bien pratique de Symfony2. Mais des fois, devoir ouvrir le terminal de Linux ou l'invite de commandes de Windows n'est pas trs agrable. Et je ne parle par des hbergements mutualiss qui n'offrent pas d'accs SSH pour utiliser la console ! Comment continuer d'utiliser la console dans ces conditions ? Ce chapitre est l pour vous expliquer cela !

Thorie : Le composant Console de Symfony2 Les commandes sont en PHP


Nous l'avons dj voqu au cours de ce tutoriel, les commandes Symfony2 sont bien de simples codes PHP ! Effectivement on les excute depuis une console, mais cela ne les empche en rien d'tre en PHP. Et comme elles sont en PHP... elle peuvent tout fait tre excutes depuis un autre script PHP. C'est en fait ce qui est dj fait par le script PHP de la console, celui que l'on excute chaque fois : le fichier app/console. V oici son contenu : Code : PHP #!/usr/bin/env php <?php require_once __DIR__.'/bootstrap.php.cache'; require_once __DIR__.'/AppKernel.php'; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Input\ArgvInput; $input = new ArgvInput(); $env = $input->getParameterOption(array('--env', '-e'), getenv('SYMFONY_ENV') ?: 'dev'); $debug = !$input->hasParameterOption(array('--no-debug', '')); $kernel = new AppKernel($env, $debug); $application = new Application($kernel); $application->run();

Comme vous pouvez le voir, ce fichier ressemble beaucoup au contrleur frontal, app.php. Il charge galement le Kernel. La seule chose qu'il fait de diffrent, c'est d'utiliser le composant Console de Symfony2 , en instanciant la classe Application (ligne 15). C'est cet objet qui va ensuite excuter les diffrentes commandes dfinies en PHP dans les bundles.

Exemple d'une commande


Chaque commande est dfinie dans une classe PHP distincte, que l'on place dans le rpertoire Command des bundles. Ces classes comprennent entres autres deux mthodes : configure() : Elle dfini le nom, les arguments et la description de la commande ; execute() : Elle excute la commande proprement parler.

Prenons l'exemple de la commande list , qui liste toutes les commandes disponibles dans l'application. Elle est dfinie dans le fichier vendor/symfony/src/Component/Console/Command/ListCommand.php, dont voici le contenu : Code : PHP <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Command; use use use use use use Symfony\Component\Console\Input\InputArgument; Symfony\Component\Console\Input\InputOption; Symfony\Component\Console\Input\InputInterface; Symfony\Component\Console\Output\OutputInterface; Symfony\Component\Console\Output\Output; Symfony\Component\Console\Command\Command;

/** * ListCommand displays the list of all available commands for the application. * * @author Fabien Potencier <fabien@symfony.com> */ class ListCommand extends Command {

www.siteduzero.com

Partie 5 : Astuces et points particuliers


/** * {@inheritdoc} */ protected function configure() { $this ->setDefinition(array( new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), new InputOption('xml', null, InputOption::VALUE_NONE, 'To output help as XML'), )) ->setName('list') ->setDescription('Lists commands') ->setHelp(<<<EOF The <info>list</info> command lists all commands: <info>php app/console list</info> You can also display the commands for a specific namespace: <info>php app/console list test</info> You can also output the information as XML by using the <comment>-xml</comment> option: <info>php app/console list --xml</info> EOF ); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { if ($input->getOption('xml')) { $output->writeln($this->getApplication()->asXml($input>getArgument('namespace')), OutputInterface::OUTPUT_RAW); } else { $output->writeln($this->getApplication()->asText($input>getArgument('namespace'))); } } }

196/201

V ous distinguez bien ici les deux mthodes qui composent la commande list. En vous basant sur cet exemple, vous tes d'ailleurs capable d'crire votre propre commande : ce n'est vraiment pas compliqu ! Mais revenons au but de ce chapitre, qui est de pouvoir utiliser ces commande depuis le navigateur.

Pratique : Utiliser un ConsoleBundle ConsoleBundle ?


V ous le savez srement, la communaut de Symfony2 est trs active, et un nombre impressionnant de bundles ont vu le jour depuis la sortie de Symfony2. V ous pouvez les retrouver presque tous sur ce site : http://knpbundles.com/ qui les recense. Il doit srement y avoir plusieurs bundles qui fournissent une console dans le navigateur, mais je vous propose d'en installer un en particulier : CoreSphereConsoleBundle. C'est un bundle simple qui rempli parfaitement sa tche, et dont l'interface est trs pratique. V oyez par vous-mmes :

www.siteduzero.com

Partie 5 : Astuces et points particuliers

197/201

Installer CoreSphereConsoleBundle
L'installation d'un tel bundle est vraiment simple, attaquons-la ds maintenant.

Tlcharger CoreSphereConsoleBundle
Pour installer ce bundle, je vous propose de tlcharger une version que j'ai modifie. J'ai entre autres rsolu quelques petits bugs et traduit les messages en franais. L'adresse du bundle est donc la suivante : https://github.com/winzou/ConsoleBundle . Cliquez sur Download et tlchargez l'archive (le format zip est recommand pour les utilisateurs de Windows). Dcompressez ensuite l'archive dans le rpertoire vendor/bundles/CoreSphere/ConsoleBundle.

Enregistrement du namespace et du bundle


Comme chaque installation de nouveau bundle, il faut enregistrer le namespace " CoreSphere" dans app/autoload.php : Code : PHP <?php use Symfony\Component\ClassLoader\UniversalClassLoader; use Doctrine\Common\Annotations\AnnotationRegistry; $loader = new UniversalClassLoader(); $loader->registerNamespaces(array( 'Symfony' => array(__DIR__.'/../vendor/symfony/src', __DIR__.'/../vendor/bundles'), 'Sensio' => __DIR__.'/../vendor/bundles', 'JMS' => __DIR__.'/../vendor/bundles', 'Doctrine\\Common' => __DIR__.'/../vendor/doctrine-common/lib', 'Doctrine\\DBAL' => __DIR__.'/../vendor/doctrine-dbal/lib', 'Doctrine' => __DIR__.'/../vendor/doctrine/lib', 'Monolog' => __DIR__.'/../vendor/monolog/src', 'Assetic' => __DIR__.'/../vendor/assetic/src', 'Metadata' => __DIR__.'/../vendor/metadata/src', // D'autres namespaces que vous auriez dj pu ajouter 'CoreSphere' => __DIR__.'/../vendor/bundles', )); // ...

Puis il faut enregistrer le bundle " CoreSphereConsoleBundle" dans app/AppKernel.php

www.siteduzero.com

Partie 5 : Astuces et points particuliers


Code : PHP <?php use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Config\Loader\LoaderInterface; class AppKernel extends Kernel { public function registerBundles() { $bundles = array( new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new Symfony\Bundle\SecurityBundle\SecurityBundle(), new Symfony\Bundle\TwigBundle\TwigBundle(), new Symfony\Bundle\MonologBundle\MonologBundle(), new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(), new Symfony\Bundle\DoctrineBundle\DoctrineBundle(), new Symfony\Bundle\AsseticBundle\AsseticBundle(), new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), new JMS\SecurityExtraBundle\JMSSecurityExtraBundle(), ); { // D'autres bundles que vous auriez dj pu ajouter

198/201

if (in_array($this->getEnvironment(), array('dev', 'test')))

$bundles[] = new Acme\DemoBundle\AcmeDemoBundle(); $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle(); $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle(); // D'autres bundles que vous auriez dj pu ajouter // On enregistre ce bundle uniquement pour l'environnement de dveloppement videmment $bundles[] = new CoreSphere\ConsoleBundle\CoreSphereConsoleBundle(); } } // ... } return $bundles;

Enregistrement des routes


Pour paramtrer un bundle, on fait comme toujours : on lit sa documentation. La documentation se trouve soit dans le readme, soit dans le rpertoire Resources/doc, cela dpend des bundles. Dans notre cas, elle se trouve dans le readme : https://github.com/winzou/ConsoleBundl [...] ter/readme.md Pour les routes, il faut donc enregistrer le fichier dans notre routing_dev.yml. On ne les met pas dans routing.yml car la console ne doit tre accessible qu'en mode dev, on a enregistr le bundle que pour ce mode. Ajoutez donc la fin de app/config/routing_dev.yml : Code : Autre console: resource: "@CoreSphereConsoleBundle/Resources/config/routing.yml"

Dfinition du layout
Par dfaut, le layout de ce bundle tend la vue ::base.html.twig, situe dans app/Resources/views/base.html.twig. Si vous n'avez pas supprim cette vue, tout est bon. Sinon, recrez-la avec ce contenu : Code : HTML <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>{% block title %}Welcome!{% endblock %}</title> {% block stylesheets %}{% endblock %} </head>

www.siteduzero.com

Partie 5 : Astuces et points particuliers


</head> <body> {% block body %}{% endblock %} {% block javascripts %}{% endblock %} </body> </html>

199/201

Cette vue correspond au strict minimum, et dfinit les 4 blocs utiliss par le bundle : title, stylesheets, body et javascripts.

Publier les assets


L'installation touche sa fin, il ne reste plus qu' rendre disponible les fichiers JS et CSS du bundle, qui se fait comme vous le savez grce la commande suivante : Code : Console php app/console assets:install --symlink web

C'est fini ! Il ne reste plus qu' utiliser notre nouvelle console.

Utilisation de la console dans son navigateur


Par dfaut, le bundle dfinit la route /console pour afficher la console. Allez donc l'adresse http://localhost/Symfony/web/app_dev.php/console et profitez ! Bien entendu, pour excuter des commandes Symfony2 depuis cette interface, il ne faut pas faire php app/console la_commande , mais uniquement la_commande ! Le script PHP du bundle n'utilise pas le script app/console, il utilise directement le composant Console.

Pour les utilisateurs de Windows, vous pouvez remarquer que le rsultats des commandes est en couleur. Et oui, Symfony2 est plus fort que l'invite de commande de Windows, il gre les couleurs ! En plus de l'adresse /console ddie, j'ai rajout un petit bouton console, regardez en bas droite dans la barre d'outils de Symfony :

Cliquez dessus, et une petite console s'ouvre par dessus votre page. Pratique pour excuter une commande rapidement ! Pour enlever la console, recliquez sur le bouton.

Prt pour l'hbergement mutualis


Et voil vous tes donc prt pour utiliser la console de votre application sur les hbergements mutualiss qui n'offrent gnralement pas d'accs SSH. V ous disposez maintenant d'une console accessible depuis votre navigateur : cela va vous simplifier la vie croyez-moi ! N'hsitez pas faire vos retours sur le bundle directement via les issues sur github : https://github.com/winzou/ConsoleBundle/issues . La liste des astuces n'est pas dfinitive et ne le sera jamais : j'en rajouterai ds que je les rencontrerai moi-mme lorsque je coderai. Si vous connaissez une astuce intressante qui n'est pas encore traite dans cette partie, vous pouvez aussi m'envoyer un MP pour que je puisse l'ajouter ici.

Avancement du cours
Ce cours est en pleine phase de rdaction. Je publierai les parties au fur et mesure afin qu'un maximum de personnes puisse profiter des premiers cours francophones sur Symfony2 . N'hsitez pas repasser plus tard ou vous abonner au flux RSS du cours .

En attendant
En attendant les prochains chapitres... ne chmez pas ! La documentation officielle est trs bien faite, n'hsitez pas parcourir les nombreux bundles qui existent dj, mais surtout, entranez-vous !

Licences
Certaines images de ce tutoriel sont tires de la documentation officielle. Elles sont donc soumises la licence suivante : Citation : Sensio Labs Copyright (c) 2004-2010 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell

www.siteduzero.com

Partie 5 : Astuces et points particuliers


copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY , FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY , WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

200/201

www.siteduzero.com

Das könnte Ihnen auch gefallen