Beruflich Dokumente
Kultur Dokumente
www.siteduzero.com
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
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
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
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
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
www.siteduzero.com
Partager
6/201
Par
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
7/201
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 !
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
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 !
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 !
Pour les autres, tlchargez l'archive traditionnelle avec les vendors, on parlera de Composer dans un prochain chapitre.
www.siteduzero.com
9/201
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
10/201
V ous pouvez ds prsent excuter Symfony2, flicitations ! Rendez-vous sur la page http://localhost/Symfony/web/app_dev.php et admirez :
www.siteduzero.com
11/201
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
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.
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
13/201
www.siteduzero.com
14/201
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.
www.siteduzero.com
15/201
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
16/201
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
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.
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.
1. Choisir le namespace
Symfony2 vous demande le namespace de votre bundle :
www.siteduzero.com
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.
www.siteduzero.com
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 .
Faisons simple et demandons Symfony2 de tout nous gnrer. Entrez donc yes .
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
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 .
$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
21/201
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
22/201
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.
www.siteduzero.com
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.
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
24/201
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
25/201
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 ! .
www.siteduzero.com
26/201
Dans ce template, nous n'avons utilis ni variable, ni structure Twig. En fait, c'est un simple fichier contenant du code HTML pur !
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
27/201
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
www.siteduzero.com
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.
www.siteduzero.com
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 }
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 :
www.siteduzero.com
30/201
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 !
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 !
www.siteduzero.com
31/201
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
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
33/201
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.
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.
www.siteduzero.com
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);
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.
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
35/201
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
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
37/201
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.
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
38/201
V oici donc la premire manire de rcuprer des arguments : ceux contenus dans la route.
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-
www.siteduzero.com
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.
www.siteduzero.com
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
41/201
www.siteduzero.com
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') !
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
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
44/201
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
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();
www.siteduzero.com
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
www.siteduzero.com
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.
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
48/201
www.siteduzero.com
49/201
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
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.).
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 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 Utiliser un filtre avec des arguments. Attention il faut que date soit un objet de type Datetime ici. Date : {{ date|date('d/m/Y') }}
www.siteduzero.com
51/201
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 }}
Date
Format
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
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<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.
{{ 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.
www.siteduzero.com
53/201
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) { // ... }
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
54/201
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++; }
La documentation de tous les tests disponibles est dans la documentation officielle de Twig : http://twig.sensiolabs.org/doc/tests/index.html
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
55/201
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
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.
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
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 !
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
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>
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
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 » </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 © 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
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.
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
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() ));
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
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
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 )); }
www.siteduzero.com
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
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 )); }
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
66/201
www.siteduzero.com
67/201
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 !
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
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
69/201
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
70/201
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
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.
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
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 !
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.
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
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.
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
74/201
name
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
75/201
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
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 !
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
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.
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
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.
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 :
www.siteduzero.com
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
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.
www.siteduzero.com
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.
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).
www.siteduzero.com
82/201
remove($entite)
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
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
84/201
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 !
www.siteduzero.com
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
www.siteduzero.com
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;
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
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
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
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 !
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
90/201
www.siteduzero.com
91/201
Entit inverse, Article : Code : PHP <?php /** * @ORM\Entity */ class Article { // Nul besoin de rajouter de proprit, ici. } // ...
www.siteduzero.com
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
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
94/201
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
95/201
/** * @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
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
97/201
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
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
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
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.
www.siteduzero.com
101/201
www.siteduzero.com
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
103/201
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]);
www.siteduzero.com
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
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
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.
www.siteduzero.com
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
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);
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
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
110/201
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.
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 !
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
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
112/201
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.
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
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
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
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
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.
www.siteduzero.com
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.
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
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.
www.siteduzero.com
119/201
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".
www.siteduzero.com
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 .
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
121/201
www.siteduzero.com
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
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();
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
124/201
www.siteduzero.com
125/201
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
126/201
www.siteduzero.com
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
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 .
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
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
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 )); } }
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.
www.siteduzero.com
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 .
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
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
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
134/201
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
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.
www.siteduzero.com
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
www.siteduzero.com
137/201
www.siteduzero.com
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
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()
www.siteduzero.com
139/201
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
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
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.
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
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;
www.siteduzero.com
143/201
www.siteduzero.com
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.
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
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.
www.siteduzero.com
146/201
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)
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="<div> <label class=" required">$$name$$</label> <div id="sdz_blogbundle_articletype_tags_$$name$$"> <div><label for="sdz_blogbundle_articletype_tags_$$name$$_nom" class=" required">Nom</label> <input type="text" id="sdz_blogbundle_articletype_tags_$$name$$_nom" name="sdz_blogbundle_articletype[tags][$$name$$][nom]" required="required" maxlength="255" value="" /></div></div></div>"> </div>
www.siteduzero.com
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
148/201
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
149/201
public function getName() { return 'sdz_blogbundle_articletype'; } public function getDefaultOptions(array $options) { return array( 'data_class' => 'Sdz\BlogBundle\Entity\Article', ); }
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
150/201
www.siteduzero.com
151/201
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 !
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
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
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.
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
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.
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
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
155/201
V ous pouvez vous amuser avec le contenu de l'entit Article pour voir comment ragit le validateur
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.
www.siteduzero.com
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.
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
157/201
// 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 .
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
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
159/201
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.
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.
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
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
161/201
www.siteduzero.com
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 !
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
163/201
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.
www.siteduzero.com
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
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
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.
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
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
# 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 .
www.siteduzero.com
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 !
www.siteduzero.com
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 !
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
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 !
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 .
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
170/201
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.
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
171/201
www.siteduzero.com
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.
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
173/201
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
174/201
Pour avoir la liste des services disponibles, utilisez la commande Code : Console php app/console container:debug
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('...').
www.siteduzero.com
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
176/201
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
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
178/201
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
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
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
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.
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
182/201
public function getName() { return 'sdz_blogbundle_articletype'; } public function getDefaultOptions(array $options) { return array( 'data_class' => 'Sdz\BlogBundle\Entity\Article', ); }
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
templating.helper
www.siteduzero.com
183/201
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 }
www.siteduzero.com
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
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
186/201
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.
www.siteduzero.com
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
188/201
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.
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).
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
189/201
www.siteduzero.com
190/201
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.
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 !
www.siteduzero.com
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.
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.
www.siteduzero.com
192/201
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')
(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
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 .
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.
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
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
195/201
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.
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
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.
www.siteduzero.com
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.
www.siteduzero.com
198/201
$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;
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
199/201
Cette vue correspond au strict minimum, et dfinit les 4 blocs utiliss par le bundle : title, stylesheets, body et javascripts.
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.
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
200/201
www.siteduzero.com