Vous êtes sur la page 1sur 510

Programmez intelligent

39
9
7
8
2
2
1
2
1
2
4
9
4
1
avec
les
Cahiers
du
Programmeur
C
o
n
c
e
p
t
i
o
n

c
o
u
v
e
r
t
u
r
e
:

N
o
r
d
c
o
m
p
o
Fabien Potencier est ing-
nieur civil des Mines de Nancy et
diplm du mastre Entrepre-
neurs HEC. Il a cr le frame-
work Symfony dont il est le dve-
loppeur principal. Co-fondateur
de Sensio, il dirige Sensio Labs,
agence spcialise dans les tech-
nologies Open Source.
Diplm dune licence spcialise
en dveloppement informatique,
Hugo Hamon a rejoint Sensio
Labs en tant que dveloppeur
web. Passionn par PHP, il a
fond le site
et promeut le langage en milieu
professionnel en sinvestissant
dans lAFUP et dans la commu-
naut Symfony.
Sommaire
Une tude de cas Symfony : Jobeet Bonnes pratiques Environnements dexcution
Configurer le serveur web Le serveur virtuel Intgrer Subversion Spcifications fonc-
tionnelles tude des besoins Concevoir le modle Configurer MySQL LORM Doctrine
Schma de la base Architecture MVC Le contrleur : les actions La vue : les tem-
plates Images et feuilles de style Helpers Erreur 404 Interaction client/serveur
Le framework de routage Configuration des URL Routage muler HTTP PUT et
DELETE Dbogage Optimiser le modle Dboguer les requtes SQL Refactoring
MVC en continu Partiels Slots Composants Tests unitaires Le framework Lime
Intgrit du modle Maintenabilit du code Tests fonctionnels Simuler le naviga-
teur Tester lapplication Gestion des formulaires Valider les donnes Intgration
dans les templates et actions Scurit Attaques CSRF et XSS Maintenance auto-
matise Interface dadministration Gnration automatique Configuration des vues
Ergonomie Ajout de fonctionnalits Authentification et droits daccs Sessions
Politique de droits Scuriser le backend Flux de syndication Atom et services web
XML, JSON et YAML Envoi de-mails Moteur de recherche PHP Lucene Dynamiser
linterface avec Ajax JavaScript jQuery Requtes Ajax Internationalisation et locali-
sation Support des langues, jeux de caractres et encodages Traduction dynamique
Plug-ins Symfony Gestion du cache Rduire les temps de chargement Dploiement
en production Connexion SSH et rsync Le format YAML Fichiers de configuration
settings.yml et factories.yml.
les
Cahiers
du
Programmeur
F
.

P
o
t
e
n
c
i
e
r
H
.

H
a
m
o
n
S
y
m
f
o
n
y
C
o
d
e

d
i
t
e
u
r
:

G
1
2
4
9
4
I
S
B
N
:

9
7
8
-
2
-
2
1
2
-
1
2
4
9
4
-
1

Symfony
Reconnu dans le monde pour sa puissance et son lgance, Symfony est issu de
plus de dix ans de savoir-faire. Le framework open source de Sensio fdre une
trs forte communaut de dveloppeurs PHP professionnels. Il leur offre des
outils et un environnement MVC pour crer des applications web robustes, main-
tenables et volutives.
Au fil dune dmarche rigoureuse et dun exemple concret dapplication web 2.0,
ce cahier dcrit le bon usage des outils Symfony mis la disposition du dvelop-
peur : de larchitecture MVC et autres design patterns labstraction de base de
donnes et au mapping objet-relationnel avec Doctrine, en passant par les tests
unitaires et fonctionnels, la gestion des URL, des formulaires ou du cache,
linternationalisation ou encore la gnration des interfaces dadministration
Mieux dvelopper en PHP
avec Symfony 1.2 et Doctrine
@
Adapt du tutoriel Jobeet mis jour en franais Tlchargez le code source !
http://www.symfony-project.org/jobeet/
Hugo Hamon
Fabien Potencier
G12494_Symfony_Couv 24/04/09 10:45 Page 1
Algeria-Educ.com
Programmez intelligent
avec
les
Cahiers
du
Programmeur
C
o
n
c
e
p
t
i
o
n

c
o
u
v
e
r
t
u
r
e
:

N
o
r
d
c
o
m
p
o
Fabien Potencier est ing-
nieur civil des Mines de Nancy et
diplm du mastre Entrepre-
neurs HEC. Il a cr le frame-
work Symfony dont il est le dve-
loppeur principal. Co-fondateur
de Sensio, il dirige Sensio Labs,
agence spcialise dans les tech-
nologies Open Source.
Diplm dune licence spcialise
en dveloppement informatique,
Hugo Hamon a rejoint Sensio
Labs en tant que dveloppeur
web. Passionn par PHP, il a
fond le site
et promeut le langage en milieu
professionnel en sinvestissant
dans lAFUP et dans la commu-
naut Symfony.
Sommaire
Une tude de cas Symfony : Jobeet Bonnes pratiques Environnements dexcution
Configurer le serveur web Le serveur virtuel Intgrer Subversion Spcifications fonc-
tionnelles tude des besoins Concevoir le modle Configurer MySQL LORM Doctrine
Schma de la base Architecture MVC Le contrleur : les actions La vue : les tem-
plates Images et feuilles de style Helpers Erreur 404 Interaction client/serveur
Le framework de routage Configuration des URL Routage muler HTTP PUT et
DELETE Dbogage Optimiser le modle Dboguer les requtes SQL Refactoring
MVC en continu Partiels Slots Composants Tests unitaires Le framework Lime
Intgrit du modle Maintenabilit du code Tests fonctionnels Simuler le naviga-
teur Tester lapplication Gestion des formulaires Valider les donnes Intgration
dans les templates et actions Scurit Attaques CSRF et XSS Maintenance auto-
matise Interface dadministration Gnration automatique Configuration des vues
Ergonomie Ajout de fonctionnalits Authentification et droits daccs Sessions
Politique de droits Scuriser le backend Flux de syndication Atom et services web
XML, JSON et YAML Envoi de-mails Moteur de recherche PHP Lucene Dynamiser
linterface avec Ajax JavaScript jQuery Requtes Ajax Internationalisation et locali-
sation Support des langues, jeux de caractres et encodages Traduction dynamique
Plug-ins Symfony Gestion du cache Rduire les temps de chargement Dploiement
en production Connexion SSH et rsync Le format YAML Fichiers de configuration
settings.yml et factories.yml.
les
Cahiers
du
Programmeur
F
.

P
o
t
e
n
c
i
e
r
H
.

H
a
m
o
n
S
y
m
f
o
n
y
Symfony
Reconnu dans le monde pour sa puissance et son lgance, Symfony est issu de
plus de dix ans de savoir-faire. Le framework open source de Sensio fdre une
trs forte communaut de dveloppeurs PHP professionnels. Il leur offre des
outils et un environnement MVC pour crer des applications web robustes, main-
tenables et volutives.
Au fil dune dmarche rigoureuse et dun exemple concret dapplication web 2.0,
ce cahier dcrit le bon usage des outils Symfony mis la disposition du dvelop-
peur : de larchitecture MVC et autres design patterns labstraction de base de
donnes et au mapping objet-relationnel avec Doctrine, en passant par les tests
unitaires et fonctionnels, la gestion des URL, des formulaires ou du cache,
linternationalisation ou encore la gnration des interfaces dadministration
Mieux dvelopper en PHP
avec Symfony 1.2 et Doctrine
@
Adapt du tutoriel Jobeet mis jour en franais Tlchargez le code source !
http://www.symfony-project.org/jobeet/
Hugo Hamon
Fabien Potencier
G12494_Symfony_Couv 24/04/09 10:45 Page 1
les
Cahiers
du
Programmeur
Symfony
12494_Symfony_Pdt 16/04/09 11:49 Page 1
ColleCtion les Cahiers du programmeur
G. ponon et J. Pauli. Zend Framework. N12392, 2008, 460 pages.
l. Jayr. Flex 3. Applications Internet riches. N12409, 2009, 226 pages.
p. roques. UML 2. Modliser une application web. N12389, 6
e
dition, 2008, 247 pages
a. gonCalves. Java EE 5. N12363, 2
e
dition, 2008, 370 pages
e. puybaret. Swing. N12019, 2007, 500 pages
e. puybaret. Java 1.4 et 5.0. N11916, 3
e
dition, 2006, 400 pages
J. molire. J2EE. N11574, 2
e
dition, 2005, 220 pages
R. Fleury Java/XML. N11316, 2004, 218 pages
J. protzenko, B. PiCaud. XUL. N11675, 2005, 320 pages
S. mariel. PHP 5. N11234, 2004, 290 pages
Chez le mme diteur
C. porteneuve. Bien dvelopper pour le Web 2.0. N12391, 2
e
dition 2008, 600 pages.
e. daspet, C. pierre de geyer. PHP 5 avanc. N12369, 5
e
dition, 2008, 844 pages
G. ponon. Best practices PHP 5. Les meilleures pratiques de dveloppement en PHP. N11676, 2005, 470 pages
t. ziad. Programmation Python. N12483, 2
e
dition, 2009, 530 pages
C. pierre de geyer, g. ponon. Mmento PHP 5 et SQL. N12457, 2
e
dition, 2009, 14 pages
J.-m. deFranCe. Premires applications Web 2.0 avec Ajax et PHP. N12090, 2008, 450 pages
d. seguy, p. gamaChe. Scurit PHP 5 et MySQL. N12114, 2007, 250 pages
a. vannieuwenhuyze. Programmation Flex 3. N12387, 2008, 430 pages
V. messager-rota. Gestion de projet. Vers les mthodes agiles. N12158, 2
e
dition, 2009, 252 pages
H. bersini, i. wellesz. Lorient objet. N12084, 3
e
dition, 2007, 600 pages
p. roques. UML 2 par la pratique. N12322, 6
e
dition, 368 pages
s. bordage. Conduite de projet Web. N12325, 5
e
dition, 2008, 394 pages
J. Dubois, J.-P. Retaill, T. Templier. Spring par la pratique. Java/J2EE, Spring, Hibernate, Struts, Ajax. n11710, 2006, 518 pages
a. bouCher. Mmento Ergonomie web. N12386, 2008, 14 pages
a. Fernandez-toro. Management de la scurit de linformation. Implmentation ISO 27001. N12218, 2007, 256 pages
ColleCtion aCCs libre
Pour que linformatique soit un outil, pas un ennemi !
conomie du logiciel libre. F. elie. N12463, 2009, 195 pages
Hackez votre Eee PC. Lultraportable efficace. C. guelFF. N12437, 2009, 306 pages
Joomla et Virtuemart Russir sa boutique en ligne. V. isaksen, T. tardiF. N12381, 2008, 270 pages
Open ERP Pour une gestion dentreprise efficace et intgre. F. pinCkaers, g. gardiner. N12261, 2008, 276 pages
Russir son site web avec XHTML et CSS. m. nebra. N12307, 2
e
dition, 2008, 316 pages
Ergonomie web. Pour des sites web efficaces. a. bouCher. N12479, 2
e
dition, 2009, 456 pages
Gimp 2 efficace Dessin et retouche photo. C. gmy. N12152, 2
e
dition, 2008, 402 pages
OpenOffice.org 3 efficace. s. gautier, g. bignebat, C. hardy, m. pinquier. N12408, 2009, 408 pages avec CD-Rom.
Russir un site web dassociation avec des outils libres. a.-l. et d. quatravaux. N12000, 2
e
dition, 2007, 372 pages
Russir un projet de site Web. n. Chu. N12400, 5
e
dition, 2008, 230 pages
les
Cahiers
du
Programmeur
Symfony
Fabien Potencier
Hugo Hamon
Mieux dvelopper en PHP
avec Symfony 1.2 et Doctrine
12494_Symfony_Pdt 16/04/09 11:49 Page 2
DITIONS EYROLLES
61, bd Saint-Germain
75240 Paris Cedex 05
www.editions-eyrolles.com
Le code de la proprit intellectuelle du 1
er
juillet 1992 interdit en effet expressment la photocopie usage collectif sans
autorisation des ayants droit. Or, cette pratique sest gnralise notamment dans les tablissements denseignement,
provoquant une baisse brutale des achats de livres, au point que la possibilit mme pour les auteurs de crer des uvres
nouvelles et de les faire diter correctement est aujourdhui menace.
En application de la loi du 11 mars 1957, il est interdit de reproduire intgralement ou partiellement le prsent ouvrage,
sur quelque support que ce soit, sans autorisation de lditeur ou du Centre Franais dExploitation du Droit de Copie, 20,
rue des Grands-Augustins, 75006 Paris.
Groupe Eyrolles, 2009, ISBN : 978-2-212-12494-1
Remerciements Franck Bodiot pour certaines illustrations douverture de chapitre.
Groupe Eyrolles, 2008
Aprs plus de trois ans dexistence en tant que projet Open Source,
Symfony est devenu lun des frameworks incontournables de la scne
PHP. Son adoption massive ne sexplique pas seulement par la richesse
de ses fonctionnalits ; elle est aussi due lexcellence de sa documenta-
tion probablement lune des meilleures pour un projet Open Source.
La sortie de la premire version officielle de Symfony a t clbre avec
la publication en ligne du tutoriel Askeet, qui dcrit la ralisation dune
application sous Symfony en 24 tapes prvues pour durer chacune une
heure. Publi Nol 2005, ce tutoriel devint un formidable outil de pro-
motion du framework. Nombre de dveloppeurs ont en effet appris
utiliser Symfony grce Askeet, et certaines socits lutilisent encore
comme support de formation.
Le temps passant, et avec larrive de la version 1.2 de Symfony, il fut
dcid de publier un nouveau tutoriel sur le mme format quAskeet. Le
tutoriel Jobeet fut ainsi publi jour aprs jour sur le blog officiel de Sym-
fony, du 1
er
au 24 dcembre 2008 ; vous lisez actuellement sa version
dite sous forme de livre papier.
Dcouvrir ltude de cas dveloppe
Cet ouvrage dcrit le dveloppement dun site web avec Symfony, depuis
ses spcifications jusqu son dploiement en production, en 21 chapitres
dune heure environ. Au travers des besoins fonctionnels du site dve-
lopper, chaque chapitre sera loccasion de prsenter non seulement les
fonctionnalits de Symfony mais galement les bonnes pratiques du
dveloppement web.
Avant-propos
COMMUNAUT
Une tude de cas communautaire
Pour Askeet, il avait t demand la commu-
naut des utilisateurs de Symfony de proposer une
fonctionnalit ajouter au site. Linitiative eut du
succs et le choix se porta sur lajout dun moteur
de recherche. Le vu de la communaut fut ra-
lis, et le chapitre consacr au moteur de
recherche est dailleurs rapidement devenu lun
des plus populaires du tutoriel.
Dans le cas de Jobeet, lhiver a t clbr le
21 dcembre avec lorganisation dun concours de
design o chacun pouvait soumettre une charte gra-
phique pour le site. Aprs un vote communautaire,
la charte de lagence amricaine centre{source}
fut choisie. Cest cette interface graphique qui sera
intgre tout au long de ce livre.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
VI
Lapplication dveloppe dans cet ouvrage aurait pu tre un moteur de
blog exemple souvent choisi pour dautres frameworks ou langages de
programmation. Nous souhaitions cependant un projet plus riche et plus
original, afin de dmontrer quil est possible de dvelopper facilement et
rapidement des applications web professionnelles avec Symfony. Cest
au chapitre 2 que vous en dcouvrirez les spcificits ; pour le moment,
seul son nom de code est mmoriser : Jobeet...
En quoi cet ouvrage est-il diffrent ?
On se souvient tous des dbuts du langage PHP 4. Ctait la belle poque
du Web ! PHP a certainement t lun des premiers langages de program-
mation ddi au Web et srement lun des plus simples matriser.
Mais les technologies web voluant trs vite, les dveloppeurs ont besoin
dtre en permanence lafft des dernires innovations et surtout des
bonnes pratiques. La meilleure faon deffectuer une veille technolo-
gique efficace est de lire des blogs dexperts, des tutoriels prouvs et
bien videmment des ouvrages de qualit. Cependant, pour des langages
aussi varis que le PHP, le Python, le Java, le Ruby, ou mme le Perl, il
est dcevant de constater quun grand nombre de ces ouvrages prsen-
tent une lacune majeure... En effet, ds quil sagit de montrer des exem-
ples de code, ils laissent de ct des sujets primordiaux, et pallient le
manque par des avertissements de ce genre :
Lors du dveloppement dun site, pensez aussi la validation et la
dtection des erreurs ;
Le lecteur veillera bien videmment ajouter la gestion de la
scurit ;
Lcriture des tests est laisse titre dexercice au lecteur.
Or chacune de ces questions validation, scurit, gestion des erreurs,
tests est primordiale ds quil sagit dcrire du code professionnel.
Comment ne pas se sentir, en tant que lecteur, un peu abandonn ? Si
ces contraintes de surcrot les plus complexes grer pour un dve-
loppeur ne sont pas prises en compte, les exemples perdent de leur
intrt et de leur exemplarit !
Le livre que vous tenez entre les mains ne contient pas davertissement
de ce type : une attention particulire est prte lcriture du code
ncessaire pour grer les erreurs et pour valider les donnes entres par
lutilisateur. Du temps est galement consacr lcriture de tests auto-
matiss afin de valider les dveloppements et les comportements
attendus du systme.
BONNE PRATIQUE Rutilisez le code libre quand
il est exemplaire !
Le code que vous dcouvrirez dans ce livre peut
servir de base vos futurs dveloppements ;
nhsitez surtout pas en copier-coller des bouts
pour vos propres besoins, voire en rcuprer des
fonctionnalits compltes si vous le souhaitez.

A
v
a
n
t
-
p
r
o
p
o
s
Groupe Eyrolles, 2008
VII
Symfony fournit en standard des outils permettant au dveloppeur de tenir
compte de ces contraintes plus facilement et en tant parcimonieux en quan-
tit de code. Une partie de cet ouvrage est consacre ces fonctionnalits car
encore une fois, la validation des donnes, la gestion des erreurs, la scurit
et les tests automatiss sont ancrs au cur mme du framework ce qui lui
permet dtre employ y compris sur des projets de grande envergure.
Dans la philosophie de Symfony, les bonnes pratiques de dveloppement
ont donc part gale avec les nombreuses fonctionnalits du framework.
Elles sont dautant plus importantes que Symfony est utilis pour le
dveloppement dapplications critiques en entreprise.
Organisation de louvrage
Cet ouvrage est compos de vingt-et-un chapitres qui expliquent pas
pas la construction dune application web professionnelle Open Source
avec le framework Symfony. Lobjectif de cette srie de chapitres est de
dtailler une une les fonctionnalits qui font le succs de Symfony,
mais aussi et surtout de montrer ce qui fait de Symfony un outil profes-
sionnel, efficace et agrable utiliser.
Le chapitre 1 ouvre le bal avec linstallation et linitialisation du projet
Jobeet. Ces premires pages sont jalonnes en cinq parties majeures : le
tlchargement et linstallation des librairies de Symfony, la gnration
de la structure de base du projet ainsi que celle de la premire applica-
tion, la configuration du serveur web et enfin linstallation dun dpt
Subversion pour le contrle du suivi du code source.
Le chapitre 2 dresse le cahier des charges fonctionnelles de lapplication
dveloppe au fil des chapitres. Les besoins fonctionnels majeurs de
Jobeet y seront dcrits un un laide de cas dutilisation illustrs.
Le chapitre 3 entame vritablement les hostilits en sintressant la
conception du modle de la base de donnes, et la construction auto-
matique de cette dernire partir de lORM Doctrine. Lintgralit du
chapitre sera ponctue par de nombreuses astuces techniques et bonnes
pratiques de dveloppement web. Ce chapitre sachvera enfin avec la
gnration du tout premier module fonctionnel de lapplication laide
des tches automatiques de Symfony.
Le chapitre 4 aborde lun des points cls du framework Symfony :
limplmentation du motif de conception Modle Vue Contrleur. Ces
quelques pages expliqueront tous les avantages quapporte cette mtho-
dologie prouve en termes dorganisation du code par rapport une
autre, et sera loccasion de dcouvrir et de mettre en uvre les couches
de la Vue et du Contrleur.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
VIII
Le chapitre 5 se consacre quant lui un autre sujet majeur de
Symfony : le routage. Cet aspect du framework concerne la gnration
des URLs propres et la manire dont elles sont traites en interne par
Symfony. Ce chapitre sera donc loccasion de prsenter les diffrentes
types de routes quil est possible de crer et de dcouvrir comment cer-
taines dentre elles sont capables dinteragir directement avec la base de
donnes pour retrouver des objets qui leur sont lis.
Le chapitre 6 est ddi la manipulation de la couche du Modle avec
Symfony. Ce sera donc loccasion de dcouvrir en dtail comment le fra-
mework Symfony et lORM Doctrine permettent au dveloppeur de
manipuler une base de donnes en toute simplicit laide dobjets plutt
que de requtes SQL brutes. Ce chapitre met galement laccent sur une
autre bonne pratique ancre dans la philosophie du framework Symfony :
le remaniement du code. Le but de cette partie du chapitre est de sensibi-
liser le lecteur lintrt dune constante remise en question de ses dve-
loppements lorsquil a la possibilit de lamliorer et de le simplifier.
Le chapitre 7 est une compilation de tous les sujets abords prcdem-
ment puisquil y est question du modle MVC, du routage et de la mani-
pulation de la base de donnes par lintermdiaire des objets. Toutefois,
les pages de ce chapitre introduisent deux nouveaux concepts : la simpli-
fication du code de la Vue ainsi que la pagination des listes de rsultats
issus dune base de donnes. De la mme manire quau sixime cha-
pitre, un remaniement rgulier du code sera opr afin de comprendre
tous les bnfices de cette bonne pratique de dveloppement.
Le chapitre 8 prsente son tour un sujet encore mconnu des dve-
loppeurs professionnels mais particulirement important pour garantir la
qualit des dveloppements : les tests unitaires. Ces quelques pages pr-
sentent tous les avantages de lajout de tests automatiques pour une
application web, et expliquent de quelle manire ces derniers sont parfai-
tement intgrs au sein du framework Symfony via la librairie Open
Source Lime.
Le chapitre 9 fait immdiatement suite au prcdent en se consacrant
un autre type de tests automatiss : les tests fonctionnels. Lobjectif de ce
chapitre est de prsenter ce que sont vritablement les tests fonctionnels
et ce quils apportent comme garanties au cours du dveloppement de
lapplication Jobeet. Symfony est en effet dot dun sous-framework de
tests fonctionnels puissant et simple prendre en main, qui permet au
dveloppeur dexcuter la simulation de lexprience utilisateur dans son
navigateur, puis danalyser toutes les couches de lapplication qui sont
impliques lors de ces scnarios.

A
v
a
n
t
-
p
r
o
p
o
s
Groupe Eyrolles, 2008
IX
Pour ne pas interrompre le lecteur dans sa lance et sa soif dapprentissage,
le chapitre 10 aborde limportante notion de gestion des formulaires. Les
formulaires constituent la principale partie dynamique dune application
web puisquelle permet lutilisateur final dinteragir avec le systme. Bien
que les formulaires soient faciles mettre en place, leur gestion nen
demeure pas moins trs complexe puisquelle implique des notions de vali-
dation de la saisie des utilisateurs, et donc de scurit. Heureusement,
Symfony intgre un sous-framework destin aux formulaires capable de
simplifier et dautomatiser leur gestion en toute scurit.
Le chapitre 11 agrge les connaissances acquises aux chapitres 9 et 10 en
expliquant de quelle manire il est possible de tester fonctionnellement
des formulaires avec Symfony. Par la mme occasion, ce sera le moment
idal pour crire une premire tche automatique de maintenance, ex-
cutable en ligne de commande ou dans une tche planifie du serveur.
Le chapitre 12 est lun des plus importants de cet ouvrage puisquil fait
le tour complet dune des fonctionnalits les plus apprcies des dve-
loppeurs Symfony : le gnrateur dinterface dadministration. En quel-
ques minutes seulement, cet outil permettra de btir un espace complet
et scuris de gestion des catgories et des offres demploi de Jobeet.
Lutilisateur est lacteur principal dans une application puisque cest lui
qui interagit avec le serveur et qui rcupre ce que ce dernier lui renvoie
en retour. Par consquent, le chapitre 13 se ddie entirement lui et
montre, entre autres, comment sauvegarder des informations persis-
tantes dans la session de lutilisateur, ou encore comment lui restreindre
laccs certaines pages sil nest pas authentifi ou sil ne dispose pas des
droits daccs ncessaires et suffisants. Dautre part, une srie de rema-
niements du code sera ralise pour simplifier davantage le code et le
rendre testable.
Le chapitre 14 sintresse une puissante fonctionnalit du sous-fra-
mework de routage : le support des formats de sortie et larchitecture
RESTful. cette occasion, un module complet de gnration de flux de
syndication RSS/ATOM est dvelopp en guise dexemple afin de mon-
trer avec quelle simplicit Symfony est capable de grer nativement dif-
frents formats de sortie standards.
Le chapitre 15 approfondit les connaissances sur le framework de rou-
tage et les formats de sortie en dveloppant une API de services web
destins aux webmasters, qui leur permet dinterroger Jobeet afin den
rcuprer des rsultats dans un format de sortie XML, JSON ou YAML.
Lobjectif est avant tout de montrer avec quelle aisance Symfony facilite
la cration de services web innovants grce son architecture RESTful.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
X
Toute application dynamique qui se respecte comprend spontanment
un moteur de recherche, et cest exactement lobjectif du chapitre 16. En
seulement quelques minutes, lapplication Jobeet bnficiera dun
moteur de recherche fonctionnel et test, reposant sur le composant
Zend_Search_Lucene du framework Open Source de la socit Zend.
Cest lun des nombreux avantages de Symfony que de pouvoir accueillir
simplement des composants tiers comme ceux du framework Zend.
Le chapitre 17 amliore lexprience utilisateur du moteur de recherche
cr au chapitre prcdent, en intgrant des composants JavaScript et
Ajax non intrusifs, dvelopps au moyen de lexcellente librairie jQuery.
Grce ces codes JavaScript, lutilisateur final de Jobeet bnficiera dun
moteur de recherche dynamique qui filtre et rafrachit la liste de rsultats
en temps rel chaque fois quil saisira de nouveaux caractres dans le
champ de recherche.
Le chapitre 18 aborde un nouveau point commun aux applications web
professionnelles : linternationalisation et la localisation. Grce Symfony,
lapplication Jobeet se dotera dune interface multilingue dont les contenus
traduits seront grs la fois par Doctrine pour les informations dynami-
ques des catgories, et par le biais de catalogues XLIFF standards.
Le chapitre 19 se consacre la notion de plug-ins dans Symfony. Les
plug-ins sont des composants rutilisables travers les diffrents projets, et
qui constituent galement un moyen dorganisation du code diffrent de la
structure par dfaut propose par Symfony. Par consquent, les pages de ce
chapitre expliquent pas pas tout le processus de transformation de
lapplication Jobeet en plug-in compltement indpendant et rutilisable.
Le chapitre 20 de cet ouvrage se consacre au puissant sous-framework
de mise en cache des pages HTML afin de rendre lapplication encore
plus performante lorsquelle sera dploye en production au dernier cha-
pitre. Ce chapitre est aussi loccasion de dcouvrir de quelle manire de
nouveaux environnements dexcution peuvent tre ajouts au projet,
puis soumis des tests automatiss.
Enfin, le chapitre 21 clture cette tude de cas par la prparation de
lapplication la dernire tape dcisive dun projet web : le dploiement
en production. Les pages de ce chapitre introduisent tous les concepts de
configuration du serveur web de production ainsi que les outils dauto-
matisation des dploiements tels que rsync.
Pour conclure, trois parties dannexes sont disponibles la fin de cet
ouvrage pour en savoir plus sur la syntaxe du format YAML et sur les
directives de paramtrage de deux fichiers de configuration de Symfony
prsents dans chaque application dveloppe.

A
v
a
n
t
-
p
r
o
p
o
s
Groupe Eyrolles, 2008
XI
Remerciements
crire un livre est une activit aussi excitante qupuisante. Pour un
ouvrage technique, cest dautant plus intense quon cherche, heure aprs
heure, comprendre comment faire passer son message, comment expli-
quer les diffrents concepts, et comment fournir des exemples la fois
simples, pertinents et rutilisables.
crire un livre est une tche tout simplement impossible raliser sans
laide de certaines personnes qui vous entourent et vous soutiennent tout
au long de ce processus.
Le plus grand soutien que lon peut obtenir vient bien sr de sa propre
famille, et je sais que jai lune des familles les plus comprhensives et
encourageantes qui soient. En tant quentrepreneur, je passe dj la plu-
part de mon temps au bureau, et en tant que principal dveloppeur de
Symfony, je passe une grande partie de mon temps libre concevoir la
prochaine version du framework. cela sajoute ma dcision dcrire un
nouveau livre. Mais sans les encouragements constants de ma femme
Hlne et de mes deux merveilleux fils, Thomas et Lucas, ce livre
naurait jamais t crit en si peu de temps et naurait jamais pu voir le
jour si rapidement.
Cet ouvrage naurait pu tre ralis sans le soutien dautres personnes
que je tiens particulirement remercier. En tant que prsident-direc-
teur gnral de Sensio, jai de nombreuses responsabilits, et grce
lappui de toute lquipe de Sensio, jai pu mener terme ce projet. Mes
principaux remerciements vont tout droit Grgory Pascal, mon parte-
naire depuis dix ans, qui tait au dbut particulirement sceptique quant
lide dentreprendre avec le business model de lOpen Source ; il
men remercie normment aujourdhui.
Je souhaite aussi remercier Laurent Vaquette, mon aide de camp, qui na
cess de me simplifier la vie chaque jour, et daccepter de maccompagner
de temps en temps pour manger un dner kebab.
Je remercie galement Jonathan Wage, le dveloppeur principal du
projet Doctrine, qui a pris part lcriture de cet ouvrage. Grce ses
nombreux efforts, la communaut Symfony bnficie aujourdhui de
lORM Doctrine en natif dans Symfony ainsi que dune vritable source
de documentation par lintermdiaire de cet ouvrage.
Enfin, Hugo Hamon, qui a t le principal artisan de cette transforma-
tion de la version originale anglaise, et qui il me semble juste de laisser
une place de co-auteur mes cts, sur ce premier ouvrage en franais.
Fabien Potencier
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
XII
Je tiens avant tout remercier ma famille, mes amis et mes proches qui
mont soutenu et encourag de prs comme de loin dans cette aventure
la fois passionnante, excitante et terriblement fatigante. Jen profite
dailleurs pour ddicacer cet ouvrage mes deux frres Hadrien et Lo.
Jadresse galement mes remerciements et ma reconnaissance toute
lquipe de Sensio, et particulirement Grgory Pascal et Fabien Poten-
cier qui ont su me faire confiance ds mon arrive dans leur entreprise, et
me faire dcouvrir le plaisir de travailler sur des projets web passionnants.
Hugo Hamon
Nous noublions pas bien sr dadresser nos remerciements aux quipes
des ditions Eyrolles qui nous ont permis de mener ce livre son terme,
et tout particulirement Muriel Shan Sei Fan pour avoir pilot ce
projet dans les meilleures conditions et dans la bonne humeur. Nous
remercions galement Romain Pouclet qui na cess de produire un tra-
vail remarquable de relecture technique et dindexation du contenu.
Et enfin, nous vous remercions, vous lecteurs, davoir achet cet ouvrage.
Nous esprons sincrement que vous apprcierez les lignes que vous vous
apprtez lire, et bien sr que vous trouverez votre place parmi
lincroyable communaut des dveloppeurs Symfony.
Fabien Potencier et Hugo Hamon
Table des matires
Groupe Eyrolles, 2007
XIII
AVANT-PROPOS............................................................ V
Dcouvrir ltude de cas dveloppe V
En quoi cet ouvrage est-il diffrent ? VI
Organisation de louvrage VII
Remerciements XI
1. DMARRAGE DU PROJET ................................................ 1
Installer et configurer les bases du projet 2
Les prrequis techniques pour dmarrer 2
Installer les librairies du framework Symfony 2
Installation du projet 5
Gnrer la structure de base du projet 5
Gnrer la structure de base de la premire application
frontend 6
Configuration du chemin vers les librairies de Symfony 7
Dcouvrir les environnements muls par Symfony 7
Quels sont les principaux environnements en
dveloppement web ? 8
Spcificits de lenvironnement de dveloppement 8
Spcificits de lenvironnement de production 9
Configurer le serveur web 10
Mthode 1 : configuration dangereuse ne pas reproduire 10
Mthode 2 : configuration sre et recommande 11
Cration dun nouveau serveur virtuel pour Jobeet 11
Tester la nouvelle configuration dApache 12
Contrler le code source avec Subversion 14
Quels sont les avantages dun gestionnaire de versions ? 14
Installer et configurer le dpt Subversion 14
En rsum 16
2. LTUDE DE CAS ......................................................... 19
la dcouverte du projet 20
Dcouvrir les spcifications fonctionnelles de Jobeet 22
Les diffrents acteurs et applications impliqus 22
Utilisation de lapplication grand public : le frontend 22
Scnario F1 : voir les dernires offres en page daccueil 22
Scnario F2 : voir les offres dune catgorie 23
Scnario F3 : affiner la liste des offres avec des mots-cls 24
Scnario F4 : obtenir le dtail dune offre 24
Scnario F5 : poster une nouvelle annonce 25
Scnario F6 : sinscrire en tant quaffili pour utiliser lAPI 27
Scnario F7 : laffili rcupre la liste des dernires offres
actives 27
Utilisation de linterface dadministration : le backend 27
Scnario B1 : grer les catgories 27
Scnario B2 : grer les offres demploi 28
Scnario B3 : grer les comptes administrateur 28
Scnario B4 : configurer le site Internet 28
En rsum 29
3. CONCEVOIR LE MODLE DE DONNES............................. 31
Installer la base de donnes 32
Crer la base de donnes MySQL 32
Configurer la base de donnes pour le projet Symfony 32
Prsentation de la couche dORM Doctrine 33
Quest-ce quune couche dabstraction de base de donnes ? 34
Quest-ce quun ORM? 34
Activer lORM Doctrine pour Symfony 35
Concevoir le modle de donnes 36
Dcouvrir le diagramme UML entit-relation 36
Mise en place du schma de dfinition de la base 37
De limportance du schma de dfinition
de la base de donnes 37
crire le schma de dfinition de la base de donnes 37
Dclaration des attributs des colonnes dune table
en format YAML 39
Gnrer la base de donnes et les classes du modle
avec Doctrine 40
Construire la base de donnes automatiquement 40
Dcouvrir les classes du modle de donnes 41
Gnrer la base de donnes et le modle en une seule passe 42
Prparer les donnes initiales de Jobeet 43
Dcouvrir les diffrents types de donnes
dun projet Symfony 43
Dfinir des jeux de donnes initiales pour Jobeet 44
Charger les jeux de donnes de tests en base de donnes 46
Rgnrer la base de donnes et le modle en une seule passe 46
Profiter de toute la puissance de Symfony dans le navigateur 47
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2007
XIV
Gnrer le premier module fonctionnel job 47
Composition de base dun module gnr par Symfony 47
Dcouvrir les actions du module job 48
Comprendre limportance de la mthode magique
__toString() 49
Ajouter et diter les offres demploi 50
En rsum 50
4. LE CONTRLEUR ET LA VUE .......................................... 53
Larchitecture MVC et son implmentation dans Symfony 54
Habiller le contenu de chaque page avec un mme gabarit 55
Dcorer une page avec un en-tte et un pied de page 55
Dcorer le contenu dune page avec un dcorateur 56
Intgrer la charte graphique de Jobeet 58
Rcuprer les images et les feuilles de style 58
Configurer la vue partir dun fichier de configuration 59
Configurer la vue laide des helpers de Symfony 61
Gnrer la page daccueil des offres demploi 62
crire le contrleur de la page : laction index 62
Crer la vue associe laction : le template 63
Personnaliser les informations affiches pour chaque offre 64
Gnrer la page de dtail dune offre 66
Crer le template du dtail de loffre 66
Mettre jour laction show 67
Utiliser les emplacements pour modifier dynamiquement le titre
des pages 68
Dfinition dun emplacement pour le titre 68
Fixer la valeur dun slot dans un template 68
Fixer la valeur dun slot complexe dans un template 69
Dclarer une valeur par dfaut pour le slot 69
Rediriger vers une page derreur 404 si loffre nexiste pas 70
Comprendre linteraction client/serveur 71
Rcuprer le dtail de la requte envoye au serveur 71
Rcuprer le dtail de la rponse envoye au client 72
En rsum 73
5. LE ROUTAGE............................................................... 75
la dcouverte du framework de routage de Symfony 76
Rappels sur la notion dURL 76
Quest-ce quune URL ? 76
Introduction gnrale au framework interne de routage 77
Configuration du routage : le fichier routing.yml 77
Dcouverte de la configuration par dfaut du routage 77
Comprendre le fonctionnement des URL par dfaut
de Symfony 79
Personnaliser les routes de lapplication 80
Configurer la route de la page daccueil 80
Configurer la route daccs au dtail dune offre 80
Forcer la validation des paramtres des URLs internes
de lapplication 82
Limiter une requte certaines mthodes HTTP 82
Optimiser la cration de routes grce la classe de route
dobjets Doctrine 83
Transformer la route dune offre en route Doctrine 83
Amliorer le format des URL des offres demploi 84
Retrouver lobjet grce sa route depuis une action 86
Faire appel au routage depuis les actions et les templates 87
Le routage dans les templates 87
Le routage dans les actions 88
Dcouvrir la classe de collection de routes
sfDoctrineRouteCollection 88
Dclarer une nouvelle collection de routes Doctrine 88
muler les mthodes PUT et DELETE 90
Outils et bonnes pratiques lis au routage 91
Faciliter le dbogage en listant les routes de lapplication 91
Supprimer les routes par dfaut 93
En rsum 93
6. OPTIMISATION DU MODLE ET REFACTORING .................. 95
Prsentation de lobjet Doctrine_Query 96
Dboguer le code SQL gnr par Doctrine 97
Dcouvrir les fichiers de log 97
Avoir recours la barre de dbogage 97
Intervenir sur les proprits dun objet avant sa srialisation
en base de donnes 98
Redfinir la mthode save() dun objet Doctrine 98
Rcuprer la liste des offres demploi actives 99
Mettre jour les donnes de test pour sassurer de la validit
des offres affiches 99
Grer les paramtres personnaliss dune application
dans Symfony 100
Dcouvrir le fichier de configuration app.yml 100
Rcuprer une valeur de configuration depuis le modle 101
Remanier le code en continu pour respecter la logique MVC 101
Exemple de dplacement du contrleur vers le modle 102
Avantages du remaniement de code 102
Ordonner les offres suivant leur date dexpiration 103
Classer les offres demploi selon leur catgorie 103
Limiter le nombre de rsultats affichs 105
Modifier les donnes de test dynamiquement par lajout
de code PHP 107
Empcher la consultation dune offre expire 108
Crer une page ddie la catgorie 110
En rsum 110
T
a
b
l
e

d
e
s

m
a
t
i

r
e
s
Groupe Eyrolles, 2007
XV
7. CONCEVOIR ET PAGINER LA LISTE DOFFRES
DUNE CATGORIE ..................................................... 113
Mise en place dune route ddie la page de la catgorie 114
Dclarer la route category dans le fichier routing.yml 114
Implmenter laccesseur getSlug() dans la classe JobeetJob 114
Personnaliser les conditions daffichage du lien
de la page de catgorie 115
Intgrer un lien pour chaque catgorie ayant plus de dix offres
valides 115
Implmenter la mthode countActiveJobs() de la classe
JobeetCategory 116
Implmenter la mthode countActiveJobs() de la classe
JobeetCategoryTable 116
Mise en place du module ddi aux catgories 118
Gnrer automatiquement le squelette du module 118
Ajouter un champ supplmentaire pour accueillir le slug
de la catgorie 119
Cration de la vue de dtail de la catgorie 119
Mise en place de laction executeShow() 119
Intgration du template showSuccess.php associ 120
Isoler le HTML redondant dans les templates partiels 121
Dcouvrir le principe de templates partiels 121
Cration dun template partiel _list.php pour les modules
job et category 122
Faire appel au partiel dans un template 122
Utiliser le partiel _list.php dans les templates
indexSuccess.php et showSuccess.php 123
Paginer une liste dobjets Doctrine 123
Que sont les listes pagines et quoi servent-elles ? 123
Prparer la pagination laide de sfDoctrinePager 124
Initialiser la classe de modle et le nombre maximum
dobjets par page 124
Spcifier lobjet Doctrine_Query de slection des rsultats 125
Configurer le numro de la page courante de rsultats 125
Initialiser le composant de pagination 125
Simplifier les mthodes de slection des rsultats 126
Implmenter la mthode getActiveJobsQuery de lobjet
JobeetCategory 126
Remanier les mthodes existantes de JobeetCategory 126
Intgrer les lments de pagination dans le template
showSuccess.php 127
Passer la collection dobjets Doctrine au template partiel 127
Afficher les liens de navigation entre les pages 128
Afficher le nombre total doffres publies et de pages 129
Description des mthodes de lobjet sfDoctrinePager
utilises dans le template 129
Code final du template showSuccess.php 130
En rsum 131
8. LES TESTS UNITAIRES ................................................. 133
Prsentation des types de tests dans Symfony 134
De la ncessit de passer par des tests unitaires 134
Prsentation du framework de test lime 135
Initialisation dun fichier de tests unitaires 135
Dcouverte des outils de tests de lime 135
Excuter une suite de tests unitaires 136
Tester unitairement la mthode slugify() 137
Dterminer les tests crire 137
crire les premiers tests unitaires de la mthode 138
Commenter explicitement les tests unitaires 138
Implmenter de nouveaux tests unitaires au fil du
dveloppement 140
Ajouter des tests pour les nouvelles fonctionnalits 140
Ajouter des tests suite la dcouverte dun bug 141
Implmenter une meilleure mthode slugify 142
Implmentation des tests unitaires dans le framework ORM
Doctrine 144
Configuration de la base de donnes 144
Mise en place dun jeu de donnes de test 145
Vrifier lintgrit du modle par des tests unitaires 145
Initialiser les scripts de tests unitaires de modles
Doctrine 145
Tester la mthode getCompanySlug() de lobjet
JobeetJob 146
Tester la mthode save() de lobjet JobeetJob 146
Implmentation des tests unitaires dans dautres classes
Doctrine 147
Lancer lensemble des tests unitaires du projet 148
En rsum 148
9. LES TESTS FONCTIONNELS........................................... 151
Dcouvrir limplmentation des tests fonctionnels 152
En quoi consistent les tests fonctionnels ? 152
Implmentation des tests fonctionnels 153
Manipuler les composants de tests fonctionnels 153
Simuler le navigateur grce lobjet sfBrowser 153
Tester la navigation en simulant le comportement
dun vritable navigateur 153
Modifier le comportement du simulateur de navigateur 154
Prparer et excuter des tests fonctionnels 155
Comprendre la structure des fichiers de tests 155
Dcouvrir le testeur sfTesterRequest 157
Dcouvrir le testeur sfTesterResponse 157
Excuter les scnarios de tests fonctionnels 158
Charger des jeux de donnes de tests 158
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2007
XVI
crire des tests fonctionnels pour le module doffres 159
Les offres demploi expires ne sont pas affiches 160
Seulement N offres sont listes par catgorie 160
Un lien vers la page dune catgorie est prsent lorsquil y a
trop doffres 161
Les offres demploi sont tries par date 162
Chacune des offres de la page daccueil est cliquable 163
Autres exemples de scnarios de tests pour les pages des modules
job et category 164
Dboguer les tests fonctionnels 167
Excuter successivement des tests fonctionnels 167
Excuter les tests unitaires et fonctionnels 168
En rsum 168
10. ACCLRER LA GESTION DES FORMULAIRES................ 171
la dcouverte des formulaires avec Symfony 172
Les formulaires de base 172
Les formulaires gnrs par les tches Doctrine 174
Personnaliser le formulaire dajout ou de modification
dune offre 174
Supprimer les champs inutiles du formulaire gnr 175
Redfinir plus prcisment la configuration dun champ 175
Utiliser le validateur sfValidatorEmail 176
Remplacer le champ permettant le choix du type doffre
par une liste droulante 176
Personnaliser le widget permettant lenvoi du logo associ
une offre 178
Modifier plusieurs labels en une seule passe 180
Ajouter une aide contextuelle sur un champ 180
Prsentation de la classe finale de configuration du
formulaire dajout dune offre 180
Manipuler les formulaires directement dans les templates 182
Gnrer le rendu dun formulaire 182
Personnaliser le rendu des formulaires 183
Dcouvrir les mthodes de lobjet sfForm 183
Comprendre et implmenter les mthodes de lobjet
sfFormField 184
Manipuler les formulaires dans les actions 184
Dcouvrir les mthodes autognres du module job utilisant
les formulaires 185
Traiter les formulaires dans les actions 186
Simplifier le traitement du formulaire dans le module job 186
Comprendre le cycle de vie du formulaire 187
Dfinir les valeurs par dfaut dun formulaire gnr
par Doctrine 187
Protger le formulaire des offres par limplmentation
dun jeton 188
Gnrer le jeton automatiquement la cration 188
Redfinir la route ddition de loffre grce au jeton 189
Construire la page de prvisualisation 190
Activer et publier une offre 192
Prparer la route vers laction de publication 192
Implmenter la mthode executePublish() 193
Implmenter la mthode publish() de lobjet JobeetJob 193
Empcher la publication et laccs aux offres non actives 194
En rsum 195
11. TESTER LES FORMULAIRES ........................................ 197
Utiliser le framework de formulaires de manire autonome 198
crire des tests fonctionnels pour les classes de formulaire 199
Tester lenvoi du formulaire de cration doffre 199
Renommer le nom des champs du formulaire 200
Soumettre le formulaire laide de la mthode click() 200
Dcouvrir le testeur sfTesterForm 201
Tester si le formulaire est erron 201
Les mthodes de lobjet sfTesterForm 201
Dboguer un formulaire 202
Tester les redirections HTTP 202
Tester les objets gnrs par Doctrine 202
Activer le testeur sfTesterDoctrine 203
Tester lexistence dun objet Doctrine dans la base
de donnes 203
Tester les erreurs des champs du formulaire 203
La mthode isError() pour le contrle des champs 204
Tester la barre dadministration dune offre 205
Forcer la mthode HTTP dun lien 206
Forcer lutilisation de la mthode HTTP PUT 206
Forcer lutilisation de la mthode HTTP DELETE 206
crire des tests fonctionnels afin de dcouvrir des bogues 207
Simuler lautopublication dune offre 207
Contrler la redirection vers une page derreur 404 208
Empcher laccs au formulaire ddition lorsque loffre
est publie 209
Tester la prolongation dune offre 210
Comprendre le problme des offres expires ractiver 210
Une route ddie pour prolonger la dure dune offre 210
Implmenter la mthode executeExtend() aux actions
du module job 211
Implmenter la mthode extend() dans JobeetJob 212
Tester la prolongation de la dure de vie dune offre 212
Scuriser les formulaires 214
Srialisation dun formulaire Doctrine 214
Scurit native du framework de formulaire 214
Se protger contre les attaques CSRF et XSS 216
T
a
b
l
e

d
e
s

m
a
t
i

r
e
s
Groupe Eyrolles, 2007
XVII
Les tches automatiques de maintenance 216
Crer la nouvelle tche de maintenance jobeet:cleanup 217
Implmenter la mthode cleanup() de la classe
JobeetJobTable 218
En rsum 219
12. LE GNRATEUR DINTERFACE DADMINISTRATION....... 221
Cration de lapplication backend 222
Gnrer le squelette de lapplication 222
Recharger les jeux de donnes initiales 222
Gnrer les modules dadministration 223
Gnrer les modules category et job 223
Personnaliser linterface utilisateur
et lergonomie des modules du backoffice 224
Dcouvrir les fonctions des modules dadministration 224
Amliorer le layout du backoffice 225
Comprendre le cache de Symfony 227
Introduction au fichier de configuration generator.yml 228
Configurer les modules autognrs par Symfony 229
Organisation du fichier de configuration generator.yml 229
Configurer les titres des pages des modules auto gnrs 229
Changer le titre des pages du module category 229
Configurer les titres des pages du module job 230
Modifier le nom des champs dune offre demploi 231
Redfinir globalement les proprits des champs
du module 231
Surcharger localement les proprits des champs
du module 231
Comprendre le principe de configuration en cascade 232
Configurer la liste des objets 232
Dfinir la liste des colonnes afficher 232
Colonnes afficher dans la liste des catgories 232
Liste des colonnes afficher dans la liste des offres 233
Configurer le layout du tableau de la vue liste 233
Dclarer des colonnes virtuelles 234
Dfinir le tri par dfaut de la liste dobjets 235
Rduire le nombre de rsultats par page 235
Configurer les actions de lot dobjets 236
Dsactiver les actions par lot dans le module category 236
Ajouter de nouvelles actions par lot dans le module job 237
Configurer les actions unitaires pour chaque objet 239
Supprimer les actions dobjets des catgories 239
Ajouter dautres actions pour chaque offre demploi 240
Configurer les actions globales de la vue liste 240
Optimiser les requtes SQL de rcupration
des enregistrements 243
Configurer les formulaires des vues de saisie de donnes 245
Configurer la liste des champs afficher dans les formulaires
des offres 245
Ajouter des champs virtuels au formulaire 247
Redfinir la classe de configuration du formulaire 247
Implmenter une nouvelle classe de formulaire par dfaut 247
Implmenter un meilleur mcanisme de gestion
des photos des offres 249
Configurer les filtres de recherche de la vue liste 251
Supprimer les filtres du module de category 251
Configurer la liste des filtres du module job 251
Personnaliser les actions dun module autognr 252
Personnaliser les templates dun module autognr 253
La configuration finale du module 255
Configuration finale du module job 255
Configuration finale du module category 256
En rsum 257
13. AUTHENTIFICATION ET DROITS AVEC LOBJET SFUSER ... 259
Dcouvrir les fonctionnalits de base de lobjet sfUser 260
Comprendre les messages flash de feedback 261
quoi servent ces messages dans Symfony ? 261
crire des messages flash depuis une action 261
Lire des messages flash dans un template 262
Stocker des informations dans la session courante
de lutilisateur 262
Lire et crire dans la session de lutilisateur courant 263
Implmenter lhistorique de navigation de lutilisateur 263
Refactoriser le code de lhistorique de navigation
dans le modle 264
Implmenter lhistorique de navigation dans la classe
myUser 264
Simplifier laction executeShow() de la couche contrleur 265
Afficher lhistorique des offres demploi consultes 265
Implmenter un moyen de rinitialiser lhistorique
des offres consultes 267
Comprendre les mcanismes de scurisation des applications 268
Activer lauthentification de lutilisateur sur une application 268
Dcouvrir le fichier de configuration security.yml 268
Analyse des logs gnrs par Symfony 269
Personnaliser la page de login par dfaut 269
Authentifier et tester le statut de lutilisateur 270
Restreindre les actions dune application lutilisateur 270
Activer le contrle des droits daccs sur lapplication 271
tablir des rgles de droits daccs complexes 271
Grer les droits daccs via lobjet sfBasicSecurityUser 272
Mise en place de la scurit de lapplication backend de Jobeet 273
Installation du plug-in sfDoctrineGuardPlugin 273
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2007
XVIII
Mise en place des scurits de lapplication backend 274
Gnrer les classes de modle et les tables SQL 274
Implmenter de nouvelles mthodes lobjet User via la
classe sfGuardSecurityUser 274
Activer le module sfGuardAuth et changer laction de login
par dfaut 275
Crer un utilisateur administrateur 276
Cacher le menu de navigation lorsque lutilisateur
nest pas authentifi 276
Ajouter un nouveau module de gestion des utilisateurs 277
Implmenter de nouveaux tests fonctionnels pour lapplication
frontend 278
En rsum 279
14. LES FLUX DE SYNDICATION ATOM........................... 281
Dcouvrir le support natif des formats de sortie 282
Dfinir le format de sortie dune page 282
Grer les formats de sortie au niveau du routage 283
Prsentation gnrale du format ATOM 283
Les informations globales du flux 284
Les entres du flux 284
Le flux ATOM minimal valide 284
Gnrer des flux de syndication ATOM 285
Flux ATOM des dernires offres demploi 285
Dclarer un nouveau format de sortie 285
Rappel des conventions de nommage des templates 286
Ajouter le lien vers le flux des offres dans le layout 287
Gnrer les informations globales du flux 288
Gnrer les entres du flux ATOM 289
Flux ATOM des dernires offres dune catgorie 290
Mise jour de la route ddie de la catgorie 291
Mise jour des liens des flux de la catgorie 291
Factoriser le code de gnration des entres du flux 292
Simplifier le template indexSuccess.atom.php 293
Gnrer le template du flux des offres dune catgorie 293
En rsum 295
15. CONSTRUIRE DES SERVICES WEB ............................... 297
Concevoir le service web des offres demploi 298
Prparer des jeux de donnes initiales des affilis 298
Construire le service web des offres demploi 300
Dclaration de la route ddie du service web 300
Implmenter la mthode getForToken() de lobjet
JobeetJobTable 301
Implmenter la mthode getActiveJobs() de lobjet
JobeetAffiliate 301
Dvelopper le contrleur du service web 302
Implmenter laction executeList() du module api 302
Implmenter la mthode asArray() de JobeetJob 303
Construction des templates XML, JSON et YAML 304
Le format XML 304
Le format JSON 305
Le format YAML 306
crire des tests fonctionnels pour valider le service web 309
Formulaire de cration dun compte daffiliation 310
Dclarer la route ddie du formulaire dinscription 310
Gnrer un module damorage 311
Construction des templates 311
Implmenter les actions du module affiliate 312
Tester fonctionnellement le formulaire 314
Dvelopper linterface dadministration des affilis 315
Gnrer le module dadministration affiliate 315
Paramtrer le module affiliate 316
Implmenter les nouvelles fonctionnalits dadministration 317
Envoyer des e-mails avec Zend_Mail 320
Installer et configurer le framework Zend 320
Implmenter lenvoi dun e-mail lactivation
du compte de laffili 321
En rsum 323
16. DPLOYER UN MOTEUR DE RECHERCHE....................... 325
Dcouverte de la librairie Zend_Search_Lucene 326
Rappels historiques au sujet de Symfony 326
Prsentation de Zend Lucene 326
Indexer le contenu de Jobeet 327
Crer et rcuprer le fichier de lindex 327
Mettre jour lindex la srialisation dune offre 328
Scuriser la srialisation dune offre laide
dune transaction Doctrine 330
Effacer lindex lors de la suppression dune offre 331
Manipuler lindex des offres demploi 331
Rgnrer tout lindex des offres demploi 331
Implmenter la recherche dinformations pour Jobeet 331
Tester la mthode getForLuceneQuery() de JobeetJob 334
Nettoyer rgulirement lindex des offres primes 335
En rsum 337
17. DYNAMISER LINTERFACE UTILISATEUR AVEC AJAX ...... 339
Choisir un framework JavaScript 340
Dcouvrir la librairie jQuery 340
Tlcharger et installer jQuery 341
Rcuprer la dernire version stable du projet 341
Charger la librairie jQuery sur chaque page du site 341
Dcouvrir les comportements JavaScript avec jQuery 342
Intercepter la valeur saisie par lutilisateur dans le moteur de
recherche 343
T
a
b
l
e

d
e
s

m
a
t
i

r
e
s
Groupe Eyrolles, 2007
XIX
Excuter un appel Ajax pour interroger le serveur web 344
Cacher dynamiquement le bouton denvoi du formulaire 345
Informer lutilisateur de lexcution de la requte Ajax 345
Faire patienter lutilisateur avec un loader 345
Dplacer le code JavaScript dans un fichier externe 346
Manipuler les requtes Ajax dans les actions 347
Dterminer que laction provient dun appel Ajax 347
Message spcifique pour une recherche sans rsultat 348
Simuler une requte Ajax avec les tests fonctionnels 349
En rsum 349
18. INTERNATIONALISATION ET LOCALISATION ................. 351
Que sont linternationalisation et la localisation ? 352
Lutilisateur au cur de linternationalisation 353
Paramtrer la culture de lutilisateur 353
Dfinir et rcuprer la culture de lutilisateur 353
Modifier la culture par dfaut de Symfony 353
Dterminer les langues favorites de lutilisateur 354
Utiliser la culture dans les URLs 355
Transformer le format des URLs de Jobeet 355
Attribuer dynamiquement la culture de lutilisateur daprs
la configuration de son navigateur 356
Tester la culture avec des tests fonctionnels 359
Mettre jour les tests fonctionnels qui chouent 359
Tester les nouvelles implmentations lies la culture 359
Changer de langue manuellement 360
Installer le plug-in sfFormExtraPlugin 361
Intgration non conforme du formulaire de changement
de langue 361
Intgration du formulaire de changement de langue
avec un composant Symfony 362
Dcouvrir les outils dinternationalisation de Symfony 365
Paramtrer le support des langues, jeux de caractres
et encodages 365
Traduire les contenus statiques des templates 367
Utiliser le helper de traduction __() 367
Extraire les contenus internationaliss vers un catalogue
XLIFF 369
Traduire des contenus dynamiques 370
Le cas des chanes dynamiques simples 371
Traduire des contenus pluriels partir du helper
format_number_choice() 372
Traduire les contenus propres aux formulaires 373
Activer la traduction des objets Doctrine 373
Internationaliser le modle JobeetCategory de la base 374
Mettre jour les donnes initiales de test 374
Surcharger la mthode findOneBySlug() du modle
JobeetCategoryTable 375
Mthodes raccourcies du comportement I18N 376
Mettre jour le modle et la route de la catgorie 376
Implmenter la mthode findOneBySlugAndCulture()
du modle JobeetCategoryTable 377
Mise jour de la route category de lapplication frontend 377
Champs internationaliss dans un formulaire Doctrine 378
Internationaliser le formulaire ddition dune catgorie
dans le backoffice 378
Utiliser la mthode embedI18n() de lobjet
sfFormDoctrine 378
Internationalisation de linterface du gnrateur
dadministration 379
Forcer lutilisation dun autre catalogue de traductions 380
Tester lapplication pour valider le processus de migration
de lI18N 380
Dcouvrir les outils de localisation de Symfony 381
Rgionaliser les formats de donnes dans les templates 381
Les helpers du groupe Date 381
Les helpers du groupe Number 381
Les helpers du groupe I18N 382
Rgionaliser les formats de donnes dans les formulaires 382
En rsum 383
19. LES PLUG-INS ......................................................... 385
Quest-ce quun plug-in dans Symfony ? 386
Les plug-ins Symfony 386
Les plug-ins privs 386
Les plug-ins publics 387
Une autre manire dorganiser le code du projet 387
Dcouvrir la structure de fichiers dun plug-in Symfony 387
Crer le plug-in sfJobeetPlugin 388
Migrer les fichiers du modle vers le plug-in 389
Dplacer le schma de description de la base 389
Dplacer les classes du modle, de formulaires
et de filtres 389
Transformer les classes concrtes en classes abstraites 389
Reconstruire le modle de donnes 390
Supprimer les classes de base des formulaires Doctrine 392
Dplacer la classe Jobeet vers le plug-in 392
Migrer les contrleurs et les vues 393
Dplacer les modules vers le plug-in 393
Renommer les noms des classes dactions et de composants 393
Mettre jour les actions et les templates 394
Mettre jour le fichier de configuration du routage 395
Activer les modules de lapplication frontend 397
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2007
XX
Migrer les tches automatiques de Jobeet 398
Migrer les fichiers dinternationalisation de lapplication 398
Migrer le fichier de configuration du routage 399
Migrer les ressources Web 399
Migrer les fichiers relatifs lutilisateur 399
Configuration du plug-in 399
Dveloppement de la classe JobeetUser 400
Comparaison des structures des projets et des plug-ins 402
Utiliser les plug-ins de Symfony 403
Naviguer dans linterface ddie aux plug-ins 403
Les diffrentes manires dinstaller des plug-ins 404
Contribuer aux plug-ins de Symfony 405
Packager son propre plug-in 405
Construire le fichier README 405
Ajouter le fichier LICENSE 405
crire le fichier package.xml 405
Hberger un plug-in public dans le dpt officiel de Symfony 408
En rsum 409
20. LA GESTION DU CACHE ............................................ 411
Pourquoi optimiser le temps de chargement des pages ? 412
Crer un nouvel environnement pour tester le cache 413
Comprendre la configuration par dfaut du cache 413
Ajouter un nouvel environnement cache au projet 414
Configuration gnrale de lenvironnement cache 414
Crer le contrleur frontal du nouvel environnement 414
Configurer le nouvel environnement 415
Manipuler le cache de lapplication 415
Configuration globale du cache de lapplication 416
Activer le cache ponctuellement page par page 416
Activation du cache de la page daccueil de Jobeet 416
Principe de fonctionnement du cache de Symfony 417
Activer le cache de la page de cration dune nouvelle offre 418
Nettoyer le cache de fichiers 418
Activer le cache uniquement pour le rsultat dune action 419
Exclure la mise en cache du layout 419
Fonctionnement de la mise en cache sans layout 420
Activer le cache des templates partiels et des composants 421
Configuration du cache 421
Principe de fonctionnement de la mise en cache 422
Activer le cache des formulaires 423
Comprendre la problmatique de la mise en cache des
formulaires 423
Dsactiver la cration du jeton unique 424
Retirer le cache automatiquement 425
Configurer la dure de vie du cache de la page daccueil 425
Forcer la rgnration du cache depuis une action 425
Tester le cache partir des tests fonctionnels 427
Activer le cache pour lenvironnement de test 427
Tester la mise en cache du formulaire de cration dune
offre demploi 427
En rsum 428
21. LE DPLOIEMENT EN PRODUCTION ............................. 431
Prparer le serveur de production 432
Vrifier la configuration du serveur web 432
Installer lacclrateur PHP APC 433
Installer les librairies du framework Symfony 433
Embarquer le framework Symfony 433
Garder Symfony jour en temps rel 434
Personnaliser la configuration de Symfony 436
Configurer laccs la base de donnes 436
Gnrer les liens symboliques pour les ressources web 436
Personnaliser les pages derreur par dfaut 436
Remplacer les pages derreur interne par dfaut 436
Personnaliser les pages derreur 404 par dfaut 437
Personnaliser la structure de fichiers par dfaut 437
Modifier le rpertoire par dfaut de la racine web 437
Modifier les rpertoires du cache et des logs 438
la dcouverte des factories 438
Initialisation des objets du noyau grce factories.yml 439
Modification du nom du cookie de session 439
Remplacer le moteur de stockage des sessions
par une base de donnes 440
Dfinir la dure de vie maximale dune session 440
Dfinir les objets denregistrement derreur 441
Dployer le projet sur le serveur de production 442
Que faut-il dployer en production ? 442
Mettre en place des stratgies de dploiement 442
Dploiement laide dune connexion SSH et rsync 442
Configurer rsync pour exclure certains fichiers du
dploiement 443
Nettoyer le cache de configuration du serveur de
production 444
En rsum 445
A. LE FORMAT YAML ................................................... 447
Les donnes scalaires 448
Les chanes de caractres 448
Les nombres 449
Les entiers 449
Les nombres octaux 449
Les nombres hexadcimaux 450
Les nombres dcimaux 450
Les nombres exponentiels 450
T
a
b
l
e

d
e
s

m
a
t
i

r
e
s
Groupe Eyrolles, 2007
XXI
Les nombres infinis 450
Les valeurs nulles : les NULL 450
Les valeurs boolennes 450
Les dates 451
Les collections 451
Les squences dlments 451
Les associations dlments 451
Les associations simples 451
Les associations complexes imbriques 452
Combinaison de squences et dassociations 453
Syntaxe alternative pour les squences et associations 453
Les commentaires 454
Les fichiers YAML dynamiques 454
Exemple complet rcapitulatif 455
B. LE FICHIER DE CONFIGURATION SETTINGS.YML.............. 457
Les paramtres de configuration du fichier settings.yml 458
Configuration de la section .actions 458
Configuration de la section .settings 458
La sous-section .actions 459
Configuration par dfaut 459
error_404 460
login 460
secure 460
module_disabled 460
La sous-section .settings 460
escaping_strategy 460
escaping_method 461
csrf_secret 461
charset 461
enabled-modules 462
default_timezone 462
cache 462
etag 462
i18n 463
default_culture 463
standard_helpers 463
no_script_name 463
logging_enabled 464
web_debug 464
error_reporting 464
compressed 464
use_database 465
check_lock 465
check_symfony_version 465
web_debug_dir 465
strip_comments 466
max_forwards 466
C. LE FICHIER DE CONFIGURATION FACTORIES.YML ............ 467
Introduction la notion de factories 468
Prsentation du fichier factories.yml 468
Configuration du service request 468
Configuration du service response 469
Configuration du service user 469
Configuration du service storage 469
Configuration du service i18n 470
Configuration du service routing 470
Configuration du service logger 470
Le service request 471
Configuration par dfaut 471
path_info_array 471
path_info_key 471
formats 472
relative_root_url 472
Le service response 472
Configuration par dfaut 472
send_http_headers 472
charset 473
http_protocol 473
Le service user 473
Configuration par dfaut 473
timeout 474
use_flash 474
default_culture 474
Le service storage 474
Configuration par dfaut 474
auto_start 475
session_name 475
Paramtres de la fonction session_set_cookie_params() 475
session_cache_limiter 475
Options de stockage des sessions en bases de donnes 476
Le service view_cache_manager 476
Le service view_cache 476
Le service i18n 477
Le service routing 478
Le service logger 480
Le service controller 481
Les services de cache anonymes 482
INDEX...................................................................... 483
Groupe Eyrolles, 2008
chapitre 1
Groupe Eyrolles, 2008
Dmarrage du projet
Un projet web ncessite ds le dmarrage une plate-forme
de dveloppement complte dans la mesure o de nombreuses
technologies interviennent et cohabitent ensemble.
Ce chapitre introduit les notions lmentaires de projet
Symfony, denvironnements web, de configuration de serveur
virtuel mais aussi de gestion du code source au moyen doutils
comme Subversion.
MOTS-CLS :
BSymfony, Apache, Subversion
BVulnrabilits XSS et CSRF
BBonnes pratiques
de dveloppement
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
2
Comme pour tout projet web, il est vident de ne pas se lancer tte
baisse dans le dveloppement de lapplication, cest pourquoi aucune
ligne de code PHP ne sera dvoile avant le troisime chapitre de cet
ouvrage. Nanmoins, ce chapitre rvlera combien il est bnfique et
utile de profiter dun framework comme Symfony seulement en crant
un nouveau projet.
Lobjectif de ce chapitre est de mettre en place lenvironnement de travail
et dafficher dans le navigateur une page gnre par dfaut par Sym-
fony. Par consquent, il sera question de linstallation du framework
Symfony, puis de linitialisation de la premire application mais aussi de
la configuration adquate du serveur web local. Pour finir, une section
dtaillera pas pas comment installer rapidement un dpt Subversion
capable de grer le contrle du suivi du code source du projet.
Installer et configurer les bases du projet
Les prrequis techniques pour dmarrer
Tout dabord, il faut sassurer que lordinateur de travail possde un envi-
ronnement de dveloppement web complet compos dun serveur web
(Apache par exemple), dune base de donnes (MySQL, PostgreSQL,
ou SQLite) et bien videmment de PHP en version 5.2.4 ou suprieure.
Tout au long du livre, la ligne de commande permettra de raliser de trs
nombreuses tches. Elle sera particulirement facile apprhender sur un
environnement de type Unix. Pour les utilisateurs sous environnement
Windows, pas de panique, puisquil sagit juste de taper quelques com-
mandes aprs avoir dmarr lutilitaire cmd (Dmarrer > Excuter > cmd).
Ce livre tant une introduction au framework Symfony, les notions rela-
tives PHP 5 et la programmation oriente objet sont considres
comme acquises.
Installer les librairies du framework Symfony
La premire tape technique de ce projet dmarre avec linstallation des
librairies du framework Symfony. Pour commencer, le dossier dans
lequel figureront tous les fichiers du projet doit tre cr. Les utilisateurs
de Windows et dUnix disposent tous de la mme commande mkdir
pour y parvenir.
ASTUCE Installer une plate-forme
de dveloppement pour Windows
Des outils comme WAMP Server 2
(www.wampserver.com) sous Windows per-
mettent dinstaller en quelques clics un environne-
ment Apache, PHP et MySQL complet utilisant les
dernires versions de PHP. Ils permettent ainsi de
dmarrer immdiatement le dveloppement de
projets PHP sans avoir se proccuper de linstal-
lation des diffrents serveurs.
REMARQUE Bnficier des outils
dUnix sous Windows
Si vous souhaitez reproduire un environnement
Unix sous Windows, et avoir la possibilit dutiliser
des utilitaires comme tar, gzip ou grep, vous
pouvez installer Cygwin (http://cygwin.com).
La documentation officielle est un peu restreinte,
mais vous trouverez un trs bon guide dinstalla-
tion ladresse http://www.soe.ucsc.edu/
~you/notes/cygwin-install.html. Si vous tes
un peu plus aventurier dans lme, vous pouvez
mme essayer Windows Services for Unix
ladresse http://technet.microsoft.com/en-
gb/interopmigration/bb380242.aspx.
1

m
a
r
r
a
g
e

d
u

p
r
o
j
e
t
Groupe Eyrolles, 2008
3
Cration du dossier du projet en environnement Unix
Cration du dossier du projet en environnement Windows
Une fois le rpertoire du projet cr, le rpertoire lib/vendor/ contenant
les librairies de Symfony doit son tour tre construit dans le rpertoire
du projet.
Cration du rpertoire lib/vendor/ du projet
La page dinstallation de Symfony (http://www.symfony-project.org/
installation) sur le site officiel du projet liste et compare les diffrentes
versions disponibles du framework. Ce livre a t crit pour fonctionner
avec la toute dernire version 1.2 de Symfony. lheure o sont crites
ces lignes, la dernire version de Symfony disponible est la 1.2.5.
La section Source Download de cette page propose un lien permettant de
tlcharger une archive des fichiers source de Symfony au format .tgz ou
.zip. Cette archive doit tre tlcharge dans le rpertoire lib/vendor/
qui vient dtre cr, puis dcompresse dans ce mme rpertoire.
Installation des fichiers sources de Symfony dans le rpertoire lib/vendor/
Sous Windows, il est plus facile dutiliser lexplorateur de fichiers pour
dcompresser larchive au format ZIP. Aprs avoir renomm le rper-
toire en symfony, la structure du projet devrait ressembler celle-ci :
c:\development\sfprojects\jobeet\lib\vendor\symfony.
La configuration par dfaut de PHP variant normment dune installa-
tion une autre, il convient de sassurer que la configuration du serveur
correspond aux prrequis minimaux de Symfony. Pour ce faire, le script
de vrification fourni avec Symfony doit tre excut depuis la ligne de
commande.
$ mkdir -p /home/sfprojects/jobeet
$ cd /home/sfprojects/jobeet
c:\> mkdir c:\development\sfprojects\jobeet
c:\> cd c:\development\sfprojects\jobeet
$ mkdir -p /lib/vendor
$ cd lib/vendor
$ tar zxpf symfony-1.2.5.tgz
$ mv symfony-1.2.5 symfony
$ rm symfony-1.2.5.tgz
ASTUCE viter les chemins
contenant des espaces
Pour des raisons de simplicit et defficacit dans
la ligne de commande Windows, il est vivement
recommand aux utilisateurs denvironnements
Microsoft dinstaller le projet et dexcuter les
commandes Symfony dans un chemin qui ne con-
tient aucun espace. Par consquent, les rpertoires
Documents and Settings ou encore My
Documents sont proscrire.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
4
Vrification de la configuration du serveur
En cas de problme, le script rapportera toutes les informations nces-
saires pour corriger lerreur. Il faut galement excuter ce script depuis le
navigateur web puisque la configuration de PHP peut tre diffrente en
fonction des deux environnements. Il suffit pour cela de copier le script
quelque part sous la racine web et daccder ce fichier avec le naviga-
teur. Il ne faut pas oublier ensuite de le supprimer une fois la vrification
termine.
Une fois la configuration du serveur valide, il ne reste plus qu vrifier
que Symfony fonctionne correctement en ligne de commande en utili-
sant le script symfony pour afficher la version du framework. Attention,
cet excutable prend un V majuscule en paramtre.
Sous Windows :
$ cd ../..
$ php lib/vendor/symfony/data/bin/check_configuration.php
$ rm web/check_configuration.php
Figure 11
Rsultat du contrle
de la configuration du serveur
$ php lib/vendor/symfony/data/bin/symfony -V
c:> cd ..\..
c:> php lib\vendor\symfony\data\bin\symfony -V
1

m
a
r
r
a
g
e

d
u

p
r
o
j
e
t
Groupe Eyrolles, 2008
5
Lexcution du script symfony sans paramtre donne lensemble des pos-
sibilits offertes par cet utilitaire. Le rsultat obtenu dresse la liste des
tches automatises et des options offertes par le framework pour acc-
lrer les dveloppements.
Sous Windows :
Cet utilitaire est le meilleur ami du dveloppeur Symfony. Il fournit de
nombreux outils permettant damliorer la productivit des activits
rcurrentes comme la suppression du cache, la gnration de code, etc.
Installation du projet
Dans Symfony, les applications partagent le mme modle de donnes et
sont regroupes en projet. Le projet Jobeet accueillera deux applications
au total. La premire, nomme frontend, est lapplication qui sera visible
par tous les utilisateurs, tandis que la seconde, intitule backend, est celle
qui permettra aux administrateurs de grer le site.
Gnrer la structure de base du projet
Pour linstant, seules les librairies du framework Symfony sont installes
dans le rpertoire du projet, mais ce dernier ne dispose pas encore des
fichiers et rpertoires qui lui sont propres. Il faut donc demander Sym-
fony de btir toute la structure de base du projet comprenant de nom-
breux fichiers et rpertoires qui seront tous tudis au fur et mesure des
chapitres de cet ouvrage. La commande generate:project de lexcu-
table symfony permet de crer ladite structure du projet.
Sous Windows :
La tche generate:project gnre la structure par dfaut des rpertoires
et cre les fichiers ncessaires un projet Symfony. Le tableau ci-dessous
dresse la liste des diffrents rpertoires crs.
$ php lib/vendor/symfony/data/bin/symfony
c:> php lib\vendor\symfony\data\bin\symfony
$ php lib/vendor/symfony/data/bin/symfony generate:project jobeet
c:\> php lib\vendor\symfony\data\bin\symfony generate:project jobeet
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
6
La tche generate:project a galement cr un raccourci symfony la
racine du projet Jobeet pour faciliter lcriture de la commande
lorsquune tche doit tre excute. partir de maintenant, au lieu duti-
liser le chemin complet pour excuter la commande symfony, il suffira
dutiliser le raccourci symfony.
Gnrer la structure de base de la premire application
frontend
prsent, lobjectif est de gnrer la structure de base de la premire
application frontend du projet. Celle-ci sera prsente dans le rpertoire
apps/ gnr juste avant. Une fois de plus, il convient de faire appel
lexcutable symfony afin dautomatiser la gnration des rpertoires et
des fichiers propres chaque application.
Une fois de plus, la tche generate:app cre la structure par dfaut des
rpertoires de lapplication dans le dossier apps/frontend/.
Tableau 11 Liste des rpertoires par dfaut dun projet Symfony
Rpertoire Description
apps/ Contient toutes les applications du projet
cache/ Contient les fichiers mis en cache
config/ Contient les fichiers de configuration globaux du projet
lib/ Contient les librairies et classes du projet
log/ Contient les fichiers de logs du framework
plugins/ Contient les plug-ins installs
Test/ Contient les scripts de tests unitaires et fonctionnels
web/ Racine web du projet, cest--dire tout ce qui est accessible depuis
un navigateur web (voir ci-dessous)
$ php symfony generate:app --escaping-strategy=on
X --csrf-secret="Unique$ecret" frontend
Tableau 12 Liste des rpertoires par dfaut dune application Symfony
Rpertoire Description
config/ Contient les fichiers de configuration de lapplication
lib/ Contient les librairies et classes de lapplication
modules/ Contient le code de lapplication (MVC)
templates/ Contient les templates principaux
REMARQUE Pourquoi Symfony gnre-t-il
autant de fichiers ?
Un des bnfices dutiliser un framework hirarchis
est de standardiser les dveloppements. Grce la
structure par dfaut des fichiers et des rpertoires
de Symfony, nimporte quel dveloppeur connais-
sant Symfony pourra reprendre un projet Symfony.
En quelques minutes, il sera mme de naviguer
dans le code, de corriger les bogues, ou encore
dajouter de nouvelles fonctionnalits.
ASTUCE Utiliser lexcutable
la racine du projet
Le fichier symfony est excutable, les utilisateurs
dUnix peuvent remplacer chaque occurrence php
symfony par ./symfony ds maintenant.
Pour Windows, il faut dabord copier le fichier
symfony.bat dans le projet et utiliser
symfony la place de php symfony.
c:\> copy lib\vendor\symfony\data
\bin\symfony.bat .
REMARQUE
Excution des commandes Symfony
Toutes les commandes Symfony doivent tre ex-
cutes depuis le rpertoire racine du projet, sauf si
le contraire est clairement indiqu.
1

m
a
r
r
a
g
e

d
u

p
r
o
j
e
t
Groupe Eyrolles, 2008
7
Lorsque la tche generate:app a t appele, deux options ddies la
scurit lui ont t passes en paramtres. Ces deux options permettent
dautomatiser la configuration de lapplication sa gnration.
--escaping-strategy : cette option active les chappements pour
prvenir des attaques XSS.
--csrf-secret : cette option active la gnration des jetons de session
des formulaires pour prvenir des attaques CSRF.
En passant ces deux options la tche, les futurs dveloppements qui seront
raliss tout au long de cet ouvrage seront dsormais protgs des vulnra-
bilits les plus courantes sur le web. Le framework Symfony se charge auto-
matiquement de prendre les mesures de scurit la place du dveloppeur
pour lui viter de se soucier de ces problmatiques rcurrentes.
Configuration du chemin vers les librairies de Symfony
La commande symfony V permet de connatre la version du framework
installe pour le projet, mais elle donne galement le chemin absolu vers
le rpertoire des librairies de Symfony qui se trouve aussi dans le fichier
de configuration config/ProjectConfiguration.class.php.
Le problme avec ce chemin absolu autognr est quil nest pas por-
table puisquil correspond exclusivement la configuration de la
machine courante. Par consquent, il convient de le changer au profit
dun chemin relatif, ce qui assurera le portage de tout le projet dune
machine une autre sans avoir modifier quoi que ce soit pour que tout
fonctionne.
Dcouvrir les environnements muls par Symfony
Le rpertoire web/ du projet contient deux fichiers crs automatique-
ment par Symfony la gnration de lapplication frontend : index.php
et frontend_dev.php. Ces deux fichiers sont appels contrleurs frontaux
ou front controllers en anglais. Les deux termes seront employs dans cet
ouvrage. Ces deux fichiers ont pour objectif de traiter toutes les requtes
HTTP qui les traversent et qui sont destination de lapplication. La
question qui se pose alors est la suivante : Pourquoi avoir deux contr-
leurs frontaux alors quune seule application a t gnre ?
require_once '/Users/fabien/work/symfony/dev/1.2/lib/autoload/
sfCoreAutoload.class.php';
require_once dirname(__FILE__).'/../lib/vendor/symfony/lib/
autoload/sfCoreAutoload.class.php';
CULTURE WEB En savoir plus
sur les attaques XSS et CSRF
Les attaques XSS (Cross Site Scripting), et les
attaques CSRF (Cross Site Request Forgeries
ou Sea Surf), sont la fois les plus rpandues sur
le web mais aussi les plus dangereuses. Par cons-
quent, il est important de bien les connatre pour
savoir sen prmunir efficacement. Lencyclopdie
en ligne Wikipdia consacre une page ddie
chacune delles aux adresses suivantes :
http://en.wikipedia.org/wiki/Cross-
site_scripting
http://en.wikipedia.org/wiki/Cross-
Site_Request_Forgery
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
8
Quels sont les principaux environnements en dveloppement web ?
Les deux fichiers pointent vers la mme application la diffrence quils
prennent chacun en compte un environnement diffrent. Lorsque lon
dveloppe une application, lexception de ceux qui dveloppent direc-
tement sur le serveur de production, il est ncessaire davoir plusieurs
environnements dexcution cloisonns :
lenvironnement de dveloppement est celui qui est utilis par les dve-
loppeurs quand ils travaillent sur lapplication pour lui ajouter de
nouvelles fonctionnalits ou corriger des bogues ;
lenvironnement de test sert quant lui soumettre lapplication des
sries de tests automatiss pour vrifier quelle se comporte bien ;
lenvironnement de recette est celui quutilise le client pour tester
lapplication et rapporter les bogues et fonctionnalits manquantes
aux chefs de projet et dveloppeurs ;
lenvironnement de production est lenvironnement sur lequel les utili-
sateurs finaux agissent.
Spcificits de lenvironnement de dveloppement
Quest-ce qui rend un environnement unique ? Dans lenvironnement de
dveloppement par exemple, lapplication a besoin denregistrer tous les
dtails de chaque requte afin de faciliter le dbogage, tandis que le sys-
tme de cache des pages est dsactiv tant donn que les changements
doivent tre visibles immdiatement.
Cet environnement est donc optimis pour les besoins du dveloppeur
puisquil lui rapporte toutes les informations techniques dont il a besoin
Figure 12
Affichage des informations de dbogage
en environnement de dveloppement
1

m
a
r
r
a
g
e

d
u

p
r
o
j
e
t
Groupe Eyrolles, 2008
9
pour travailler dans de bonnes conditions. Le meilleur exemple est bien sr
lorsquune exception PHP survient. Pour aider le dveloppeur dboguer
le problme rapidement, le framework Symfony lui affiche dans le naviga-
teur le message derreur avec toutes les informations quil dispose concer-
nant la requte excute. La capture dcran prcdente en tmoigne.
Spcificits de lenvironnement de production
Sur lenvironnement de production, la diffrence provient du fait que le
cache des pages doit bien sr tre activ, et que lapplication est confi-
gure de telle sorte quelle affiche des messages derreur personnaliss
aux utilisateurs finaux la place des exceptions brutes. En dautres
termes, lenvironnement de production doit tre optimis pour rpondre
aux problmatiques de performance et favoriser lexprience utilisateur.
La capture dcran ci-dessous donne le rsultat de la mme requte, ex-
cute prcdemment en environnement de dveloppement, sur lenvi-
ronnement de production.
Un environnement Symfony est un ensemble unique de paramtres de
configuration. Le framework Symfony est livr par dfaut avec trois
dentre eux : dev, test, et prod. Au cours du chapitre 20, il sera prsent
comment crer de nouveaux environnements tel que celui de la recette.
Si lon ouvre les diffrents fichiers des contrleurs frontaux pour les
comparer, on constate que leur contenu est strictement identique,
lexception du paramtre de configuration ddi lenvironnement.
Figure 13
Affichage de la page derreur par dfaut de
Symfony en environnement de production
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
10
Contenu du contrleur frontal web/index.php
Configurer le serveur web
Les sections qui suivent sintressent la configuration du serveur web
afin que celle-ci convienne parfaitement aux besoins dun projet Sym-
fony en termes de scurit et de bonnes pratiques. Deux mthodes de
configuration du serveur sont prsentes. La premire explique ce quil
ne faut absolument pas faire, tandis que la seconde montre la bonne
manire de procder.
Mthode 1 : configuration dangereuse ne pas
reproduire
Dans la section prcdente, un rpertoire complet a t cr pour
hberger lensemble du projet Jobeet. Si ce dernier a t construit
quelque part sous la racine web du serveur, alors il est dsormais acces-
sible entirement depuis un navigateur web.
Bien sr, comme il ny a aucune configuration et que cest trs facile
mettre en uvre, cela signifie aussi que ce nest pas la meilleure manire
de procder Pour sen convaincre, il suffit dessayer daccder au
fichier config/databases.yml depuis le navigateur pour comprendre les
consquences qui peuvent tre provoques avec ce type dattitude pares-
seuse. En effet, si un utilisateur malintentionn dcouvre que le site web
est dvelopp avec Symfony, il aura alors accs de nombreux fichiers de
configuration sensibles en lecture
Il est donc important de garder lesprit de ne jamais utiliser ce type de
configuration sur un serveur de production, et ainsi de prfrer ltape de
configuration dcrite la section suivante. En effet, cette dernire pr-
sente pas pas comment configurer proprement le serveur web pour un
projet Symfony.
<?php
require_once(dirname(__FILE__).'/../config/
ProjectConfiguration.class.php');
$configuration =
ProjectConfiguration::getApplicationConfiguration('frontend',
'prod', false);
sfContext::createInstance($configuration)->dispatch();
ASTUCE Crer de nouveaux
environnements
Dclarer un nouvel environnement Symfony est
aussi simple que de crer un nouveau contrleur
frontal. Plusieurs chapitres et annexes de cet
ouvrage prsentent comment modifier la configu-
ration pour un environnement donn.
1

m
a
r
r
a
g
e

d
u

p
r
o
j
e
t
Groupe Eyrolles, 2008
11
Mthode 2 : configuration sre et recommande
Une bonne pratique de dveloppement web consiste placer sous la racine
web uniquement les fichiers qui ont vritablement besoin dtre atteints
depuis un navigateur : les feuilles de styles en cascade (CSS), les scripts
JavaScript, les animations Flash ou encore les images. Bien sr, par dfaut,
ces fichiers sont stocks sous le rpertoire web/ du projet Symfony.
Ce rpertoire contient dautres sous-dossiers ddis aux ressources web
(css/ et images/) ainsi que les deux contrleurs frontaux. Ces derniers
sont les seuls fichiers PHP qui ont vocation se trouver sous la racine
web du serveur. Tous les autres fichiers PHP peuvent tre cachs du
navigateur, ce qui est plus que conseill dun point de vue scurit.
Cration dun nouveau serveur virtuel pour Jobeet
prsent, il est temps de changer la configuration par dfaut du serveur
Apache pour rendre le nouveau projet accessible sur Internet. Pour cela,
il suffit de localiser et douvrir le fichier de configuration http.conf et
dy ajouter la fin les paramtres de configuration suivants.
Dfinition de la configuration du serveur virtuel de Jobeet
Cette configuration indique au serveur Apache quil faut couter le port
8080 de la machine. Par consquent, le site Internet de Jobeet sera acces-
sible lURL suivante : http://localhost:8080/. Le port peut tre modifi par
un nombre strictement suprieur 1 024 tant donn quil ne requiert
pas de droits dadministrateur.
# Be sure to only have this line once in your configuration
NameVirtualHost 127.0.0.1:8080
# This is the configuration for Jobeet
Listen 127.0.0.1:8080
<VirtualHost 127.0.0.1:8080>
DocumentRoot "/home/sfprojects/jobeet/web"
DirectoryIndex index.php
<Directory "/home/sfprojects/jobeet/web">
AllowOverride All
Allow from All
</Directory>
Alias /sf /home/sfprojects/jobeet/lib/vendor/symfony/data/web/sf
<Directory "/home/sfprojects/jobeet/lib/vendor/symfony/data/web/sf">
AllowOverride All
Allow from All
</Directory>
</VirtualHost>
CONFIGURATION
La directive de configuration Alias
Lalias /sf autorise laccs aux images et fichiers
JavaScript dont ont besoin pour safficher la page
par dfaut de Symfony et la barre de dbogage.
Sur les environnements Windows, la valeur de la
directive Alias doit tre remplace par quelque
chose du genre :
Alias /sf
"c:\development\sfprojects\jobeet\li
b\vendor\symfony\data\web\sf"
Et /home/sfprojects/jobeet/web
devrait tre remplac au profit de :
c:\development\sfprojects\jobeet\web
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
12
Tester la nouvelle configuration dApache
Pour tester cette nouvelle configuration, le serveur Apache doit dabord
tre redmarr afin de recharger les nouveaux paramtres de configura-
tion. Il ne reste alors plus qu ouvrir le navigateur web et vrifier que le
contrleur frontal index.php est bien accessible sur le web.
Pour ce faire, il suffit dappeler lune des deux URLs suivantes en fonc-
tion de la configuration choisie prcdemment pour Apache : http://
localhost:8080/index.php/ ou http://jobeet.localhost/index.php/. La page
daccueil par dfaut devrait apparatre lcran confirmant deux choses :
dune part que le serveur virtuel de Jobeet fonctionne, et dautre part que
lalias /sf est bien configur puisque le navigateur est capable de rcu-
prer les ressources web de la page.
Il est galement possible daccder lapplication en environnement de
dveloppement en utilisant lURL suivante : http://jobeet.localhost/
frontend_dev.php/. La barre de dbogage de Symfony devrait safficher dans
langle suprieur droit de la fentre du navigateur, incluant avec elle de
petits icnes qui prouvent que lalias /sf est convenablement configur.
La barre de dbogage de Symfony est prsente sur chaque page en envi-
ronnement de dveloppement et donne au dveloppeur laccs de nom-
breuses informations en cliquant sur les diffrents onglets. Parmi elles
figurent la configuration courante de lapplication, les traces de logs
enregistrs pour la requte excute, les requtes SQL excutes sur la
base de donnes, le temps de gnration de la page ainsi que la quantit
de mmoire utilise.
CONFIGURATION Configurer un nom de domaine pour lapplication
Pour les administrateurs de la machine, il est prfrable de configurer des serveurs virtuels
plutt que douvrir un nouveau port chaque fois quun nouveau projet dmarre. Au lieu
de choisir un port et dajouter un couteur supplmentaire, il vaut mieux trouver un nom
de domaine et lajouter la directive de configuration ServerName.
# This is the configuration for Jobeet
<VirtualHost 127.0.0.1:80>
ServerName jobeet.localhost
<!-- same configuration as before -->
</VirtualHost>
Le nom de domaine jobeet.localhost utilis dans la configuration dApache doit
tre dclar localement. Pour les environnements Linux, cela se passe dans le fichier
/ect/hosts, tandis que pour Windows XP, ce fichier se trouve dans le rpertoire
C:\WINDOWS\system32\drivers\etc\.
La configuration du nom de domaine consiste ajouter cette ligne supplmentaire au
fichier hosts.
127.0.0.1 jobeet.localhost
ASTUCE
Profiter de la rcriture dURL dApache
Si le module mod_rewrite dApache est ins-
tall sur le serveur web local, le nom du contrleur
frontal index.php/ peut tre retir de lURL.
Ceci est rendu possible grce aux rgles de rcri-
ture dURL configures dans le fichier web/
.htaccess.
REMARQUE Configurer Jobeet sur un serveur
Microsoft Windows IIS
Linstallation et la configuration dun projet Sym-
fony est lgrement diffrente sur les serveurs IIS
des environnements Windows. Le tutoriel figurant
ladresse http://www.symfony-project.org/
cookbook/1_0/en/web_server_iis explique pas
pas comment configurer Symfony sur ce type de
serveur.
1

m
a
r
r
a
g
e

d
u

p
r
o
j
e
t
Groupe Eyrolles, 2008
13
Figure 14
Page daccueil par dfaut dun projet Symfony
en environnement de production
Figure 15
Page daccueil par dfaut dun projet Symfony
en environnement de dveloppement
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
14
Contrler le code source avec Subversion
Quels sont les avantages dun gestionnaire de versions ?
Cest une bonne pratique dutiliser un logiciel de contrle de versions des
fichiers source lorsque lon dveloppe une application web. En effet,
lutilisation de tels logiciels de suivi de versions assure aux dveloppeurs
de nombreux avantages comme :
travailler avec confiance puisquil ny a plus aucun risque de perdre le
moindre fichier source du projet tant donn que Subversion sauve-
garde tout ;
revenir une version antrieure si un changement casse une portion
de code quelconque ;
travailler efficacement plusieurs sur le mme projet en vitant les
conflits ;
avoir accs toutes les versions successives de lapplication.
Cette dernire section dcrit comment utiliser Subversion avec Symfony.
Les utilisateurs dun autre outil de suivi de versions tels que CVS ou
GIT pourront sinspirer de la dmarche prsente ici pour ladapter
leur logiciel de contrle de code source. Cette nouvelle tape considre
quun serveur Subversion est dj install sur la machine et configur
pour tre accessible depuis le protocole HTTP. Par consquent, seul le
processus de cration du dpt Subversion est dcrit.
Installer et configurer le dpt Subversion
La premire tape dinstallation dun dpt Subversion consiste dabord
crer un rpertoire ddi au projet Jobeet dans le dpt global du ser-
veur Subversion.
Puis, sur la machine, la structure de base du dpt Subversion doit tre
cre. Celle-ci inclut entre autres les rpertoires pour grer le tronc, les
branches et les tags du projet. Tout le cycle de vie du projet Jobeet se
passera dans le tronc du dpt.
$ svnadmin create /path/to/jobeet/repository
$ svn mkdir -m "created default directory structure"
X http://svn.example.com/jobeet/trunk
X http://svn.example.com/jobeet/tags
X http://svn.example.com/jobeet/branches
ASTUCE Utiliser un service gratuit en ligne
de suivi du code source
Lorsquil est impossible davoir accs un dpt
Subversion, des solutions alternatives existent. En
effet, des services gratuits en ligne comme Google
Code ou GIT Hub permettent aux dveloppeurs de
se crer gratuitement leur propre dpt de code.
1

m
a
r
r
a
g
e

d
u

p
r
o
j
e
t
Groupe Eyrolles, 2008
15
Il convient ensuite dextraire le contenu vide du rpertoire trunk/ laide
de la commande svn checkout.
Ltape suivante consiste vider le contenu des rpertoires cache/ et
log/ du projet tant donn quils nont aucun intrt tre prsents dans
le dpt Subversion.
prsent, il faut sassurer que les permissions en criture sont bien dfi-
nies sur les rpertoires cache/ et log/ afin que le serveur web puisse
crire dans chacun deux.
Lajout des fichiers dans le dpt peut maintenant tre effectu laide
de la commande svn add. Les fichiers ne sont pas encore envoys au ser-
veur Subversion. Ils sont juste marqus comme prts tre envoys et
sauvegards dans le dpt.
tant donn quaucun fichier ne devra tre enregistr dans les rpertoires
cache/ et log/ du dpt, il convient dajouter le contenu de ces rper-
toires la liste de fichiers ignors par Subversion.
Lditeur de texte par dfaut configur pour SVN devrait se lancer. Sub-
version doit absolument ignorer tout le contenu de ce rpertoire. Par
consquent, il suffit de saisir le caractre toile dans lditeur de texte.
Pour valider la modification, le fichier doit tre sauvegard et lditeur de
texte ferm. Cette opration doit son tour tre rpte pour le rper-
toire log/.
De la mme manire, le caractre toile doit tre saisi.
$ cd /home/sfprojects/jobeet
$ svn co http://svn.example.com/jobeet/trunk/ .
$ rm -rf cache/* log/*
$ chmod 777 cache/ log/
$ svn add *
$ svn propedit svn:ignore cache
*
$ svn propedit svn:ignore log
*
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
16
Il ne reste finalement plus qu valider tous ces changements et les
envoyer au serveur Subversion afin quil se charge de les sauvegarder et
de les versionner. Pour ce faire, un simple appel la commande svn
import suffit comme le montre le code ci-dessous.
En rsum
Ce tout premier chapitre sachve ici. Bien quil nait pas encore t vri-
tablement question de Symfony, le projet Jobeet a pu nanmoins
dmarrer dans de bonnes conditions et repose dj sur des bases solides.
En effet, la premire application Symfony gnre est dj scurise par
dfaut, le serveur web est configur proprement, un dpt Subversion a
t install afin de suivre les volutions du code source du projet, et enfin
dautres bonnes pratiques de dveloppement web ont t prsentes. Par
consquent, le projet est prt recevoir ses premires lignes de code.
Le chapitre qui suit ne sintresse pas encore au code puisquil sera uni-
quement question dy rvler les diffrentes spcifications fonctionnelles
du projet qui seront implmentes partir du troisime chapitre.
Lensemble du projet Jobeet a t versionn dans un dpt Subversion
(http://svn.jobeet.org/doctrine/) du site officiel de Symfony. Ce dpt contient
le code source de lapplication chaque chapitre, ce qui permet de le rcu-
prer nimporte quelle tape de son avance. Par exemple, pour rcuprer
tous les fichiers source du premier chapitre, il suffit dextraire la version
marque release_day_01 laide de la commande svn checkout.
$ svn import -m "made the initial import"
X. http://svn.example.com/jobeet/trunk
ASTUCE Grer un dpt Subversion laide dun client graphique
Les utilisateurs de Windows peuvent sappuyer sur lexcellent logiciel TortoiseSVN pour grer
leurs dpts Subversion en toute simplicit dans un explorateur de fichiers graphique.
$ svn co http://svn.jobeet.org/doctrine/tags/release_day_01/ jobeet/
Groupe Eyrolles, 2008
chapitre 2
Groupe Eyrolles, 2008
Ltude de cas
Tout projet professionnel doit dmarrer avec une tude
pralable des besoins fonctionnels, menant ensuite
llaboration de documents de spcifications techniques.
Ce second chapitre est a pour seul objectif de prsenter les
besoins fonctionnels de ltude de cas qui sera dveloppe tout
au long de cet ouvrage, en ayant recours des descriptions
simples et des maquettes graphiques dinterface.
MOTS-CLS :
BFramework Symfony
Bmaquette dinterface graphique
Btude des besoins fonctionnels
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
20
Tout projet professionnel informatique qui se respecte est labor sui-
vant un planning jalonn en plusieurs tapes. Parmi ces tapes figurent
obligatoirement lanalyse des besoins fonctionnels du commanditaire
ainsi que la rdaction de spcifications techniques. Ces deux tapes sont
bien videmment menes par les quipes dcisionnelles en amont du
dveloppement du projet afin de valider ce que les quipes de production
devront raliser pour satisfaire les besoins du client et des utilisateurs
finaux de lapplication.
Ltude de cas qui sera dveloppe pas pas au cours de cet ouvrage
bnficie elle aussi de spcifications fonctionnelles, dans le but de dter-
miner lensemble des fonctionnalits majeures de lapplication. Par con-
squent, ce chapitre se destine exclusivement prsenter la liste des
besoins fonctionnels laide de maquettes dinterface graphique. Deux
sections distinctes seront spcifies : lapplication grand public, le fron-
tend et linterface dadministration du site, le backend.
la dcouverte du projet
Le premier chapitre de cet ouvrage ntait rsolument pas orient vers le
dveloppement des premires lignes de code PHP, puisquil sagissait de
prparer le terrain en installant lenvironnement de dveloppement, puis
de crer un projet vide avec Symfony. Les premiers pas avec la ligne de
commande de Symfony ont galement permis de sassurer que le projet,
qui sera dvoil dans les sections suivantes, repose dj sur des bases
solides et quil est convenablement configur avec des paramtres de
scurit par dfaut. En progressant un peu au-del des explications du
premier chapitre, le lecteur aura srement remarqu lcran de flicita-
tions de Symfony qui confirme que le projet est prt dmarrer.
lheure o cet ouvrage est rdig, une crise conomique touche toute la
plante depuis plusieurs mois. Le licenciement des salaris ne cesse
quant lui de crotre dans de nombreux secteurs dactivit Heureuse-
ment les dveloppeurs Symfony ont la chance de ne pas vritablement se
sentir concerns par la crise, et cest probablement pour cette raison que
lapprentissage de Symfony se justifie. Nanmoins aujourdhui, il est
encore particulirement difficile de trouver des dveloppeurs Symfony
trs comptents.
Les questions qui se posent alors sont les suivantes : o peut-on trouver
des dveloppeurs Symfony ? Et comment les dveloppeurs peuvent-ils
promouvoir leurs comptences avec Symfony ?
2

t
u
d
e

d
e

c
a
s
Groupe Eyrolles, 2008
21
Il faut pour cela trouver un gestionnaire doffres demploi qui se focalise
uniquement sur les annonces. Ce gestionnaire doit rendre possible la
recherche des meilleures personnes qualifies dans leur domaine dexper-
tise respectif. Ce cyberespace doit tre un lieu convivial o il est facile et
rapide de rechercher une offre demploi ou den proposer une nouvelle.
Inutile de chercher plus loin ! Jobeet est lapplication idale pour ce genre
de besoin. Jobeet est un logiciel Open Source de gestion doffres
demploi qui ne fait quune seule chose, mais qui la fait bien. Il est facile
utiliser, personnaliser, faire voluer et bien sr embarquer dans
dautres applications web. Par ailleurs, il supporte nativement plusieurs
langues, et fait bien videmment usage des toutes dernires technologies
Web 2.0 innovantes afin damliorer lexprience utilisateur. Il fournit
galement des flux RSS ainsi quune API pour interagir avec lui par le
biais de services web.
Ce genre dapplications nexiste-t-il pas dj sur Internet aujourdhui ?
En tant quutilisateur, il ne sera pas difficile de trouver des gestionnaires
doffres demploi comme Jobeet sur Internet. Le plus compliqu est tou-
tefois dessayer den trouver un qui soit la fois libre et disposant de
nombreuses fonctionnalits riches comme celles qui seront dveloppes
ici. Enfin, le dernier avantage de Jobeet est quil suffit de moins de
24 heures pour le dvelopper avec Symfony. Dans cet ouvrage, il est
question de 21 chapitres lire et pratiquer son rythme
Figure 21
Page daccueil par dfaut
dun nouveau projet Symfony
ASTUCE Trouver de nouveaux dveloppeurs
grce Symfonians
Il existe depuis dj quelques annes un vritable
outil Open Source de gestion doffres demploi sur
Internet destination des recruteurs et dve-
loppeurs Symfony : le site symfonians.net (http://
symfonians.net).
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
22
Dcouvrir les spcifications fonctionnelles
de Jobeet
Les diffrents acteurs et applications impliqus
Avant de plonger dans le code, il est important de dcrire davantage les
spcificits du projet. Les sections qui suivent dcrivent les fonctionna-
lits qui seront implmentes la premire version (itration) de Jobeet.
Ces fonctionnalits ont t tablies partir de quelques cas dutilisation.
Le site Internet de Jobeet possde quatre types dacteurs :
ladministrateur qui a les pleins pouvoirs sur le site Internet, cest--
dire qui peut tout y faire ;
lutilisateur qui se contente de visiter le site Internet la recherche
dun emploi ;
le posteur (ou recruteur) qui visite le site afin de poster une nouvelle
offre demploi ;
laffili qui relaie quelques offres demploi sur son propre site
Internet.
Le projet se dcompose galement en deux applications distinctes. La
premire est linterface Internet grand public, appele frontend, partir
de laquelle les utilisateurs interagissent. Elle leur permet entre autres de
consulter et de dposer de nouvelles offres demploi, de sinscrire et
dutiliser lAPI de services web, ou encore de sabonner des flux RSS.
Lapplication frontend est dcrite dans les cas dutilisation F1 F7.
La seconde application est linterface dadministration, autrement
dnomme backend, dans laquelle les administrateurs ont la possibilit de
grer lintgralit des contenus dynamiques comme les utilisateurs, les
offres demploi, les catgories ou encore les affilis. Cette zone est bien
videmment scurise et strictement rserve aux utilisateurs possdant
les droits daccs requis pour y pntrer. Les fonctionnalits de cette
application sont dcrites dans les cas dutilisations B1 B4.
Utilisation de lapplication grand public : le frontend
Scnario F1 : voir les dernires offres en page daccueil
Lorsquun utilisateur arrive sur le site Internet de Jobeet, il dcouvre une
liste des dernires offres demploi actives. Les annonces sont dabord
classes par catgorie par ordre alphabtique croissant, puis par date de
publication dcroissante. Pour chaque annonce, seuls le type de poste, la
socit et sa localisation sont affichs.
2

t
u
d
e

d
e

c
a
s
Groupe Eyrolles, 2008
23
Pour chaque catgorie, la liste montre seulement les dix premires offres
et un lien permet de lister toutes les annonces pour la catgorie donne
(se reporter au cas dutilisation F2).
Depuis la page daccueil, ou tout autre page, lutilisateur peut affiner la
liste des offres demploi (cas dutilisation F3) ou poster une nouvelle
annonce sur le site (cas dutilisation F5).
Scnario F2 : voir les offres dune catgorie
Lorsquun utilisateur clique sur le nom de la catgorie ou bien sur le lien
more jobs depuis la page daccueil, il accde la liste de toutes les offres
de cette catgorie, classes par date de publication dcroissante. Afin de
faciliter la navigation et lexprience utilisateur, la liste est pagine avec
vingt annonces maximum par page.
Figure 22
Maquette de la page daccueil de Jobeet pour le scnario F1
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
24
Scnario F3 : affiner la liste des offres avec des mots-cls
Comme toute application web dynamique hbergeant du contenu en
masse, il est important de faciliter la remonte dinformations partir
dun moteur de recherche.
Lutilisateur peut ainsi saisir une srie de mots-cls pour affiner sa
recherche et rduire le nombre doffres celles qui correspondent le
mieux ses attentes. Les mots-cls saisis par ce dernier peuvent tre des
informations issues des champs localisation, type de poste, nom de la cat-
gorie ou encore nom de la socit.
Lutilisation de la technologie client JavaScript assure galement un
meilleur confort dutilisation et une exprience utilisateur accrue en
rduisant en temps rel la slection doffres demploi chaque fois que
lutilisateur tape un nouveau caractre dans le moteur de recherche. Pour
des raisons daccessibilit, cette fonctionnalit est dveloppe avec du
code non intrusif pour garantir un fonctionnement en mode dgrad
lorsque le JavaScript nest pas activ sur le poste de lutilisateur.
Scnario F4 : obtenir le dtail dune offre
Lutilisateur peut slectionner une offre demploi en cliquant sur le type de
poste depuis la liste, afin dobtenir lintgralit des informations la concer-
nant. La page de dtails affiche les informations suivantes de lannonce :
Figure 23
Maquette de la liste des offres
dune catgorie pour le scnario F2
2

t
u
d
e

d
e

c
a
s
Groupe Eyrolles, 2008
25
le type de poste pourvoir ;
le nom de la socit ;
le logo de la socit ;
le lien vers le site de la socit ;
la localisation du poste ;
le type de contrat (temps plein, temps partiel ou freelance) ;
la description du poste ;
et la dmarche suivre pour postuler.
Scnario F5 : poster une nouvelle annonce
Un utilisateur peut librement ajouter une nouvelle offre demploi au site
Internet. Cette dernire se compose de plusieurs types dinformation,
dont certains sont obligatoires :
le nom de la socit ;
le type de contrat (temps plein, temps partiel ou freelance) ;
le logo de la socit (optionnel) ;
lURL du site de la socit (optionnel) ;
le type de poste ;
Figure 24
Maquette du dtail dune offre demploi pour le scnario F4
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
26
la localisation ;
le nom de la catgorie (choisi daprs une liste de catgories possibles) ;
la description de loffre (les adresses e-mails et les URLs sont auto-
matiquement transformes en liens cliquables) ;
la dmarche suivre pour postuler (les adresses e-mails et les URLs
sont automatiquement transformes en liens cliquables) ;
le mode de diffusion qui indique si loffre peut tre publie ou pas sur
les sites affilis ;
ladresse e-mail de lauteur de lannonce.
Poster une nouvelle offre demploi sur Jobeet ne requiert pas de cra-
tion de compte utilisateur. Le processus dajout dune nouvelle
annonce est simple et se ralise en deux temps. Tout dabord, lutili-
sateur remplit le formulaire avec toutes les informations obligatoires
pour dcrire loffre, puis le valide en prvisualisant la page de
lannonce finale.
Figure 25
Maquette du formulaire de cration dune
nouvelle offre pour le scnario F5
2

t
u
d
e

d
e

c
a
s
Groupe Eyrolles, 2008
27
Bien quun utilisateur nait pas de compte dabonn sur lapplication, il
reste en mesure de modifier son annonce plus tard grce une URL sp-
cifique dans laquelle figure un jeton unique qui lui est attribu la cra-
tion dfinitive de loffre.
Chaque annonce dispose dune dure de vie de trente jours, configurable
par ladministrateur du site (se rfrer au cas dutilisation B2). Lutilisateur
peut quant lui revenir pour ractiver ou prolonger la validit de son
offre pour une nouvelle priode de trente jours condition que celle-ci
arrive expiration dans moins de cinq jours.
Scnario F6 : sinscrire en tant quaffili pour utiliser lAPI
Un utilisateur a besoin de postuler pour devenir un affili et tre autoris
manipuler lAPI de Jobeet. Pour postuler lAPI, il doit dabord ren-
seigner les informations suivantes :
son identit ;
son adresse e-mail ;
ladresse de son site Internet.
Le compte de laffili est soumis la validation expresse des administra-
teurs (se rfrer au cas dutilisation B4). Une fois activ, laffili reoit par e-
mail le jeton unique qui lui permet dutiliser lAPI. Enfin, quand laffili
sabonne au service, il peut aussi choisir un sous-ensemble dannonces
afficher sur son site Internet en slectionnant une liste de catgories dispo-
nibles dans lesquelles figurent les dernires offres remonter.
Scnario F7 : laffili rcupre la liste des dernires offres actives
Un affili peut rcuprer la liste des dernires offres demploi actives en
appelant lAPI partir de son jeton daffili. La liste peut tre retourne au
choix au format XML, JSON ou bien encore YAML. Cette liste contient
alors lensemble des informations publiques disponibles dune annonce.
Utilisation de linterface dadministration : le backend
Scnario B1 : grer les catgories
Un administrateur est capable de grer lensemble des catgories du site
Internet. Il dispose dune page lui permettant de lister toutes les catgo-
ries disponibles ainsi que dun cran lui permettant de crer ou dditer
une catgorie existante. Lapplication Jobeet tant prvue pour supporter
le franais et langlais, les formulaires de cration et ddition des catgo-
ries embarquent les champs de traduction pour chaque langue du site.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
28
Scnario B2 : grer les offres demploi
Un administrateur dispose dune interface dadministration complte des
offres demploi publies sur Jobeet. Un cran de gestion lui permet de lister
lensemble des annonces classes par date de publication dcroissante.
Cette liste se prsente sous la forme dun tableau dont chaque ligne
reprsente une offre. Certains en-ttes du tableau sont cliquables afin de
modifier le classement et lordre des offres. Dautre part, la liste est
pagine avec dix rsultats par page et un formulaire de filtres permet de
laffiner en effectuant des recherches sur certains critres prdfinis.
Chaque offre demploi dispose de ses propres actions qui permettent
ladministrateur de lditer, de la supprimer ou encore de la prolonger
dans le temps. Ces oprations, lexception de la fonction ddition, sont
galement disponibles sur un lot doffres slectionnes au moyen de
cases cocher.
Enfin, une action globale permet lutilisateur de nettoyer la base de
donnes des offres demploi primes depuis un certain nombre de jours
ou qui nont jamais t actives.
Scnario B3 : grer les comptes administrateur
De la mme manire que pour les catgories et les offres demploi,
ladministrateur dispose dun panel de gestion des autres administrateurs
du site. Ce module lui permet ainsi de lister les personnes autorises
grer lapplication, mais galement de crer de nouveaux comptes ou de
supprimer des comptes existants.
Scnario B4 : configurer le site Internet
Enfin, ladministrateur dispose dun dernier panneau dadministration
qui lui permet de grer lensemble des comptes affilis en attente de vali-
dation. Cet espace lui permet en effet dactiver ou de dsactiver sa
guise le compte dun affili souhaitant profiter de lAPI.
Lorsque le compte dun nouvel affili est activ, le systme lui cre et lui
attribue automatiquement un jeton unique servant didentifiant sur
lAPI. Ce jeton lui est envoy directement par e-mail pour lui indiquer
que son compte est valide et en tat de marche immdiat.
2

t
u
d
e

d
e

c
a
s
Groupe Eyrolles, 2008
29
En rsum
Dans tout dveloppement web, il est important de ne pas se prcipiter
sur le codage ds le premier jour. La premire tape doit toujours con-
sister en un recueil des besoins du commanditaire, puis en lcriture des
spcifications fonctionnelles et techniques donnant lieu llaboration
de maquettes visuelles. Cest exactement ce qua voulu montrer ce
deuxime chapitre.
Le prochain chapitre entame plus srieusement le projet en sintressant la
cration de tout le modle de la base de donnes de Jobeet. Il y est notam-
ment question de la prise en main de lORM Doctrine, de la gnration
automatique de la base de donnes et des classes de modle, de lcriture de
donnes initiales de test, de la gnration dun premier module fonctionnel,
et bien sr des premires lignes de code PHP tant attendues
Groupe Eyrolles, 2008
chapitre 3
id
category_id
type
company
logo
url
position
location
description
how_to_apply
token
is_public
is_validated
email
expires_at
created_at
updated_at
Job
id
name
Category
id
url
email
token
is_active
created_at
AfIiate
category_id
affliate_id
CategoryAfIiate
Groupe Eyrolles, 2008
Concevoir
le modle de donnes
La base de donnes est lun des piliers de toute application web
mais sa nature et sa structure peuvent rendre difficile
son intgration dans le dveloppement de lapplication.
Heureusement, Symfony en simplifie la manipulation grce
la couche dORM embarque Doctrine, qui automatise
la cration de la base de donnes partir dun schma
de description et de quelques fichiers de donnes initiales.
MOTS-CLS :
BLORM Doctrine
BBase de donnes
BProgrammation oriente objet
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
32
Ce troisime chapitre se consacre principalement la base de donnes de
Jobeet. Les notions de modle de donnes, de couche dabstraction de bases
de donnes, de librairie dORM ou bien encore de gnration de code seront
abordes. Enfin, le tout premier module fonctionnel de lapplication sera
dvelopp malgr le peu de code que nous aurons crire.
Installer la base de donnes
Le framework Symfony supporte toutes les bases de donnes compati-
bles avec PDO (MySQL, PostgreSQL, SQLite, Oracle, MSSQL).
PDO est la couche native dabstraction de bases de donnes de PHP.
Dans le cadre de ce projet, cest MySQL qui a t choisi.
Crer la base de donnes MySQL
La premire tape consiste bien videmment crer une base de donnes
locale dans laquelle seront sauvegardes et rcupres les donnes de
Jobeet. Pour ce faire, la commande mysqladmin suffit amplement, mais
un outil graphique comme PHPMyAdmin ou bien MySQL Query Browser fait
aussi trs bien laffaire.
Le parti pris dutiliser MySQL pour Jobeet tient juste dans le fait que
cest le plus connu et le plus accessible pour tous. Un autre moteur de
base de donnes aurait pu tre choisi la place dans la mesure o le code
SQL sera automatiquement gnr par lORM. Cest ce dernier qui se
proccupe dcrire les bonnes requtes SQL pour le moteur de base de
donnes install.
Configurer la base de donnes pour le projet Symfony
Maintenant que la base de donnes est cre, il faut spcifier Symfony
sa configuration afin quelle puisse tre relie lapplication via lORM.
La commande configure:database configure Symfony pour fonctionner
avec la base de donnes :
Aprs avoir configur la connexion la base de donnes, toutes les con-
nexions qui rfrencent Propel dans le fichier config/databases.yml
$ mysqladmin uroot pmYsEcret create jobeet
$ php symfony configure:database --name=doctrine
X --class=sfDoctrineDatabase
X "mysql:host=localhost;dbname=jobeet" root mYsEcret
REMARQUE Parcimonie du code crit
Symfony ralise une majeure partie du travail la
place du dveloppeur ; le module web ainsi cr
sera entirement fonctionnel sans que vous ayez
crire beaucoup de code PHP.
3


C
o
n
c
e
v
o
i
r

l
e

m
o
d

l
e

d
e

d
o
n
n

e
s
Groupe Eyrolles, 2008
33
doivent tre supprimes manuellement. Le fichier de configuration de la
base de donnes doit finalement ressembler celui-ci :
La tche configure:database accepte trois arguments : le DSN (Data
Set Name, lien vers la base de donnes) PDO, le nom dutilisateur et le
mot de passe permettant daccder la base de donnes. Si aucun mot de
passe nest requis pour accder la base de donnes du serveur de dve-
loppement, le troisime argument peut tre omis.
Prsentation de la couche dORM Doctrine
Symfony fournit de base deux bibliothques dORM Open-Source pour
interagir avec les bases de donnes : Propel et Doctrine, agissant toutes
deux comme des couches dabstraction. Cependant, cet ouvrage ne
sintresse qu lutilisation de Symfony avec lORM Doctrine.
all:
doctrine:
class: sfDoctrineDatabase
param:
dsn: 'mysql:host=localhost;dbname=jobeet'
username: root
password: mYsEcret
CHOIX DE CONCEPTION Pourquoi Doctrine plutt que Propel ?
Le choix de la librairie Doctrine par rapport Propel simpose de lui-mme pour plusieurs rai-
sons. Bien que la librairie Propel soit aujourdhui mature, il nen rsulte pas moins que son ge
lui fait dfaut. En effet, ce projet Open-Source rendit bien des services aux dveloppeurs
jusqu aujourdhui, mais malheureusement son support et sa communaut ne sont plus aussi
actifs quauparavant. Propel est clairement sur le point de mourir, laissant place des outils
plus rcents comme Doctrine.
La librairie Doctrine dispose de plusieurs atouts par rapport Propel tels quune API simple et
fluide pour dfinir des requtes SQL, de meilleures performances avec les requtes complexes,
la gestion native des migrations, la validation des donnes, lhritage de tables ou bien encore
le support de diffrents comportements utiles ( sluggification , ensembles imbriqus, sup-
pressions virtuelles, recherches).
De surcrot, le projet Doctrine jouit aujourdhui dune communaut toujours plus active et
dune documentation abondante. Dailleurs, lheure o nous crivons ces lignes, un livre
contenant toute la documentation technique de Doctrine est en prparation.
Enfin, le projet Doctrine est support par Sensio Labs, socit ditrice du framework Symfony.
Le dveloppeur principal du projet Doctrine, Jonathan Wage, a rejoint lquipe de production
de la socit en 2008 pour se consacrer davantage au dveloppement et lintgration de
Doctrine dans Symfony.
REMARQUE Fichier databases.yml
La tche configure:database stocke la
configuration de la base de donnes dans le fichier
de configuration config/databases.yml.
Au lieu dutiliser cette tche, le fichier peut tre
dit manuellement.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
34
Quest-ce quune couche dabstraction de base de donnes ?
Une couche dabstraction de bases de donnes est une interface logicielle
qui permet de rendre indpendant le systme de gestion de base de don-
nes de lapplication. Ainsi, une application fonctionnant sur un systme
de base de donnes relationnel (SGBDR), comme MySQL, doit pou-
voir fonctionner de la mme manire avec un systme de base de don-
nes diffrent (Oracle par exemple) sans avoir modifier son code
fonctionnel. Une simple ligne de configuration dans un fichier doit suf-
fire indiquer que le gestionnaire de base de donnes nest plus le mme.
Depuis la version 5.1.0, PHP dispose de sa propre couche daccs aux
bases de donnes : PDO. PDO est labrviation pour PHP Data
Objects . Il sagit en fait surtout dune interface commune daccs aux
bases de donnes plus quune vritable couche dabstraction. En effet,
avec PDO, il est ncessaire dcrire soi-mme les requtes SQL pour
interroger la base de donnes laquelle lapplication est connecte. Or,
les requtes sont largement dpendantes du systme de base de donnes,
bien que SQL soit un langage standard et normalis. Chaque SGBDR
propose en ralit ses propres fonctionnalits, et donc sa propre version
enrichie de SQL pour interroger la base de donnes.
Doctrine sappuie sur lextension PDO pour tout ce qui concerne la con-
nexion et linterrogation des bases de donnes (requtes prpares, tran-
sactions, ensembles de rsultats). En revanche, lAPI se charge de
convertir les requtes SQL pour le systme de gestion de base de don-
nes actif, ce qui en fait une vritable couche dabstraction.
La librairie Doctrine supporte toutes les bases de donnes compatibles
avec PDO telles que MySQL, PostgreSQL, SQLite, Oracle, MSSQL,
Sybase, IBM DB2, IBM Informix
Quest-ce quun ORM ?
ORM est le sigle de Object-Relational Mapping ou Mapping
Objet Relationnel en franais. Une couche dORM est une interface
logicielle qui permet de reprsenter et de manipuler sous forme dobjet
tous les lments qui composent une base de donnes relationnelle.
Ainsi, une table ou bien un enregistrement de celle-ci est peru comme un
objet du langage sur lequel il est possible dappliquer des actions (les
mthodes). Lavantage de cette approche est de sabstraire compltement de
la technologie de gestion de la base de donnes qui fonctionne en arrire-
plan, et de ne travailler quavec des objets ayant des liaisons entre eux.
partir dun modle de donnes dfini plus loin dans ce chapitre, Doc-
trine construit entirement la base de donnes pour le SGBDR choisi,
3


C
o
n
c
e
v
o
i
r

l
e

m
o
d

l
e

d
e

d
o
n
n

e
s
Groupe Eyrolles, 2008
35
ainsi que les classes permettant dinterroger la base de donnes au travers
dobjets. Grce aux relations qui lient les tables entre elles, Doctrine est
par exemple capable de retrouver tous les enregistrements dune table qui
dpendent dun autre dans une seconde table.
Activer lORM Doctrine pour Symfony
Les deux ORMs du framework, Propel et Doctrine, sont tous deux
fournis nativement sous forme de plug-ins internes. ce jour, Propel est
encore la couche dORM active par dfaut dans la configuration dun
projet Symfony. Il faut donc commencer par le dsactiver, puis activer le
plug-in sfDoctrinePlugin qui contient toute la bibliothque Doctrine.
La manipulation est triviale puisquelle ne ncessite quune unique
modification dans le fichier de configuration gnrale du projet config/
ProjectConfiguration.class.php comme le montre le code suivant :
Le mme rsultat peut galement tre obtenu en une seule ligne de code.
Le listing ci-dessous active par dfaut tous les plug-ins du projet, hormis
ceux spcifis dans le tableau pass en paramtre.
Lune ou lautre de ces deux oprations ncessite de vider le cache du
projet Symfony.
Dautre part, certains plug-ins comme sfDoctrinePlugin embarquent
des ressources supplmentaires qui doivent tre accessibles depuis un
navigateur web comme des images, des feuilles de style ou bien encore
des fichiers JavaScript. Lorsquun plug-in est nouvellement install et
activ, lexcution de la tche plugin:publish-assets cre les liens sym-
boliques qui permettent de publier toutes les ressources web ncessaires.
public function setup()
{
$this->enablePlugins(array('sfDoctrinePlugin'));
$this->disablePlugins(array('sfPropelPlugin'));
}
public function setup()
{
$this->enableAllPluginsExcept(array('sfPropelPlugin',
'sfCompat10Plugin'));
}
$ php symfony cache:clear
$ php symfony plugin:publish-assets
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
36
Comme Propel nest pas utilis pour ce projet Jobeet, le lien symbolique
vers le rpertoire web/sfPropelPlugin qui subsiste peut tre supprim en
toute scurit.
Il est temps prsent de sintresser larchitecture de la base de don-
nes qui accueille toutes les donnes de Jobeet.
Concevoir le modle de donnes
Le chapitre prcdent a dcrit les cas dutilisation de lapplication Jobeet
ainsi que tous les composants ncessaires : les offres demploi, les affilia-
tions et les catgories. Nanmoins, cette dfinition des besoins fonction-
nels nest pas suffisante. Une transposition sous forme dun diagramme
UML apporte plus de visibilit sur chaque objet et les relations qui les
lient les uns aux autres.
Dcouvrir le diagramme UML entit-relation
Daprs ltude des besoins fonctionnels de Jobeet, on dtermine claire-
ment les diffrentes relations suivantes :
une offre demploi est associe une catgorie ;
une catgorie a entre 0 et N offres demploi associes ;
une affiliation possde entre 1 et N catgories ;
une catgorie a entre 0 et N affiliations.
Il en rsulte presque naturellement le modle entit-relation suivant.
Ce diagramme dcrit galement les diffrentes proprits de chaque
objet qui deviendront les noms des colonnes de chaque table de la base
$ rm web/sfPropelPlugin
Figure 31
Diagramme entit-relation de Jobeet
id
category_id
type
company
logo
url
position
location
description
how_to_apply
token
is_public
is_validated
email
expires_at
created_at
updated_at
Job
id
name
Category
id
url
email
token
is_active
created_at
AfIiate
category_id
affliate_id
CategoryAfIiate
3


C
o
n
c
e
v
o
i
r

l
e

m
o
d

l
e

d
e

d
o
n
n

e
s
Groupe Eyrolles, 2008
37
de donnes. Un champ created_at a t ajout quelques tables. Sym-
fony reconnat ce type de champ et fixe sa valeur avec la date courante du
serveur lorsquun enregistrement est cr. Il en va de mme pour les
champs updated_at : leur valeur est dfinie partir de la date courante
du serveur lorsque lenregistrement est mis jour dans la table.
Mise en place du schma de dfinition de la base
De limportance du schma de dfinition de la base de donnes
Les offres demploi, les affiliations et les catgories doivent tre stockes
dans la base de donnes relationnelle installe plus haut. Symfony est un
framework qui a la particularit dtre entirement orient Objet, ce qui
permet au dveloppeur de manipuler des objets aussi souvent que pos-
sible. Par exemple, au lieu dcrire des requtes SQL pour retrouver des
enregistrements de la base de donnes, il sera plus naturel et logique de
manipuler des objets.
Dans Symfony, les informations de la base de donnes relationnelle sont
reprsentes ( mappes en langage informatique) en un modle objet.
La gnration et la gestion de modles objets sont entirement laisses
la charge de lORM Doctrine. Pour ce faire, Doctrine a besoin dune
description des tables et de leurs relations pour crer toutes les classes
correspondantes. Il existe deux manires pour tablir ce schma de des-
cription. La premire consiste analyser une base de donnes existante
par rtro-ingnierie (reverse engineering pour les puristes) ou bien en le
crant manuellement.
crire le schma de dfinition de la base de donnes
Comme la base de donnes nexiste pas encore et que nous souhaitons la
garder agnostique, le schma de dfinition de la base de donnes doit tre
crit la main dans le fichier config/doctrine/schema.yml. Le rpertoire
config/doctrine/ nexiste pas encore, il doit tre cr la main.
Le fichier config/doctrine/schema.yml contient une dfinition au
format YAML de tous les lments qui composent la base de donnes.
Cette description indique la structure de chaque table et de ses champs
respectifs, les contraintes appliques sur chacun deux ainsi que toutes les
relations qui les lient les unes aux autres. Le code suivant correspond au
schma de dfinition de la base de donnes de lapplication Jobeet.
$ mkdir config/doctrine
$ touch config/doctrine/schema.yml
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
38
Contenu du fichier config/doctrine/schema.yml
JobeetCategory:
actAs: { Timestampable: ~ }
columns:
name: { type: string(255), notnull: true, unique: true }
JobeetJob:
actAs: { Timestampable: ~ }
columns:
category_id: { type: integer, notnull: true }
type: { type: string(255) }
company: { type: string(255), notnull: true }
logo: { type: string(255) }
url: { type: string(255) }
position: { type: string(255), notnull: true }
location: { type: string(255), notnull: true }
description: { type: string(4000), notnull: true }
how_to_apply: { type: string(4000), notnull: true }
token: { type: string(255), notnull: true,
X unique: true }
is_public: { type: boolean, notnull: true, default: 1 }
is_activated: { type: boolean, notnull: true, default: 0 }
email: { type: string(255), notnull: true }
expires_at: { type: timestamp, notnull: true }
relations:
JobeetCategory: { local: category_id, foreign: id,
foreignAlias: JobeetJobs }
JobeetAffiliate:
actAs: { Timestampable: ~ }
columns:
url: { type: string(255), notnull: true }
email: { type: string(255), notnull: true,
X unique: true }
token: { type: string(255), notnull: true }
is_active: { type: boolean, notnull: true, default: 0 }
relations:
JobeetCategories:
class: JobeetCategory
refClass: JobeetCategoryAffiliate
local: affiliate_id
foreign: category_id
foreignAlias: JobeetAffiliates
JobeetCategoryAffiliate:
columns:
category_id: { type: integer, primary: true }
affiliate_id: { type: integer, primary: true }
relations:
JobeetCategory: { onDelete: CASCADE, local: category_id,
X foreign: id }
JobeetAffiliate: { onDelete: CASCADE, local: affiliate_id,
X foreign: id }
3


C
o
n
c
e
v
o
i
r

l
e

m
o
d

l
e

d
e

d
o
n
n

e
s
Groupe Eyrolles, 2008
39
Le schma est la traduction directe, en format YAML, du diagramme
entit-relation . On identifie ici clairement quatre entits dans ce
modle : JobeetCategory, JobeetJob, JobeetAffiliate et
JobeetCategoryAffiliate. Les relations entre les objets JobeetCategory
et JobeetJob dcouvertes plus haut sont bien retranscrites au mme titre
que celles entre JobeetCategory et JobeetAffiliate via la quatrime
entit JobeetCategoryAffiliate.
De plus, ce modle dfinit des comportements pour certaines entits
grce la section actAs. Les comportements (behaviors en anglais) sont
des outils internes de Doctrine qui permettent dautomatiser des traite-
ments sur les donnes lorsquelles sont crites dans la base. Ici, le com-
portement Timestampable permet de crer et de fixer les valeurs des
champs created_at et updated_at la vole par Doctrine.
Si les tables de la base de donnes sont directement cres grce aux
requtes SQL ou bien laide dun diteur graphique, le fichier de confi-
guration schema.yml correspondant peut tre construit en excutant la
tche doctrine:build-schema.
Dclaration des attributs des colonnes dune table en format YAML
Le fichier schema.yml contient la description de toutes les tables et de
leurs colonnes. Chaque colonne est dcrite au moyen des attributs
suivants :
1 type : le type de la colonne (float, decimal, string, array,
object, blob, clob, timestamp, time, date, enum, gzip) ;
2 notnull : plac la valeur true, cet attribut rend la colonne
obligatoire ;
$ php symfony doctrine:build-schema
FORMAT YAML pour la srialisation des donnes
Daprs le site officiel de YAML, YAML est un standard de srialisation des donnes, facile
utiliser pour un tre humain quel que soit le langage de programmation .
En dautres termes, YAML est un langage simple pour dcrire des donnes (chane de carac-
tres, entiers, dates, tableaux ou tableaux associatifs).
En YAML, la structure est prsente grce lindentation. Les listes dlments sont identifies
par un tiret, et les paires cl/valeur dune section par une virgule. YAML dispose galementdune
syntaxe raccourcie pour dcrire la mme structure en moins de lignes. Les tableaux sont explici-
tement identifis par des crochets [] et les tableaux associatifs avec des accolades {}.
Si vous ntes pas familier avec YAML, cest le moment de commencer vous y intresser dans la
mesure o le framework Symfony lemploie excessivement pour ses fichiers de configuration.
Il y a enfin une chose importante dont il faut absolument se souvenir lorsque lon dite un
fichier YAML : lindentation doit toujours tre compose dun ou de plusieurs espaces, mais
jamais de tabulations.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
40
3 unique : plac la valeur true, lattribut unique cre automatique-
ment un index dunicit sur la colonne.
La base de donnes existe et est configure pour fonctionner avec Sym-
fony, et le schma de description de cette dernire est dsormais crit.
Nanmoins, la base de donnes est toujours vierge et rien ne permet
pour le moment de la manipuler. La partie suivante couvre ces probl-
matiques en prsentant comment lORM Doctrine gnre tout le nces-
saire pour rendre la base de donnes oprationnelle.
Gnrer la base de donnes et les classes
du modle avec Doctrine
Grce la description de la base de donnes prsente dans le fichier
config/doctrine/schema.yml, Doctrine est capable de gnrer les ordres
SQL ncessaires pour crer les tables de la base de donnes ainsi que toutes
les classes PHP qui permettent de lattaquer au travers dobjets mtiers.
Construire la base de donnes automatiquement
La construction de la base de donnes est ralise en trois temps : la
gnration des classes du modle de donnes, puis la gnration du
fichier contenant toutes les requtes SQL excuter, et enfin lexcution
de ce dernier pour crer physiquement toutes les tables.
La premire tape consiste tout dabord gnrer le modle de donnes,
autrement dit les classes PHP relatives chaque table et enregistrement
de la base de donnes.
Cette commande gnre un ensemble de fichiers PHP dans le rpertoire
lib/model/doctrine qui correspondent une entit du schma de dfi-
nition de la base de donnes.
Lorsque toutes les classes du modle de donnes sont prtes, ltape sui-
vante doit permettre de gnrer tous les scripts SQL qui crent physi-
quement les tables dans la base de donnes. Cette fois encore, Symfony
facilite grandement le travail du dveloppeur grce la tche automa-
tique doctrine:build-sql quil suffit dexcuter.
$ php symfony doctrine:build-model
$ php symfony doctrine:build-sql
PROPOS Comportements
supports par Doctrine
Lattribut onDelete dtermine le comportement
ON DELETE des cls trangres. Doctrine sup-
porte les comportements CASCADE, SET NULL
et RESTRICT. Par exemple, lorsquun enregistre-
ment de la table job est supprim, tous les enre-
gistrements associs la table
jobeet_category_affiliate seront
automatiquement effacs de la base de donnes.
3


C
o
n
c
e
v
o
i
r

l
e

m
o
d

l
e

d
e

d
o
n
n

e
s
Groupe Eyrolles, 2008
41
La tche doctrine:build-sql cre les requtes SQL dans le rpertoire
data/sql/, optimises pour le moteur de base de donnes configur :
chantillon de code du fichier data/sql/schema.sql
Enfin, il ne reste plus que la dernire tape franchir. Il sagit de crer
physiquement toutes les tables dans la base de donnes. Une fois de plus,
cest un jeu denfant grce aux tches automatiques fournies par le fra-
mework. Le plug-in sfDoctrinePlugin comporte une tche
doctrine:insert-sql qui se charge dexcuter le script SQL gnr pr-
cdemment pour monter toute la base de donnes.
a y est, la base de donnes est prte accueillir des informations.
Toutes les tables ont t cres ainsi que les contraintes dintgrit rf-
rentielle qui lient les enregistrements des tables entre eux. Il est dsor-
mais temps de sintresser aux classes du modle qui ont t gnres.
Dcouvrir les classes du modle de donnes
la premire tape de construction de la base de donnes, les fichiers
PHP du modle de donnes ont t gnrs laide de la tche
doctrine:build-model. Ces fichiers correspondent aux classes PHP qui
transforment les enregistrements dune table en objets mtiers pour
lapplication.
La tche doctrine:build-model construit les fichiers PHP dans le rper-
toire lib/model/doctrine/ qui permettent dinteragir avec la base de
donnes. En parcourant ces derniers, il est important de remarquer que
Doctrine gnre trois classes par table. Par exemple, pour la table
jobeet_job :
1 JobeetJob : un objet de cette classe reprsente un seul enregistrement
de la table jobeet_job. La classe est vide par dfaut ;
2 BaseJobeetJob : cest la superclasse de JobeetJob. Chaque fois que
lon excute la tche doctrine:build-model, cette classe est rg-
nre, cest pourquoi toutes les personnalisations doivent tre crites
dans la classe JobeetJob ;
3 JobeetJobTable : la classe dfinit des mthodes qui retournent prin-
cipalement des collections dobjets JobeetJob. Cette classe est vide
par dfaut.
CREATE TABLE jobeet_category (id BIGINT AUTO_INCREMENT, name VARCHAR(255)
NOT NULL COMMENT 'test', created_at DATETIME, updated_at DATETIME, slug
VARCHAR(255), UNIQUE INDEX sluggable_idx (slug), PRIMARY KEY(id))
ENGINE = INNODB;
$ php symfony doctrine:insert-sql
PROPOS Aide sur les tches automatiques
Comme nimporte quel outil en ligne de commande,
les tches automatiques de Symfony peuvent
prendre des arguments et des options. Chaque
tche est livre avec un manuel dutilisation qui
peut tre affich grce la commande help.
$ php symfony help
X doctrine:insert-sql
Le message daide liste tous les arguments et
options possibles, donne la valeur par dfaut de
chacun deux, et donne quelques exemples prati-
ques dutilisation.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
42
Les valeurs des colonnes dun enregistrement sont manipules partir
dun objet modle en utilisant quelques accesseurs (mthodes get*()) et
mutateurs (mthodes set*()) :
Au vu de cette syntaxe, il est vident que manipuler des objets plutt que des
requtes SQL devient la fois plus naturel, plus ais mais aussi plus scuris,
puisque lchappement des donnes est laiss la charge de lORM.
Le modle de donnes de Jobeet tablit une relation entre les offres demploi
et les catgories. Au moment de gnrer les classes du modle de donnes,
Doctrine a devin les relations possibles entre les entits et les a reportes
fidlement dans les classes PHP gnres. Ainsi, un objet JobeetJob dis-
pose de mthodes pour dfinir ou bien rcuprer lobjet JobeetCategory qui
lui est associ comme le prsente lexemple de code ci-dessous.
Doctrine fonctionne bilatralement, cest--dire que les liaisons entre les
objets sont gres aussi bien dun ct que dun autre. La classe
JobeetJob possde des mthodes pour agir sur lobjet JobeetCategory
qui lui est associ mais la classe JobeetCategory possde elle aussi des
mthodes pour dfinir les objets JobeetJob qui lui appartiennent.
Gnrer la base de donnes et le modle en une seule passe
La tche doctrine:build-all est un raccourci pour les tches excutes
dans cette section et bien dautres. Il est temps maintenant de gnrer les
formulaires et les validateurs pour les classes de modle de Jobeet.
Les validateurs seront prsents la fin de ce chapitre, tandis que les for-
mulaires seront expliqus en dtail au cours du chapitre 10.
Symfony charge automatiquement les classes PHP la place du dve-
loppeur, ce qui signifie que nul appel require nest requis dans le code.
$job = new JobeetJob();
$job->setPosition('Web developer');
$job->save();
echo $job->getPosition();
$job->delete();
$category = new JobeetCategory();
$category->setName('Programming');
$job = new JobeetJob();
$job->setCategory($category);
$ php symfony doctrine:build-all --no-confirmation
3


C
o
n
c
e
v
o
i
r

l
e

m
o
d

l
e

d
e

d
o
n
n

e
s
Groupe Eyrolles, 2008
43
Cest lune des innombrables fonctionnalits que le framework automa-
tise, bien que cela entrane un lger inconvnient. En effet, chaque fois
quune nouvelle classe est ajoute au projet, le cache de Symfony doit
tre vid. La tche doctrine:build-model a gnr un certain nombre de
nouvelles classes, cest pourquoi le cache doit tre rinitialis.
Le dveloppement dun projet est fortement acclr grce aux nom-
breux composants et tches automatiques que fournit nativement le fra-
mework Symfony. Dans le cas prsent, la base de donnes ainsi que
toutes les classes PHP du modle de donnes ont t mises en place en
un temps record. Imaginez le temps quil vous aurait fallu pour raliser
tout cela la main en partant de rien !
Toutefois, il manque encore quelque chose dessentiel pour pouvoir se
lancer pleinement dans le code et le dveloppement des fonctionnalits
de Jobeet. Il sagit bien sr des donnes initiales qui permettent lappli-
cation de sinitialiser et dtre teste. La section suivante se consacre
pleinement ce sujet.
Prparer les donnes initiales de Jobeet
Les tables ont t cres dans la base de donnes mais celles-ci sont tou-
jours vides. Il faut donc prparer quelques jeux de donnes pour remplir
les tables de la base de donnes au fur et mesure de lavance du projet.
Dcouvrir les diffrents types de donnes
dun projet Symfony
Pour nimporte quelle application, il existe trois types de donnes :
1 les donnes initiales : ce sont les donnes dont a besoin lapplication
pour fonctionner. Par exemple, Jobeet requiert quelques catgories.
Sil ny en a pas, personne ne pourra soumettre doffre demploi. Un
utilisateur administrateur capable de sauthentifier linterface
dadministration (backend en anglais) est galement ncessaire ;
2 les donnes de test : les donnes de test sont ncessaires pour tester
lapplication et pour sassurer quelle se comporte comme les cas dutili-
sation fonctionnels le spcifient. Bien videmment, le meilleur moyen
de le vrifier est dcrire des sries de tests automatiss ; cest pourquoi
des tests unitaires et fonctionnels seront dvelopps pour Jobeet. Ainsi,
chaque fois que les tests seront excuts, une base de donnes saine
constitue de donnes fraches et prtes tre testes sera ncessaire ;
$ php symfony cache:clear
ASTUCE Comprendre la syntaxe des tches
automatiques de Symfony
Une tche Symfony est constitue dun espace de
nom (namespace pour les puristes) et dun nom.
Chacun deux peut tre raccourci tant quil ny a
pas dambigut avec dautres tches. Ainsi, les
commandes suivantes sont quivalentes
cache:clear :
$ php symfony cache:cl
$ php symfony ca:c
Comme la tche cache:clear est frquem-
ment utilise, elle possde une autre abrviation
encore plus courte :
$ php symfony cc
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
44
3 les donnes utilisateur : les donnes utilisateur sont cres par les utili-
sateurs au cours du cycle de vie normal de lapplication.
Pour le moment, Jobeet requiert quelques donnes initiales pour initia-
liser lapplication. Symfony fournit un moyen simple et efficace de
dfinir ce type de donnes laide de fichiers YAML, comme lexplique
la partie suivante.
Dfinir des jeux de donnes initiales pour Jobeet
Chaque fois que Symfony cre les tables dans la base de donnes, toutes
les donnes sont perdues. Pour peupler la base de donnes avec des don-
nes initiales, nous pourrions crer un script PHP, ou bien excuter
quelques requtes SQL avec le programme MySQL. Nanmoins,
comme ce besoin est relativement frquent, il existe une meilleure faon
de procder avec Symfony. Il sagit de crer des fichiers YAML dans le
rpertoire data/fixtures/, puis dexcuter la tche doctrine:data-load
pour les charger en base de donnes.
Les deux listings suivants de code YAML dfinissent un jeu de donnes
initiales pour lapplication. Le premier fichier dclare les donnes pour
remplir la table jobeet_category tandis que le second sert peupler la
table des offres demploi jobeet_job.
Contenu du fichier data/fixtures/categories.yml
Contenu du fichier data/fixtures/jobs.yml
JobeetCategory:
design:
name: Design
programming:
name: Programming
manager:
name: Manager
administrator:
name: Administrator
JobeetJob:
job_sensio_labs:
JobeetCategory: programming
type: full-time
company: Sensio Labs
logo: sensio-labs.gif
url: http://www.sensiolabs.com/
position: Web Developer
location: Paris, France
description: |
You've already developed websites with Symfony and you
want to work with Open-Source technologies. You have a
3


C
o
n
c
e
v
o
i
r

l
e

m
o
d

l
e

d
e

d
o
n
n

e
s
Groupe Eyrolles, 2008
45
Un fichier de donnes est crit au format YAML, et dcrit les objets
modles rfrencs par un nom unique. Par exemple, les deux offres
demploi sont intitules job_sensio_labs et job_extreme_sensio. Cet
intitul sert lier les objets entre eux sans avoir exprimer explicitement
les cls primaires, qui sont dans la plupart des cas auto-incrmentes et
qui varient perptuellement. La catgorie de loffre demploi
job_sensio_labs est programming, ce qui correspond la catgorie
nomme Programming .
Dans un fichier YAML, lorsquune chane de caractres contient des
retours la ligne (comme la colonne description dans les donnes ini-
tiales du fichier doffres demploi), la barre verticale (pipe en anglais) |
sert indiquer que la chane occupera plusieurs lignes.
minimum of 3 years experience in web development with PHP
or Java and you wish to participate to development of
Web 2.0 sites using the best frameworks available.
how_to_apply: |
Send your resume to fabien.potencier [at] sensio.com
is_public: true
is_activated: true
token: job_sensio_labs
email: job@example.com
expires_at: '2010-10-10'
job_extreme_sensio:
JobeetCategory: design
type: part-time
company: Extreme Sensio
logo: extreme-sensio.gif
url: http://www.extreme-sensio.com/
position: Web Designer
location: Paris, France
description: |
Lorem ipsum dolor sit amet, consectetur adipisicing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in.
Voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est
laborum.
how_to_apply: |
Send your resume to fabien.potencier [at] sensio.com
is_public: true
is_activated: true
token: job_extreme_sensio
email: job@example.com
expires_at: '2010-10-10'
REMARQUE Tlcharger les images relatives
aux donnes initiales
Le fichier de donnes initiales des offres rfrence
deux images. Vous pouvez les tlcharger :
Bhttp://www.symfony-project.org/get/
jobeet/sensio-labs.gif
Bhttp://www.symfony-project.org/get/
jobeet/extreme-sensio.gif
Vous devrez ensuite les placer dans le rpertoire
uploads/jobs/.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
46
Bien quun fichier de donnes contienne des objets provenant dun ou de
plusieurs modles, il est vivement recommand de ne crer quun seul
fichier par modle.
Dans un fichier de donnes, il nest nul besoin de dfinir toutes les
valeurs des colonnes. Si certaines valeurs ne sont pas dfinies, Symfony
utilisera la valeur par dfaut dfinie dans le schma de la base de don-
nes. Comme Symfony utilise Doctrine pour charger les donnes en
base de donnes, tous les comportements natifs (comme la fixation auto-
matique des colonnes created_at et updated_at) et les comportements
personnaliss ajouts aux classes de modle sont activs.
Charger les jeux de donnes de tests en base de donnes
Une fois les fichiers de donnes initiales crs, leur chargement en base
de donnes est aussi simple que de lancer une tche automatique. Le
plug-in sfDoctrinePlugin possde la commande doctrine:data-load
qui se charge denregistrer toutes ces donnes dans la base de donnes.
Excuter lune aprs lautre toutes les tches pour rgnrer la base de
donnes, construire les classes du modle et insrer les donnes initiales
peut se rvler trs vite fastidieux. Symfony propose une tche simple
qui ralise toutes ces oprations en une seule passe comme lexplique la
section suivante.
Rgnrer la base de donnes et le modle en une seule
passe
La tche doctrine:build-all-reload est un raccourci pour la tche
doctrine:build-all suivi de la tche doctrine:data-load. Celle-ci
soccupe de rgnrer toute la base de donnes et les classes du modle,
puis finit par charger les donnes initiales dans les tables.
Il suffit de lancer la commande doctrine:build-all-reload puis de
sassurer que tout a bien t gnr depuis le schma. Cette tche gnre les
classes de formulaires, de filtres, de modle, supprime la base de donnes
existante et la recre avec toutes les tables peuples par les donnes initiales.
$ php symfony doctrine:data-load
$ php symfony doctrine:build-all-reload
ASTUCE Propel versus Doctrine
Propel a besoin que les fichiers de donnes de
test soient prfixs par des nombres pour dter-
miner dans quel ordre les fichiers doivent tre
chargs. Avec Doctrine, ce nest pas ncessaire
puisque toutes les donnes sont charges et sau-
vegardes dans le bon ordre pour sassurer que
les cls trangres sont dfinies correctement.
3


C
o
n
c
e
v
o
i
r

l
e

m
o
d

l
e

d
e

d
o
n
n

e
s
Groupe Eyrolles, 2008
47
Profiter de toute la puissance de Symfony
dans le navigateur
Linterface en ligne de commande est plutt pratique mais nest malgr
tout pas trs attrayante, qui plus est pour un projet web. ce stade
davancement du projet, Jobeet est dj prt accueillir les pages web
dynamiques qui interagissent avec la base de donnes.
La suite du chapitre aborde les fonctionnalits essentielles daffichage de
la liste des offres demploi, et ddition et de suppression dune offre
existante. Comme cela a dj t expliqu au premier chapitre, un projet
Symfony est constitu dapplications. Chaque application est ensuite
divise en modules.
Un module est un ensemble autonome de code PHP qui reprsente une
fonctionnalit de lapplication (le module API par exemple), ou bien un
ensemble de manipulations que lutilisateur peut raliser sur un objet du
modle (un module doffres demploi par exemple).
Gnrer le premier module fonctionnel job
Le module principal de lapplication Jobeet est bien videmment celui
qui permet de crer et de consulter des offres. Le framework Symfony
est capable de gnrer automatiquement un module fonctionnel complet
pour un modle donn. Ce module intgre de base toutes les fonction-
nalits de manipulation simples telles que lajout, la modification, la sup-
pression et la consultation.
Ce travail est ralis laide de la commande doctrine:generate-module
comme le prsente le code ci-dessous.
La tche doctrine:generate-module gnre un module job dans lappli-
cation frontend pour le modle JobeetJob. Comme avec la plupart des
tches Symfony, quelques fichiers et rpertoires ont t crs. Tous les
fichiers du prsent module ont t fabriqus sous le rpertoire apps/
frontend/modules/job/.
Composition de base dun module gnr par Symfony
Le tableau ci-aprs dcrit les rpertoires de base qui ont t gnrs par
Symfony lexcution de la tche doctrine:generate-module dans le
rpertoire apps/frontend/modules/job/.
$ php symfony doctrine:generate-module --with-show
X --non-verbose-templates frontend job JobeetJob
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
48
Le rpertoire actions/ contient la classe dans laquelle se trouvent toutes
les actions CRUD (create, retrieve, update et delete) de base qui permet-
tent de manipuler une offre demploi. toutes ces actions est associ un
ensemble de fichiers de templates gnrs dans le rpertoire templates/.
Dcouvrir les actions du module job
Le fichier actions/actions.class.php dfinit toutes les actions possibles
pour le module job. Cest exactement ce que dcrit le tableau 3-2.
Tableau 31 Rpertoires du module apps/frontend/modules/job
Rpertoire Description
actions/ Les actions du module
templates/ Les templates du module
Figure 32
Formulaire ddition dune offre demploi
3


C
o
n
c
e
v
o
i
r

l
e

m
o
d

l
e

d
e

d
o
n
n

e
s
Groupe Eyrolles, 2008
49
Le module job est dsormais accessible et utilisable depuis un navigateur
web ladresse suivante :
Bhttp://jobeet.localhost/frontend_dev.php/job
Comprendre limportance de la mthode magique
__toString()
Si lon tente dditer une offre demploi, on remarque que la liste drou-
lante Category id est une slection de lensemble des catgories pr-
sentes dans la base de donnes. La valeur de chaque option est obtenue
grce la mthode __toString().
Doctrine essaye dappeler nativement une mthode __toString() en
devinant un nom de colonne descriptif tel que title, name, subject, etc.
Si lon dsire quelque chose de plus personnalis, il est alors ncessaire
de redfinir la mthode __toString() comme le prsente le listing ci-
aprs. Le modle JobeetCategory est capable de deviner la mthode
__toString() en utilisant la colonne name de la table jobeet_category.
Listing du fichier lib/model/doctrine/JobeetJob.class.php
Tableau 32 Liste des actions du fichier
apps/frontend/modules/job/actions/actions.class.php
Nom de laction Description
index Affiche les enregistrements dune table
show Affiche les champs et les valeurs dun enregistrement donn
new Affiche un formulaire pour crer un nouvel enregistrement
create Cre un nouvel enregistrement
edit Affiche un formulaire pour diter un enregistrement existant
update Met jour les informations dun enregistrement daprs les valeurs
transmises par lutilisateur
delete Supprime un enregistrement donn de la table
class JobeetJob extends BaseJobeetJob
{
public function __toString()
{
return sprintf('%s at %s (%s)', $this->getPosition(),
X $this->getCompany(), $this->getLocation());
}
}
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
50
Listing du fichier lib/model/doctrine/JobeetAffiliate.class.php
Ajouter et diter les offres demploi
Les offres demploi sont prsent prtes tre ajoutes et dites. Si un
champ obligatoire est laiss vide ou bien si sa valeur est incorrecte (une
date invalide par exemple), le processus de validation du formulaire pro-
voquera une erreur, empchant alors la mise jour de lenregistrement.
Symfony cre effectivement les rgles de validation basiques en intros-
pectant le schma de la base de donnes.
class JobeetAffiliate extends BaseJobeetAffiliate
{
public function __toString()
{
return $this->getUrl();
}
}
Figure 33
Contrle de saisie basique dans le formulaire
de cration dune offre demploi.
3


C
o
n
c
e
v
o
i
r

l
e

m
o
d

l
e

d
e

d
o
n
n

e
s
Groupe Eyrolles, 2008
51
En rsum
Cest tout pour ce troisime chapitre. Lintroduction tait trs claire. En
effet, peu de code PHP a t crit mais Jobeet dispose dj dun module
doffres demploi entirement fonctionnel et prt tre amlior et per-
sonnalis. Souvenez-vous, moins de code PHP signifie aussi moins de
risques dy trouver un bug !
Un moyen simple de progresser avant de passer au chapitre suivant est
de prendre la peine de lire le code gnr pour le module et le modle, et
essayer den comprendre le fonctionnement.
Le chapitre qui suit aborde lun des plus importants paradigmes utiliss
dans les frameworks web : le patron de conception MVC.
Le code complet du chapitre est disponible dans le dpt SVN de Jobeet
au tag release_day_03 :
$ svn co http://svn.jobeet.org/doctrine/tags/release_day_03/
jobeet/
Groupe Eyrolles, 2008
chapitre 4
Groupe Eyrolles, 2008
Le contrleur
et la vue
Structurer une application web nest pas toujours vident
du fait des nombreux composants qui interviennent au cours
du dveloppement. Le paradigme modle, vue, contrleur
est lune des solutions capables de rpondre ce besoin.
Nous verrons ainsi comment Symfony intgre parfaitement
ce motif de conception prouv dans un projet.
MOTS-CLS :
BParadigme Modle Vue
Contrleur
BObjets sfWebRequest
et sfWebResponse
BLayout
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
54
Le chapitre prcdent a montr comment Symfony simplifie la gestion
dune base de donnes en abstrayant les diffrences entre les moteurs de
base de donnes et en convertissant des objets relationnels en classes
PHP. De plus, ce fut loccasion de dcouvrir la couche dORM Doctrine
qui permet dautomatiser la cration dune base de donnes partir dun
schma de description et de quelques fichiers de donnes initiales.
Ce chapitre sintresse la personnalisation du module basique doffres
demploi gnr prcdemment. Ce module dispose dj de tout le code
ncessaire pour lapplication :
une page qui liste toutes les offres demploi ;
une page pour crer une nouvelle offre demploi ;
une page pour mettre jour une offre demploi existante ;
une page pour supprimer une offre demploi.
Larchitecture MVC et son implmentation
dans Symfony
Dvelopper un site en PHP sans recourir un framework signifie en
gnral de navoir quun seul fichier PHP par page HTML, chaque fichier
ayant la mme structure : initialisation et configuration gnrale, logique
mtier relative la page appele, rcupration des enregistrements de la
base de donnes, et enfin le code HTML qui construit la page.
Mme en utilisant un moteur de templates pour sparer la logique
mtier du code HTML final, ou en recourant une couche dabstraction
pour sparer les interactions du modle de la base de donnes, il nen
rsulte pas moins, la plupart du temps, une trop grosse quantit de code
qui se rvle tre un vritable cauchemar maintenir.
Heureusement, chaque problme sa solution. En dveloppement web,
la solution actuelle la plus rpandue pour organiser du code de manire
efficace et maintenable est davoir recours au motif de conception MVC.
Ce dernier dfinit en effet un moyen dorganiser le code en fonction de
la nature de chacune de ses parties. Ce patron spare ainsi le code en
trois couches distinctes :
le modle qui dfinit la logique mtier (la base de donnes appartient
au modle). Dans Symfony, toutes les classes et tous les fichiers pro-
pres au modle sont stocks dans le rpertoire lib/model/ ;
la vue qui est linterface avec quoi lutilisateur interagit (un moteur de
templates fait partie de la vue). Dans Symfony, la couche vue est
principalement constitue de templates PHP. Ils sont stocks dans les
diffrents rpertoires templates/ ;
4


L
e

c
o
n
t
r

l
e
u
r

e
t

l
a

v
u
e
Groupe Eyrolles, 2008
55
le contrleur qui est la partie du code appelant le modle pour en
rcuprer des donnes quil transmet ensuite la vue pour le rendu
final au client. Lors de linstallation de Symfony le premier jour, il a
t montr que toutes les requtes taient gres par les front control-
lers (index.php et frontend_dev.php). Ces contrleurs frontaux dl-
guent le vritable travail aux actions. Ces dernires sont logiquement
groupes l'intrieur de modules.
Les prochaines pages de cet ouvrage sappuient sur les maquettes (mock-
ups en anglais) tablies au second chapitre. La page daccueil et la page
des offres seront personnalises et dynamises. Dans la foule, de nom-
breuses amliorations seront apportes dans diffrents fichiers, afin de
prsenter la structure de fichiers de Symfony et la manire de sparer le
code entre les diffrentes couches.
Habiller le contenu de chaque page avec un
mme gabarit
Dcorer une page avec un en-tte et un pied de page
Pour commencer, en regardant de plus prs les maquettes, on identifie
clairement que la plupart des pages HTML se ressemblent. Or, il a t
dmontr juste avant quil vaut mieux viter tout prix la duplication de
code, quil sagisse de code HTML ou bien de code PHP. Mais com-
ment empcher cette copie des lments communs de la vue ? Un moyen
Figure 41
Schma de fonctionnement du motif de conception MVC
BONNES PRATIQUES Le principe DRY
DRY est un acronyme pour Dont Repeat
Yourself. Il sagit dune philosophie en dve-
loppement informatique qui consiste limiter le
code redondant (i.e. la duplication). De cette
manire, le dbogage et la maintenance sen
voient grandement simplifis.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
56
simple de rsoudre ce problme est de dfinir un en-tte et un pied de
page, puis de les inclure dans chaque template.
Dcorer le contenu dune page avec un dcorateur
Malheureusement, ici, len-tte et le pied de page ne contiennent pas de
code HTML valide. Il faut donc opter pour une meilleure manire de
faire. Au lieu de rinventer la roue, Symfony sappuie sur un autre motif
de conception : le patron Dcorateur (decorator en anglais). Ce dernier
rsout le problme autrement, en habillant le contenu rendu par le tem-
plate principal, appel layout.
Dans Symfony, le layout par dfaut dune application est un fichier PHP
appel layout.php et se trouve dans le rpertoire apps/frontend/
templates/. Celui-ci contient lensemble des templates globaux dune
application.
Listing du fichier apps/frontend/templates/layout.php
Figure 42
Structure dune page web
en trois parties
Figure 43
Schma de fonctionnement
du motif de conception Dcorateur
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Jobeet - Your best job board</title>
<link rel="shortcut icon" href="/favicon.ico" />
<?php include_javascripts() ?>
<?php include_stylesheets() ?>
</head>
<body>
<div id="container">
<div id="header">
<div class="content">
<h1><a href="/job">
<img src="/images/jobeet.gif" alt="Jobeet Job Board" />
</a></h1>
4


L
e

c
o
n
t
r

l
e
u
r

e
t

l
a

v
u
e
Groupe Eyrolles, 2008
57
<div id="sub_header">
<div class="post">
<h2>Ask for people</h2>
<div>
<a href="/job/new">Post a Job</a>
</div>
</div>
<div class="search">
<h2>Ask for a job</h2>
<form action="" method="get">
<input type="text" name="keywords"
id="search_keywords" />
<input type="submit" value="search" />
<div class="help">
Enter some keywords (city, country, position, ...)
</div>
</form>
</div>
</div>
</div>
</div>
<div id="content">
<?php if ($sf_user->hasFlash('notice')): ?>
<div class="flash_notice">
<?php echo $sf_user->getFlash('notice') ?>
</div>
<?php endif; ?>
<?php if ($sf_user->hasFlash('error')): ?>
<div class="flash_error">
<?php echo $sf_user->getFlash('error') ?>
</div>
<?php endif; ?>
<div class="content">
<?php echo $sf_content ?>
</div>
</div>
<div id="footer">
<div class="content">
<span class="symfony">
<img src="/images/jobeet-mini.png" />
powered by <a href="http://www.symfony-project.org/">
<img src="/images/symfony.gif" alt="symfony framework" />
</a>
</span>
<ul>
<li><a href="">About Jobeet</a></li>
<li class="feed"><a href="">Full feed</a></li>
<li><a href="">Jobeet API</a></li>
<li class="last"><a href="">Affiliates</a></li>
</ul>
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
58
Ce layout fait appel des fonctions et fait rfrence des variables PHP.
La variable $sf_content est lune des plus importantes car elle est auto-
matiquement dfinie par le framework lui-mme et contient le code
HTML gnr par laction.
Dsormais, en parcourant le module job (http://jobeet.localhost/
frontend_dev.php/job), toutes les actions sont dcores par le layout.
Intgrer la charte graphique de Jobeet
Rcuprer les images et les feuilles de style
Ce livre ne sintresse pas au design web, cest pourquoi toutes les res-
sources ncessaires au projet ont t prpares lavance. Les images et
les feuilles de style sont toutes disponibles en tlchargement.
1 Tlchargez larchive des images (http://www.symfony-project.org/get/
jobeet/images.zip) et placez les dans le rpertoire web/images/ ;
2 Tlchargez larchive des feuilles de style (http://www.symfony-
project.org/get/jobeet/css.zip), puis placez les dans le rpertoire web/css/.
Le fichier layout.php fait appel un favicon . Le favicon de Jobeet
peut tre tlcharg ladresse http://www.symfony-project.org/get/jobeet/
favicon.ico puis dpos la racine du rpertoire web/.
Par dfaut, la tche generate:project cre trois rpertoires pour les res-
sources du projet : web/images/ pour les images, web/css/ pour les
feuilles de style, et web/js/ pour les JavaScripts. Cest lune des conven-
tions dfinies par Symfony, mais il est bien sr possible de les stocker
ailleurs dans le rpertoire web/.
Le lecteur assidu aura remarqu que mme si le fichier main.css est
mentionn nulle part dans le layout par dfaut, il est finalement prsent
dans le code HTML gnr. Comment est-ce possible ?
La feuille de style a t incluse par lappel de la fonction
include_stylesheets(), se trouvant dans le tag <head> du layout. La
fonction include_stylesheets() est en fait un helper.
</div>
</div>
</div>
</body>
</html>
DFINITION Helpers dans Symfony
Un helper est une fonction dfinie par Symfony qui
peut prendre des paramtres et qui renvoie du
code HTML. La plupart du temps, les helpers
embarquent des petits bouts de code frquem-
ment utiliss dans les templates et font ainsi
gagner du temps.
4


L
e

c
o
n
t
r

l
e
u
r

e
t

l
a

v
u
e
Groupe Eyrolles, 2008
59
Configurer la vue partir dun fichier de configuration
Comment le helper a-t-il connaissance des feuilles de style inclure
dans la page ?
La couche Vue peut tre modele en ditant le fichier de configuration
de lapplication view.yml. Ci-dessous, le contenu de celui gnr par
dfaut par la tche generate:app :
Fichier de configuration de la vue, gnr par dfaut : apps/frontend/config/view.yml
Figure 44 Interface graphique de Jobeet
default:
http_metas:
content-type: text/html
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
60
Le fichier view.yml configure les paramtres par dfaut de tous les tem-
plates de lapplication. Par exemple, la section des feuilles de style dfinit
un tableau de fichiers de feuilles de style inclure pour chaque page de
lapplication. Dans le layout, linclusion se fait au moyen du helper
include_stylesheets().
Dans le fichier de configuration view.yml, la feuille de style rfrence par
dfaut est main.css, et non /css/main.css. En fait, les deux dfinitions
sont quivalentes puisque Symfony prfixe les chemins relatifs par /css/.
Si plusieurs fichiers sont dfinis, le framework les inclura tous dans
lordre de leur dclaration :
Lattribut media est aussi modifiable et le suffixe .css peut tre omis.
Cette configuration gnre le code HTML suivant :
Le fichier de configuration view.yml dfinit galement le layout de
lapplication. Par dfaut, le nom est layout, et donc Symfony dcore
chaque page avec le fichier layout.php. Le processus de dcoration peut
galement tre dsactiv en fixant lentre has_layout false.
Cela marche tel quel mais le fichier jobs.css est toujours ncessaire pour
la page daccueil tandis que le fichier job.css est seulement requis pour
metas:
#title: symfony project
#description: symfony project
#keywords: symfony, project
#language: en
#robots: index, follow
stylesheets: [main.css]
javascripts: []
has_layout: true
layout: layout
stylesheets: [main.css, jobs.css, job.css]
stylesheets: [main.css, jobs.css, job.css, print: { media:
print }]
<link rel="stylesheet" type="text/css" media="screen"
href="/css/main.css" />
<link rel="stylesheet" type="text/css" media="screen"
href="/css/jobs.css" />
<link rel="stylesheet" type="text/css" media="screen"
href="/css/job.css" />
<link rel="stylesheet" type="text/css" media="print"
href="/css/print.css" />
4


L
e

c
o
n
t
r

l
e
u
r

e
t

l
a

v
u
e
Groupe Eyrolles, 2008
61
la page de dtail dune offre. Le fichier de configuration view.yml est
personnalisable pour chaque module de base.
Le bout de code ci-dessous configure le fichier view.yml de lapplication
afin quil ne fasse appel quau fichier main.css.
Extrait du fichier apps/frontend/config/view.yml
Pour personnaliser la vue du module doffres demploi, il suffit de crer
un nouveau fichier view.yml dans le rpertoire apps/frontend/modules/
job/config/ :
Contenu du fichier apps/frontend/modules/job/config/view.yml
Les sections indexSuccess et showSuccess correspondent aux noms des
templates des actions index et show qui seront voques plus tard.
Chaque constante de configuration se trouvant sous la section all peut
tre redfinie dans les nouvelles sections cres. La section spciale all
permet de dfinir les paramtres de configuration partags par
lensemble des actions du module.
Configurer la vue laide des helpers de Symfony
En rgle gnrale, tout ce quil est possible de paramtrer dans un fichier
de configuration peut tre accompli de la mme manire avec du code
PHP. Par exemple, au lieu de crer un fichier view.yml spcifique au
module job, nous pouvons directement inclure une feuille de style
depuis un template laide du helper use_stylesheet() :
Utiliser ce helper dans le fichier layout.php pour charger des feuilles de
style revient inclure cette dernire de manire globale pour chaque
module de lapplication.
Choisir lune ou lautre des deux mthodes est en fait une simple ques-
tion de got. Le fichier view.yml fournit une manire de dfinir des
paramtres pour toutes les actions dun module, ce qui est impossible
dans un template. Par ailleurs, le fichier de configuration est totalement
statique. En revanche, avoir recours au helper use_stylesheet() est bien
stylesheets: [main.css]
indexSuccess:
stylesheets: [jobs.css]
showSuccess:
stylesheets: [job.css]
<?php use_stylesheet('main.css') ?>
IMPORTANT Principes de configuration
dans Symfony
Pour les diffrents fichiers de configuration de
Symfony, le mme paramtre peut tre modifi
plusieurs niveaux :
la configuration par dfaut dans le
framework ;
la configuration globale du projet (dans
config/) ;
la configuration locale dune application
(dans apps/APP/config/) ;
la configuration locale restreinte un
module (dans apps/APP/modules/
MODULE/config/).
En cours dexcution, le systme de configura-
tion fusionne toutes les valeurs des diffrents
fichiers sils existent, et met en cache le rsultat
pour de meilleures performances.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
62
plus flexible puisque chaque chose est sa place, la dfinition des feuilles
de style comme le code HTML.
Tout au long de cet ouvrage, cest le helper use_stylesheet() qui sera
utilis. Le fichier apps/frontend/modules/job/config/view.yml, qui
vient tout juste dtre ajout, peut finalement tre supprim au profit de
lemploi du helper use_stylesheet() dans les templates du module job.
Helper ajouter en haut du fichier apps/frontend/modules/job/templates/
indexSuccess.php
Helper ajouter en haut du fichier apps/frontend/modules/job/templates/
showSuccess.php
De la mme manire, la configuration des JavaScripts est ralise grce la
section javascripts du fichier de configuration view.yml ou bien grce
lappel du helper use_javascript() directement dans un template.
Gnrer la page daccueil des offres demploi
Comme il la t prsent au chapitre 3, la page daccueil des offres
demploi est gnre par laction index du module job. Laction index est
la partie Contrleur de la page, et le template associ, indexSuccess.php,
est la partie Vue. Le code ci-dessous rappelle la structure arborescente
du module job gnr pour lapplication frontend.
crire le contrleur de la page : laction index
Chaque action est reprsente par une mthode dune classe. Pour la
page daccueil du module job, la classe est jobActions (le nom du
module suffix par Actions) et la mthode est executeIndex() (execute
suffix par le nom de laction). Laction index rcupre toutes les offres
demploi de la base de donnes comme le montre le code ci-dessous.
<?php use_stylesheet('jobs.css') ?>
<?php use_stylesheet('job.css') ?>
apps/
frontend/
modules/
job/
actions/
actions.class.php
templates/
indexSuccess.php
4


L
e

c
o
n
t
r

l
e
u
r

e
t

l
a

v
u
e
Groupe Eyrolles, 2008
63
Contenu du fichier apps/frontend/modules/job/actions/actions.class.php
Il est temps dtudier de plus prs ces quelques lignes de code. La
mthode executeIndex() (le contrleur) appelle la table JobeetJob pour
crer une requte SQL qui rcupre toutes les offres demploi. Cette
dernire retourne un objet Doctrine_Collection une liste dobjets
JobeetJob qui est assign la proprit objet jobeet_job_list.
Toutes ces proprits dobjet sont ensuite automatiquement transmises
au template (la vue). En rsum, le passage dune variable du contrleur
la vue est simple : il suffit de dclarer une nouvelle proprit dans la
classe dactions.
Ce code cre les variables $foo et $bar accessibles dans le template.
Crer la vue associe laction : le template
Par dfaut, le nom du template associ une action est dduit par Sym-
fony grce une convention : le nom de laction suffixe par Success.
Le template indexSuccess.php gnre un tableau HTML pour toutes
les offres demploi. Le code actuel du template est prsent ci-dessous :
Contenu du fichier apps/frontend/modules/job/templates/indexSuccess.php
class jobActions extends sfActions
{
public function executeIndex(sfWebRequest $request)
{
$this->jobeet_job_list = Doctrine::getTable('JobeetJob')
->createQuery('a')
->execute();
}
// ...
}
public function executeFooBar(sfWebRequest $request)
{
$this->foo = 'bar';
$this->bar = array('bar', 'baz');
}
<?php use_stylesheet('jobs.css') ?>
<h1>Job List</h1>
<table>
<thead>
<tr>
<th>Id</th>
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
64
Dans le code du template, linstruction foreach itre travers la liste
dobjets JobeetJob ($jobeet_job_list) et, pour chaque offre demploi,
affiche la valeur des colonnes en sortie. Accder la valeur dune colonne
dune table est aussi simple quun appel une mthode accesseur dont le
nom commence par get, suivi du nom de la colonne en Camel Case
(par exemple, la mthode getCreatedAt() pour la colonne created_at).
Personnaliser les informations affiches pour chaque offre
Le code prcdent de la vue affiche toutes les informations de lobjet
JobeetJob. Nanmoins, toutes ne sont pas forcment pertinentes pour la
page daccueil du site Internet et cest pour cette raison que seuls la situa-
tion gographique, le nom de la socit et le type de poste seront affichs.
Contenu du fichier apps/frontend/modules/job/templates/indexSuccess.php
<th>Category</th>
<th>Type</th>
<!-- more columns here -->
<th>Created at</th>
<th>Updated at</th>
</tr>
</thead>
<tbody>
<?php foreach ($jobeet_job_list as $jobeet_job): ?>
<tr>
<td>
<a href="<?php echo url_for('job/show?id='.$jobeet_job-
>getId()) ?>">
<?php echo $jobeet_job->getId() ?>
</a>
</td>
<td><?php echo $jobeet_job->getCategoryId() ?></td>
<td><?php echo $jobeet_job->getType() ?></td>
<!-- more columns here -->
<td><?php echo $jobeet_job->getCreatedAt() ?></td>
<td><?php echo $jobeet_job->getUpdatedAt() ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<a href="<?php echo url_for('job/new') ?>">New</a>
<?php use_stylesheet('jobs.css') ?>
<div id="jobs">
<table class="jobs">
<?php foreach ($jobeet_job_list as $i => $job): ?>
<tr class="<?php echo fmod($i, 2) ? 'even' : 'odd' ?>">
<td class="location"><?php echo $job->getLocation() ?>
</td>
4


L
e

c
o
n
t
r

l
e
u
r

e
t

l
a

v
u
e
Groupe Eyrolles, 2008
65
La fonction url_for() appele dans les templates sera prsente au cha-
pitre suivant. En attendant, il sagit juste de retenir que ce helper permet
de gnrer des URLs internes ou externes.
<td class="position">
<a href="<?php echo url_for('job/show?id='.$job->getId()) ?>">
<?php echo $job->getPosition() ?>
</a>
</td>
<td class="company"><?php echo $job->getCompany() ?></td>
</tr>
<?php endforeach; ?>
</table>
</div>
Figure 45 Page de listing des offres demploi
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
66
Gnrer la page de dtail dune offre
Crer le template du dtail de loffre
prsent, il est temps de personnaliser le template qui affiche le dtail des
offres demploi. Pour ce faire, nous allons diter le fichier showSuccess.php
et remplacer son contenu actuel par le code prsent ci-aprs.
Contenu du fichier apps/frontend/modules/job/templates/showSuccess.php
<?php use_stylesheet('job.css') ?>
<?php use_helper('Text') ?>
<div id="job">
<h1> <?php echo $job->getCompany() ?></h1>
<h2> <?php echo $job->getLocation() ?></h2>
<h3>
<?php echo $job->getPosition() ?>
<small> - <?php echo $job->getType() ?></small>
</h3>
<?php if ($job->getLogo()): ?>
<div class="logo">
<a href="<?php echo $job->getUrl() ?>">
<img src="/uploads/jobs/<?php echo $job->getLogo() ?>"
alt="<?php echo $job->getCompany() ?> logo" />
</a>
</div>
<?php endif; ?>
<div class="description">
<?php echo simple_format_text($job->getDescription()) ?>
</div>
<h4>How to apply?</h4>
<p class="how_to_apply"><?php echo $job->getHowToApply() ?>
</p>
<div class="meta">
<small>posted on <?php echo date('m/d/Y',
X strtotime($job->getCreatedAt())) ?></small>
</div>
<div style="padding: 20px 0">
<a href="<?php echo url_for('job/edit?id='.$job->getId()) ?>">
Edit
</a>
</div>
</div>
4


L
e

c
o
n
t
r

l
e
u
r

e
t

l
a

v
u
e
Groupe Eyrolles, 2008
67
Mettre jour laction show
Ce template utilise la variable $job, transmise par laction, pour afficher
les informations dtailles dune offre demploi. La variable passe au
template a t renomme de $jobeet_job en $job ; ce mme change-
ment doit donc tre opr sur les deux occurrences de la variable pr-
sentes dans le corps de laction show.
Dtail de la mthode executeShow() du fichier apps/frontend/modules/job/actions/
actions.class.php
La description dune offre demploi est formate laide du helper
simple_format_text(). Celui-ci remplace les retours la ligne par des
balises <br/>. Ce helper appartient au groupe des helpers Text qui ne
sont pas chargs par dfaut par le framework. Lappel au helper
use_helper() permet de charger manuellement tous les helpers du
groupe Text et de les rendre disponibles dans le template.
public function executeShow(sfWebRequest $request)
{
$this->job = Doctrine::getTable('JobeetJob')-> find($request->getParameter('id'));
$this->forward404Unless($this->job);
}
Figure 46
Page de dtail dune offre demploi
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
68
Utiliser les emplacements pour modifier
dynamiquement le titre des pages
Pour le moment, le titre de toutes les pages est inscrit en dur dans le tag
<title> du layout :
Extrait du fichier apps/frontend/templates/layout.php
Bien videmment, il serait plus judicieux de dterminer un titre plus
explicite, compos du nom de la socit et de lintitul du poste, pour
chaque page dtaillant une offre demploi.
Dans Symfony, lorsquune zone du layout dpend du template afficher,
il devient ncessaire de dclarer un emplacement (slot).
Ajouter un slot au layout dans la balise <head> permet ainsi de dfinir un
titre dynamique pour chaque page.
Dfinition dun emplacement pour le titre
Extrait du fichier apps/frontend/templates/layout.php
Chaque emplacement se dfinit par un nom (ici title) et est affich au
moyen du helper include_slot().
Fixer la valeur dun slot dans un template
Une fois que le slot est dfini, sa valeur peut tre fixe depuis nimporte
quel template laide du helper slot(). Dans le cadre de cette applica-
tion, il sagit de dfinir la valeur du slot title afin de modifier dynami-
quement le titre de chaque page. Pour ce faire, il suffit de modifier le
fichier showSuccess.php en lui ajoutant le code ci-dessous.
<title>Jobeet - Your best job board</title>
Figure 47
Principe de fonctionnement
des slots dans Symfony
<title><?php include_slot('title') ?></title>
4


L
e

c
o
n
t
r

l
e
u
r

e
t

l
a

v
u
e
Groupe Eyrolles, 2008
69
Dfinition du titre de la page dans le fichier apps/frontend/modules/job/templates/
showSuccess.php
Fixer la valeur dun slot complexe dans un template
Si le titre est complexe gnrer, le helper slot() peut aussi tre
employ sous la forme dun bloc comme le montre le code ci-aprs.
Exemple de slot complexe dans le fichier apps/frontend/modules/job/templates/
showSuccess.php
Dclarer une valeur par dfaut pour le slot
Pour quelques pages comme la page daccueil, nous avons seulement
besoin dun titre gnrique. Au lieu de rpter le mme titre encore et
encore dans tous les templates, il est possible de dclarer un titre par
dfaut dans le layout :
Dfinition dun titre de page web par dfaut apps/frontend/templates/layout.php
Le helper include_slot() retourne true si le slot a bien t dfini. En
somme, lorsque le contenu du slot title est fix depuis un template, il
est utilis ; sinon cest le titre par dfaut qui est retenu.
<?php slot(
'title',
sprintf('%s is looking for a %s', $job->getCompany(), $job-
>getPosition()))
?>
<?php slot('title') ?>
<?php echo sprintf('%s is looking for a %s', $job-
>getCompany(), $job->getPosition()) ?>
<?php end_slot(); ?>
<title>
<?php if (!include_slot('title')): ?>
Jobeet - Your best job board
<?php endif; ?>
</title>
REMARQUE propos des helpers
Jusqu maintenant, un certain nombre de helpers
commenant par include_ ont t prsents.
Ces fonctions gnrent le code HTML et, dans la
plupart des cas, ont un helper associ get_ qui
retourne exclusivement le contenu :
<?php include_slot('title') ?>
<?php echo get_slot('title') ?>
<?php include_stylesheets() ?>
<?php echo get_stylesheets() ?>
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
70
Rediriger vers une page derreur 404
si loffre nexiste pas
La page dune offre demploi est gnre au moyen de laction show,
dclare dans la mthode executeShow() du module job :
Mthode executeShow() de la classe apps/frontend/modules/job/actions/
actions.class.php
De la mme manire que dans laction index, la classe de la table
JobeetJob permet de retrouver une offre demploi, cette fois-ci grce la
mthode find(). Le paramtre de cette mthode est lidentifiant unique
dune offre demploi, sa cl primaire. La section suivante expliquera
pourquoi le code $request->getParameter('id') retourne la cl pri-
maire de loffre demploi.
Si une offre demploi nexiste pas dans la base de donnes, lidal est de redi-
riger lutilisateur vers une page derreur 404. Pour ce faire, il suffit dutiliser la
mthode forward404Unless(). Cette dernire prend un boolen comme
premier argument et, sil nest pas true, arrte le flot dexcution en cours.
Les mthodes forward arrtent immdiatement lexcution de laction en
cours en lanant une exception sfError404Exception, ce qui explique quil
nest pas ncessaire de retourner de valeur ensuite.
Comme pour toutes les exceptions lances, la page affiche lutilisateur est
diffrente en fonction de lenvironnement (production ou dveloppement) :
class jobActions extends sfActions
{
public function executeShow(sfWebRequest $request)
{
$this->job = Doctrine::getTable('JobeetJob')
X ->find($request->getParameter('id'));
$this->forward404Unless($this->job);
}
// ...
}
PROPOS La famille
des mthodes de Forward
Lappel forward404Unless() est quiva-
lent :
$this->forward404If(!$this->job);
qui est similaire :
if (!$this->job)
{
$this->forward404();
}
La mthode forward404() elle-mme est sim-
plement un raccourci pour :
$this->forward('default', '404');
La mthode forward() redirige vers une autre
action de la mme application. Dans lexemple
prcdent, il sagit de laction 404 du module
default. Le module par dfaut est livr avec
Symfony et fournit les actions par dfaut pour
gnrer les pages 404, ainsi que les pages de con-
trle daccs et didentification.
Figure 48
Page derreur 404 en environnement
de dveloppement
4


L
e

c
o
n
t
r

l
e
u
r

e
t

l
a

v
u
e
Groupe Eyrolles, 2008
71
La personnalisation de la page derreur 404 sera prsente plus loin dans
cet ouvrage lorsquil sera temps de dployer lapplication sur le serveur
de production.
Comprendre linteraction client/serveur
Lorsque lon navigue sur les pages /job ou /job/show/id/1 dans un naviga-
teur, un aller-retour avec le serveur web est effectu. Le navigateur
envoie une requte et le serveur lui retourne une rponse.
Nous avons dj vu que Symfony encapsulait la requte dans un objet
sfWebRequest (vu dans la signature de la mthode executeShow()). Le
framework tant entirement orient objet, la rponse est elle aussi un
objet de la classe sfWebResponse. Laccs lobjet rponse depuis une
action se ralise au moyen de linstruction $this->getResponse().
Ces objets fournissent un certain nombre de mthodes adquates pour
accder aux informations issues des fonctions et des variables globales de
PHP.
Rcuprer le dtail de la requte envoye au serveur
La classe sfWebRequest encapsule les tableaux globaux $_SERVER,
$_COOKIE, $_GET, $_POST, et $_FILES de PHP.
Figure 49
Page derreur 404 en environnement
de production
Tableau 41 Liste des mthodes disponibles de lobjet sfWebRequest
Nom de la mthode Equivalent PHP
getMethod() $_SERVER['REQUEST_METHOD']
getUri() $_SERVER['REQUEST_URI']
getReferer() $_SERVER['HTTP_REFERER']
getHost() $_SERVER['HTTP_HOST']
getLanguages() $_SERVER['HTTP_ACCEPT_LANGUAGE']
getCharsets() $_SERVER['HTTP_ACCEPT_CHARSET']
CHOIX DE CONCEPTION Pourquoi Symfony
encapsule-t-il des fonctionnalits
existantes de PHP ?
Tout dabord parce que les mthodes du fra-
mework sont plus puissantes que leurs quiva-
lents en PHP. Ensuite, lorsque lon teste une
application, il devient beaucoup plus simple de
simuler un objet requte ou un objet rponse
que dessayer de bricoler un programme avec
des variables globales ou des fonctions PHP
comme header(), dont le comportement est
occult par le dveloppeur.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
72
Les paramtres de la requte ont dj t accds en utilisant la mthode
getParameter(). Celle-ci retourne une valeur en provenance de la
variable globale $_GET, $_POST, ou bien de la variable PATH_INFO. Si lon
souhaite sassurer quun paramtre provient bien dune de ces variables, il
faut alors avoir recours respectivement lune des mthodes
getGetParameter(), getPostParameter() et getUrlParameter().
Le framework Symfony introduit aussi la mthode isMethod() qui permet
de contrler la mthode HTTP utilise ou de servir restreindre une
action une mthode spcifique. Cest exactement ce quil se passe lorsque
lon manipule des formulaires. Grce la mthode isMethod(), on peut
ainsi sassurer directement dans laction que la requte a t correctement
transmise laide de la mthode POST : $this->forwardUnless($request-
> isMethod('POST'));
Rcuprer le dtail de la rponse envoye au client
La classe sfWebResponse() encapsule les fonctions PHP header() et
setrawcookie().
isXmlHttpRequest() $_SERVER['X_REQUESTED_WITH']
== 'XMLHttpRequest'
getHttpHeader() $_SERVER
getCookie() $_COOKIE
isSecure() $_SERVER['HTTPS']
getFiles() $_FILES
getGetParameter() $_GET
getPostParameter() $_POST
getUrlParameter() $_SERVER['PATH_INFO']
getRemoteAddress() $_SERVER['REMOTE_ADDR']
Tableau 42 Liste des mthodes disponibles de lobjet sfWebResponse
Nom de la mthode Equivalent PHP
setCookie() setrawcookie()
setStatusCode() header()
setHttpHeader() header()
setContentType() header()
addVaryHttpHeader() header()
addCacheControlHttpHeader() header()
Tableau 41 Liste des mthodes disponibles de lobjet sfWebRequest (suite)
Nom de la mthode Equivalent PHP
4


L
e

c
o
n
t
r

l
e
u
r

e
t

l
a

v
u
e
Groupe Eyrolles, 2008
73
Bien sr, la classe sfWebResponse fournit galement une manire de fixer
le contenu de la rponse (setContent()) et denvoyer cette dernire au
navigateur (send()).
Plus haut dans ce chapitre, nous avons dcouvert comment grer les
feuilles de style et les JavaScripts aussi bien dans le fichier view.yml que
dans les templates. Au final, les deux techniques sappuient sur les
mthodes addStyleSheet() et addJavascript() de lobjet rponse.
Les classes sfAction, sfRequest et sfReponse fournissent un lot dautres
mthodes pratiques. Nhsitez pas parcourir la documentation de
lAPI (http://www.symfony-project.org/api/1_2/) pour en savoir plus au sujet
des classes internes de Symfony.
En rsum
Ce chapitre a permis de prsenter deux motifs de conception intgrs
dans Symfony pour rpondre aux besoins dorganisation du code.
Larborescence des fichiers du projet prend maintenant tout son sens
puisque chaque chose est idalement range sa place. Dans la foule,
nous en avons profit pour apprivoiser la vue en manipulant le layout et
les fichiers de templates. Certains dentre eux ont dailleurs t rendus
dynamiques par lintermdiaire des slots et des actions.
Le chapitre suivant prsente le sous-framework de routage. Ce sera ainsi
loccasion den savoir un peu plus propos du helper url_for() qui a t
vaguement aperu et mis en uvre au cours de ce chapitre.
Groupe Eyrolles, 2008
chapitre 5
Groupe Eyrolles, 2008
Le routage
Toute ressource disponible sur un site web est identifie
au moyen dune adresse Internet unique qui permet
de latteindre. Ces URL jouent un rle dterminant
dans la smantique et le rfrencement du site car elles
apportent des informations utiles sur la ressource identifie,
et cest pour cette raison quil est important de rflchir
la manire dont elles seront implmentes.
Le framework Symfony intgre parfaitement un mcanisme
interne puissant de gestion des URLs propres .
MOTS-CLS :
BRoutage
BURL propres
BObjets sfRoute
et sfDoctrineRoute
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
76
Le chapitre prcdent a permis de dcouvrir et de se familiariser avec
larchitecture MVC qui devient avec la pratique une manire de coder de
plus en plus naturelle. Cest en sexerant davantage avec cette mthode
de conception que lon saperoit quel point il est dlicat de sorganiser
autrement Le chapitre 3 a galement permis de sentraner un peu
plus en personnalisant certaines pages de Jobeet, mais aussi de revoir
plusieurs concepts importants de Symfony comme le layout, les helpers
ou encore les slots. prsent, il est temps de sintresser un autre outil
indispensable de Symfony : le framework de routage.
la dcouverte du framework de routage
de Symfony
Rappels sur la notion dURL
En cliquant sur la page dtaille dune offre demploi, lURL ressemble
/jobs/show/id/1. Pourtant, les dveloppeurs dapplications web PHP
traditionnelles sont gnralement plus familiers des URLs paramtres
telles que /job.php?id=1. Comment Symfony est-il capable de se com-
porter de la sorte avec les URLs ? Comment le framework dtermine-t-
il laction excuter daprs cette URL ? Pourquoi lidentifiant dune
offre demploi est-il rcupr avec $request->getParameter('id') ? Ce
sont toutes les questions auxquelles rpond ce cinquime chapitre. Pour
commencer, il est important de rappeler ce quest une URL dans le con-
texte du Web en gnral, et quel rle elle joue exactement.
Quest-ce quune URL ?
Dans le contexte du Web, une URL est lidentifiant unique dune res-
source accessible depuis un navigateur web (une page HTML, une
image, un fichier texte, une vido). Schmatiquement, lorsquun utili-
sateur saisit une adresse Internet dans son navigateur, il demande ce
dernier de lui rcuprer la ressource distante identifie par cette URL.
Par consquent, une URL se comporte comme une interface entre le site
Internet et lutilisateur, et peut ainsi vhiculer des informations utiles au
sujet de la ressource quelle rfrence.
Or, un problme se pose avec les URLs paramtres traditionnelles
puisquelles ne dcrivent pas vritablement la ressource distante et expo-
sent, par la mme occasion, limplmentation technique interne de
lapplication. Lutilisateur se moque perdument de savoir que le site
Internet quil consulte est dvelopp avec le langage PHP, ou bien que
5


L
e

r
o
u
t
a
g
e
Groupe Eyrolles, 2008
77
chaque offre demploi est identifie par un numro unique dans la base
de donnes. La seule chose qui lintresse, cest daccder la ressource
quil dsire grce cette URL.
Exposer les implmentations techniques internes de lapplication se rvle
par ailleurs particulirement dangereux pour la scurit de celle-ci. En
effet, quels seraient les ventuels dgts provoqus si un utilisateur mal-
veillant arrivait deviner lURL de ressources auxquelles il na pas le droit
daccder ? Bien videmment, cest le rle du dveloppeur de scuriser de
la meilleure manire possible son application, mais il reste toujours prf-
rable de limiter les risques en cachant les informations sensibles.
Introduction gnrale au framework interne de routage
Au final, les adresses Internet sont si importantes dans Symfony quun
framework entier leur est consacr : le framework de routage. Le sys-
tme de routage gre la fois les URLs internes et externes. Lorsquune
requte entrante parvient au contrleur frontal de lapplication, celui-ci
dlgue le travail danalyse de lURL au framework interne de routage
qui se charge de la convertir en une requte interne comme celle de la
page dune offre demploi, dans le template showSuccess.php.
Le helper url_for() convertit une URL interne en une URL propre :
Une URL interne par dfaut est compose de plusieurs parties : tout
dabord le module job, suivi de laction show et enfin de la chane de
requte qui contient les paramtres passer laction. Le motif gn-
rique dune URL interne suit le format suivant :
Le framework de routage de Symfony est bidirectionnel, ce qui signifie
que toutes les URLs peuvent tre modifies sans avoir changer leur
implmentation technique. Cest lun des principaux avantages du motif
de conception Front Controller.
Configuration du routage : le fichier routing.yml
Dcouverte de la configuration par dfaut du routage
La configuration de toutes les URLs dune application Symfony se situe
dans un seul et mme fichier de configuration YAML : le fichier
'job/show?id='.$job->getId()
/job/show/id/1
MODULE/ACTION?key=value&key_1=value_1&...
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
78
routing.yml. Celui-ci permet en effet de dfinir toute la carte des URLs
internes de lapplication de manire trs simple. Cest ce qui fait aussi
que le framework de routage est bidirectionnel puisque la modification
de la configuration dune URL dans ce fichier nimpactera pas son
implmentation technique dans les templates ou dans les actions du
projet. Le code ci-dessous dcrit le contenu par dfaut de ce fichier. Il
sagit de la dclaration des routes des trois motifs dURLs ncessaires au
fonctionnement de base du framework.
Configuration par dfaut du routage dans le fichier apps/frontend/config/
routing.yml
Le fichier routing.yml dcrit toutes les routes de lapplication laide
dune syntaxe YAML particulirement simple. En effet, une route se
dclare au minimum avec trois paramtres. Le premier dentre eux est
tout dabord le nom donn la route (homepage) afin dy faire rfrence
dans les actions et les templates lors de son implmentation technique.
Le deuxime paramtre (url) dtermine bien videmment le motif que
doit prendre lURL dans son implmentation finale. Par exemple, le
motif /:module/:action/* indique que ladresse Internet est compose
dune barre oblique, suivie dune valeur pour la variable module, elle
mme suivie dune barre oblique et dune valeur pour la variable action,
et enfin dune dernire barre oblique suivie dun caractre toile qui
indique au framework de routage que la route accepte une srie de para-
mtres supplmentaires facultatifs.
Enfin, le dernier paramtre (param) est un tableau associatif dans lequel
sont dclares les valeurs par dfaut de certaines variables propres
lURL. Par exemple, la route homepage force le framework de routage
excuter laction index du module default en guise de page daccueil.
Les sections qui suivent abordent plus en dtail tous ces aspects de con-
figuration des routes de lapplication.
homepage:
url: /
param: { module: default, action: index }
default_index:
url: /:module
param: { action: index }
default:
url: /:module/:action/*
5


L
e

r
o
u
t
a
g
e
Groupe Eyrolles, 2008
79
Comprendre le fonctionnement des URL par dfaut de Symfony
Lorsquune requte arrive sur le contrleur frontal, le systme de routage
tente de lui faire correspondre un motif dURL. La premire route iden-
tifie lemporte, ce qui signifie que lordre de dclaration des routes dans
le fichier routing.yml est important. Voici quelques exemples pour
mieux comprendre comment cela fonctionne.
Quand un utilisateur demande la page daccueil des offres demploi,
dont lURL est /job, la premire route qui correspond ce motif est en
effet default_index. Dans un motif, un mot prfix par deux points : est
en fait une variable. Par consquent, le motif /:module signifie : trouve
un / suivi par quelque chose qui sera ensuite stock dans la variable
module . Dans cet exemple, la variable module prend pour valeur job et
peut ensuite tre retrouve dans laction grce linstruction $request-
>getParameter('module'). Cette route dfinit aussi une valeur par
dfaut pour la variable action. Ainsi, pour toutes les URLs identifiables
avec cette route, la requte disposera dun paramtre action dont la
valeur est index.
Si la page /job/show/id/1 est demande, Symfony fera correspondre son
URL au dernier motif : /:module/:action/*. Dans un motif, ltoile *
indique une collection de paires variable/valeur spares par des barres
obliques /.
LURL /job/show/id/1 peut tre cre depuis un template en ayant
recours lappel au helper url_for() suivant :
Le mme rsultat est galement possible partir du nom de la route pr-
fix par un arobase @ :
Les deux appels sont quivalents, mais le dernier est nettement plus per-
formant. En effet, le framework de routage na pas besoin danalyser
chaque route pour dterminer celle qui correspond le mieux. De plus, il
est moins li limplmentation du nom du module et de laction
puisque leur valeur respective est absente de lURL interne.
Tableau 51 Liste des paramtres et valeurs de lURL de la page dune offre demploi
Paramtre Valeur
module job
action show
id 1
url_for('job/show?id='.$job->getId())
url_for('@default?id='.$job->getId())
REMARQUE
Les variables spciales module et action
Les variables module et action sont spciales
puisquelles sont utilises par Symfony pour dter-
miner laction excuter.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
80
Personnaliser les routes de lapplication
Configurer la route de la page daccueil
Pour linstant, la page daccueil de lapplication Jobeet est toujours celle
par dfaut de flicitations de Symfony. Cest en effet parce que ladresse
Internet interne / correspond la route homepage dfinit dans le fichier
de configuration routing.yml. Il faut donc remplacer cette page daccueil
par dfaut au profit de celle de Jobeet. Pour ce faire, il suffit dditer la
configuration initiale de la route homepage en remplaant la valeur de la
variable module par job.
Configuration de la page daccueil de Jobeet dans le fichier apps/frontend/config/
routing.yml
Ceci tant fait, le lien figurant sur le logo de chaque page de Jobeet peut
galement tre dit afin dappliquer lURL vers la page daccueil de
lapplication.
Dfinition du lien vers la page daccueil dans le fichier apps/frontend/templates/
layout.php
Configurer la route daccs au dtail dune offre
La modification de la configuration dune URL nimplique que de
changer certains paramtres dans le fichier de configuration
routing.yml. Lobjectif prsent est daller de lavant en transformant le
motif de lURL qui mne au dtail dune annonce, afin que cette adresse
embarque davantage dinformations utiles comme le nom de la socit,
la ville ou bien encore le type de poste propos. Cela permet la fois de
deviner par avance quoi sattendre en atteignant cette URL, mais aussi
doptimiser le rfrencement du site auprs des moteurs de recherche qui
indexent les mots-cls quils trouvent dans les adresses Internet. Le nou-
veau motif des URLs de chaque offre correspondra celui ci-dessous :
homepage:
url: /
param: { module: job, action: index }
<h1>
<a href="<?php echo url_for('@homepage') ?>">
<img src="/images/jobeet.gif" alt="Jobeet Job Board" />
</a>
</h1>
/job/sensio-labs/paris-france/1/web-developer
5


L
e

r
o
u
t
a
g
e
Groupe Eyrolles, 2008
81
Grce ce motif, lutilisateur ne connaissant absolument rien de Jobeet
est capable de comprendre que la socit parisienne Sensio Labs est la
recherche dun nouveau dveloppeur web. Cette URL est certes plus
longue que celle par dfaut mais elle a lavantage dtre bien plus perti-
nente et smantique.
Pour y parvenir, il faut bien entendu modifier le contenu du fichier de
configuration routing.yml afin de lui ajouter une route supplmentaire
job_show_user dont la configuration est donne dans le code ci-aprs.
Le motif de cette route fait tat de quatre variables spares les unes des
autres par des barres obliques. Les variables company, location, id et
position reprsentent respectivement le nom de la socit, le lieu,
lidentifiant unique et le type de poste propos pour loffre courante.
En rafrachissant de nouveau la page daccueil, les liens vers les pages
respectives des offres demploi nont pas chang. Cest en effet normal
tant donn que la route accepte prsent des paramtres obligatoires
qui doivent tre transmis au helper url_for() afin quil gnre lURL
adquate. Par consquent, la dfinition du helper url_for() dans le tem-
plate indexSuccess.php du module job doit tre modifie de la manire
suivante.
Il est galement possible dexprimer une URL interne laide dun
tableau associatif.
/job/:company/:location/:id/:position
job_show_user:
url: /job/:company/:location/:id/:position
param: { module: job, action: show }
url_for('job/show?id='.$job->getId().'&company='.$job-
>getCompany().
'&location='.$job->getLocation().'&position='.$job-
>getPosition())
url_for(array(
'module' => 'job',
'action' => 'show',
'id' => $job->getId(),
'company' => $job->getCompany(),
'location' => $job->getLocation(),
'position' => $job->getPosition(),
));
REMARQUE De lutilit des URLs propres
Les URLs propres et bien formes sont importantes
car elles vhiculent des informations lutilisateur.
Cest aussi particulirement pratique lorsque lon
copie/colle lURL dans un e-mail ou lorsquil sagit
doptimiser un site Internet pour les moteurs de
recherche qui se servent des mots-cls prsents
dans les URLs.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
82
Forcer la validation des paramtres des URLs internes
de lapplication
Pour des raisons videntes de scurit et daide au dbogage, le premier
chapitre a mis en vidence les notions de validation et de gestion des
erreurs. Le systme de routage nchappe pas non plus cette rgle
puisquil possde une fonctionnalit native de validation des paramtres
des URLs internes. La valeur de chaque variable dune adresse interne
ayant un format propre peut tre valide au moyen dune expression
rgulire, dfinie par lintermdiaire de la section requirements de la
configuration dune route.
La section requirements ci-dessus force la valeur de la variable id tre
une valeur numrique entire strictement positive. Si ce nest pas le cas,
la route ne correspondra pas au motif.
Limiter une requte certaines mthodes HTTP
Chaque route configure dans le fichier routing.yml est convertie en
interne sous la forme dun objet de la classe sfRoute. Il arrive parfois
quil faille crire des routes plus complexes se comportant diffremment
des routes traditionnelles. Par consquent, la classe sfRoute doit tre
remplace par une classe plus spcifique.
Lentre class de la configuration des routes du fichier routing.yml
permet au dveloppeur de modifier le nom de la classe utiliser pour
contrler les comportements de lURL interne courante. Par exemple, le
lecteur familier du protocole HTTP sait que celui-ci accepte les
mthodes GET, POST, HEAD, DELETE ou encore PUT, bien que les navigateurs
web ne supportent que les trois premires. Par consquent, il mesure
tout lintrt de pouvoir limiter lutilisation dune requte HTTP pour
une ou plusieurs de ces mthodes.
En remplaant la classe sfRoute par la classe sfRequestRoute et en ajou-
tant une contrainte la variable virtuelle sf_method, le framework
interne de routage force la route nemployer que certaines mthodes de
requtes HTTP. Comme les mthodes HEAD et PUT ne sont pas suppor-
tes par les navigateurs web modernes, le framework Symfony est
job_show_user:
url: /job/:company/:location/:id/:position
param: { module: job, action: show }
requirements:
id: \d+
REMARQUE La mthode
sfWebRequest::isMethod() vs framework
de routage
Forcer une route correspondre certaines
requtes HTTP nest pas entirement quivalent
utiliser sfWebRequest::isMethod() dans
les actions. En effet, dans le premier cas, le routage
continuera de chercher une route correspondante si
la mthode ne correspond pas celle attendue.
5


L
e

r
o
u
t
a
g
e
Groupe Eyrolles, 2008
83
capable dmuler leur comportement grce notamment lutilisation de
cette variable spciale sf_method.
Optimiser la cration de routes grce la classe de
route dobjets Doctrine
La nouvelle URL interne pour les offres demploi est particulirement
longue et fastidieuse crire puisquil est ncessaire de passer lintgralit
des paramtres obligatoires la route par le biais du helper url_for().
Hormis le fait quelle soit contraignante crire, elle dispose dun second
inconvnient tout aussi ennuyeux. En effet, limplmentation technique
de la route et sa dclaration dans le fichier de configuration routing.yml
sont fortement couples, ce qui implique que des modifications dans le
paramtrage de lURL affecteront aussi limplmentation technique
dans les templates et les actions. Par exemple, si un nouveau paramtre
est ajout au motif de lURL, alors il faudra penser modifier tous les
templates et les actions pour passer la valeur de ce dernier. Ce nest ni
pratique ni intressant pour gagner du temps.
Transformer la route dune offre en route Doctrine
Lidal est donc de se tourner vers une approche diffrente de la cration
dURLs complexes. La section prcdente a montr comment il tait
simple de modifier la manire dont est gre une route en modifiant le
nom de sa classe associe. De ce fait, il convient davoir recours la
classe sfDoctrineRoute pour manipuler la route job_show_user, dans la
mesure o cette classe est optimise pour reprsenter nimporte quel
objet Doctrine ou nimporte quelle collection dobjets Doctrine.
job_show_user:
url: /job/:company/:location/:id/:position
class: sfRequestRoute
param: { module: job, action: show }
requirements:
id: \d+
sf_method: [get]
job_show_user:
url: /job/:company/:location/:id/:position
class: sfDoctrineRoute
options: { model: JobeetJob, type: object }
param: { module: job, action: show }
requirements:
id: \d+
sf_method: [get]
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
84
La section options personnalise le comportement de la route. Ici,
loption model indique la classe de modle (JobeetJob) relative la route,
tandis que loption type prcise que la route est lie un objet. Si la
valeur de loption type est list alors la route se rapportera une collec-
tion dobjets.
La route job_show_user est maintenant informe de sa relation avec la
classe de modle JobeetJob, ce qui permet de simplifier lappel
url_for() par :
Ou encore tout simplement :
Amliorer le format des URL des offres demploi
Limplmentation de la route Doctrine prcdente fonctionne telle
quelle car toutes les variables qui se trouvent dans le motif de lURL dis-
posent en ralit dun accesseur correspondant dans la classe JobeetJob.
Par exemple, la valeur de la variable company est rendue automatique-
ment lappel implicite la mthode getCompany(). Toutefois, les URLs
gnres pour certaines offres ne sont pas vritablement celles dsires
puisque en effet certaines donnes comme la localisation ou bien le type
de poste contiennent des caractres non standards pour une URL.
Il apparat donc ncessaire de transformer la vole les valeurs de ces
colonnes en remplaant tous les caractres non ASCII par des tirets -
afin dobtenir ce quon appelle des slugs dans le jargon informatique.
Nayant pas de vritable traduction courte en franais, le terme slug sera
employ tout au long de cet ouvrage pour dsigner une chane optimise
pour une URL. Pour ce faire, de nouvelles mthodes doivent tre ajou-
tes la classe JobeetJob.
Mthodes ajouter la classe JobeetJob dans le fichier lib/model/doctrine/
JobeetJob.class.php
url_for(array('sf_route' => 'job_show_user', 'sf_subject' =>
$job))
url_for('job_show_user', $job)
http://jobeet.localhost/frontend_dev.php/job/Sensio+Labs/
Paris%2C+France/1/Web+Developer
public function getCompanySlug()
{
return Jobeet::slugify($this->getCompany());
}
ASTUCE Choisir le bon appel url_for()
Le premier des deux appels url_for() pr-
sents ici est plus pratique lorsquil sagit de trans-
mettre des paramtres supplmentaires autres que
lobjet relatif la route Doctrine lui-mme.
REMARQUE
Quest-ce que la cration de slugs ?
Laction de ralisation dun slug partir dune
chane de dpart consiste transformer cette der-
nire en vue de la rutiliser dans une URL comme
identifiant dune ressource ou seulement comme
mots-cls supplmentaires pour vhiculer de
linformation au sujet de la ressource identifie.
Cette technique apporte de nombreux avantages
pour une URL car elle permet la fois de rendre
cette dernire plus claire, plus lisible mais surtout
plus smantique pour lutilisateur. Dautre part, les
mots-cls quelle vhicule participent loptimisa-
tion de lindexation du site Internet auprs des
moteurs de recherche.
5


L
e

r
o
u
t
a
g
e
Groupe Eyrolles, 2008
85
Ces trois mthodes font appel une nouvelle classe Jobeet dans laquelle
se trouve une mthode statique slugify(). Cest cette mthode qui se
charge de transformer une chane de caractres dorigine sous la forme
dune chane simplifie et optimise pour une URL. Il convient donc de
crer le fichier lib/Jobeet.class.php dans lequel est implmente cette
nouvelle classe Jobeet.
Implmentation de la classe Jobeet dans le fichier lib/Jobeet.class.php
La dernire tape consiste remanier la dfinition de la route
job_show_user afin que celle-ci fasse dsormais usage des trois nouveaux
accesseurs virtuels de la classe JobeetJob la place des trois accesseurs
actuels. La modification de la route consiste en fait uniquement
changer les noms des paramtres dans le motif de lURL sans avoir
modifier quoi que ce soit dans le template.
dition de la route job_show_user dans le fichier de configuration apps/frontend/
config/routing.yml
public function getPositionSlug()
{
return Jobeet::slugify($this->getPosition());
}
public function getLocationSlug()
{
return Jobeet::slugify($this->getLocation());
}
<?php
class Jobeet
{
static public function slugify($text)
{
// replace all non letters or digits by -
$text = preg_replace('/\W+/', '-', $text);
// trim and lowercase
$text = strtolower(trim($text, '-'));
return $text;
}
}
job_show_user:
url: /job/:company_slug/:location_slug/:id/:position_slug
class: sfDoctrineRoute
options: { model: JobeetJob, type: object }
param: { module: job, action: show }
REMARQUE
Suppression des balises PHP <?php
Tout au long de cet ouvrage, les balises douver-
ture de script PHP <?php seront volontairement
omises dans les morceaux de code prsents. Cest
en fait tout simplement par conomie de place et
pour rendre le code plus agrable lire en retirant
le bruit que gnrent ces balises. Toutefois,
elles sont obligatoires lexcution des scripts PHP
par le serveur. Il faut donc prendre garde ne pas
les oublier lors de la rimplmentation des codes
prsents.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
86
Il ne reste plus qu vider le cache de Symfony (commande symfony cc),
tant donn quune nouvelle classe Jobeet a t ajoute au projet, afin de
pouvoir constater la transformation des chanes de caractres passes
dans les URLs des offres demploi.
Retrouver lobjet grce sa route depuis une action
La puissance du framework de routage se trouve encore au-del des con-
cepts tudis jusqu prsent. En effet, la route est capable de gnrer
une URL base sur un objet, mais aussi de rcuprer celui-ci grce
lobjet sfDoctrineRoute. Lobjet li peut alors tre retrouv en utilisant la
mthode getObject() de lobjet de la route Doctrine. Lorsque le sys-
tme de routage analyse une requte entrante, il garde en mmoire
lobjet de la route correspondante afin de lutiliser dans les actions. Ainsi,
la mthode executeShow() est dsormais en mesure de retrouver lobjet
JobeetJob grce lobjet de la route Doctrine.
Extrait de la mthode executeShow() dans le fichier apps/frontend/modules/job/
actions/actions.class.php
Lappel la mthode getObject() de lobjet de la route Doctrine lance
une exception si lobjet Doctrine li nexiste pas, ce qui provoque une
page derreur 404 dont le message derreur est diffrent de celui renvoy
habituellement en environnement de dveloppement comme le montre
la capture dcran un peu plus bas. Par consquent, le corps de la
mthode executeShow() peut tre simplifi puisquil nest plus ncessaire
de grer soi-mme la redirection vers une page derreur 404 lorsque
aucun objet nest renvoy.
requirements:
id: \d+
sf_method: [get]
http://jobeet.localhost/frontend_dev.php/job/sensio-labs/paris-
france/1/web-developer
class jobActions extends sfActions
{
public function executeShow(sfWebRequest $request)
{
$this->job = $this->getRoute()->getObject();
$this->forward404Unless($this->job);
}
// ...
}
5


L
e

r
o
u
t
a
g
e
Groupe Eyrolles, 2008
87
Simplification de la mthode executeShow() dans le fichier apps/frontend/modules/
job/actions/actions.class.php
Faire appel au routage depuis les actions et les templates
Le routage dans les templates
Dans un template, le helper url_for() convertit une URL interne en
une URL externe. Dautres helpers Symfony prennent aussi une URL
interne comme argument, comme le helper link_to() qui gnre une
balise HTML <a>.
La portion de code ci-dessus gnre le code HTML suivant :
Les deux helpers url_for() et link_to() peuvent produire des URLs
absolues si on leur fournit un nouveau paramtre boolen facultatif.
class jobActions extends sfActions
{
public function executeShow(sfWebRequest $request)
{
$this->job = $this->getRoute()->getObject();
}
// ...
}
Figure 51 Page derreur 404 dune route Doctrine en environnement de dveloppement
<?php echo link_to($job->getPosition(), 'job_show_user', $job) ?>
<a href="/job/sensio-labs/paris-france/1/web-developer">Web
Developer</a>
url_for('job_show_user', $job, true);
link_to($job->getPosition(), 'job_show_user', $job, true);
ASTUCE Dsactiver la leve des exceptions
de la mthode getObject()
Pour viter que la mthode getObject() ne
lve une erreur 404, loption allow_empty
peut tre dfinie la valeur true dans la configu-
ration de la route.
REMARQUE Chargement la demande
de lobjet Doctrine dune route
Lobjet li la route est charg la demande,
cest--dire quil est rcupr de la base de don-
nes uniquement lorsque la mthode
getRoute() est appele.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
88
Le routage dans les actions
Le routage trouve aussi sa place dans les actions lorsquil sagit par
exemple deffectuer une redirection automatique vers une page de
lapplication aprs que lutilisateur a ralis une opration, comme le
remplissage dun formulaire. Les redirections sont rendues possibles
depuis les actions par le biais de la mthode redirect() tandis que la
gnration des URLs spcifiques est ralise grce la mthode
generateUrl().
Dcouvrir la classe de collection de routes
sfDoctrineRouteCollection
La route de laction show du module doffres demploi a dj t person-
nalise, mais les URLs des autres mthodes (index, new, edit, create,
updated et delete) sont toujours gres par la route par dfaut.
La route par dfaut est un moyen trs pratique de commencer coder
sans dfinir trop de routes. Mais ds lors que la route agit comme un
catch-all (littralement fourre-tout en franais), elle ne peut plus tre con-
figure pour des besoins spcifiques.
Dclarer une nouvelle collection de routes Doctrine
Comme toutes les actions dune offre demploi sont relatives la classe
de modle JobeetJob, il est possible de dfinir une route
sfDoctrineRoute personnalise pour chacune delles au mme titre que
celle mise en uvre avec laction show. Or, le module doffres demploi
dfinit sept actions de base possibles pour le modle. Il est donc prf-
rable davoir recours la classe sfDoctrineRouteCollection. Lutilisation
de cette classe ncessite de modifier le fichier routing.yml comme suit.
$this->redirect($this->generateUrl('job_show_user', $job));
ASTUCE La famille des mthodes de redirection
Dans le prcdent chapitre, il tait question des mthodes de forward. Ces mthodes trans-
mettent la requte en cours une autre action sans raliser dchange avec le serveur.
Les mthodes de redirect redirigent lutilisateur vers une autre URL. Comme pour forward, il
est possible dutiliser redirect(), ou ses raccourcis redirectIf() et
redirectUnless().
default:
url: /:module/:action/*
5


L
e

r
o
u
t
a
g
e
Groupe Eyrolles, 2008
89
Configuration dune collection de routes pour lobjet JobeetJob dans le fichier apps/
frontend/config/routing.yml
La route job ci-dessus nest quun simple raccourci qui gnre automati-
quement les sept routes sfDoctrineRoute suivantes :
job:
class: sfDoctrineRouteCollection
options: { model: JobeetJob }
job_show_user:
url: /job/:company_slug/:location_slug/:id/:position_slug
class: sfDoctrineRoute
options: { model: JobeetJob, type: object }
param: { module: job, action: show }
requirements:
id: \d+
sf_method: [get]
# default rules
homepage:
url: /
param: { module: job, action: index }
default_index:
url: /:module
param: { action: index }
default:
url: /:module/:action/*
job:
url: /job.:sf_format
class: sfDoctrineRoute
options: { model: JobeetJob, type: list }
param: { module: job, action: index, sf_format: html }
requirements: { sf_method: get }
job_new:
url: /job/new.:sf_format
class: sfDoctrineRoute
options: { model: JobeetJob, type: object }
param: { module: job, action: new, sf_format: html }
requirements: { sf_method: get }
job_create:
url: /job.:sf_format
class: sfDoctrineRoute
options: { model: JobeetJob, type: object }
param: { module: job, action: create, sf_format: html }
requirements: { sf_method: post }
job_edit:
url: /job/:id/edit.:sf_format
REMARQUE Routes identiques dans une
collection de routes Doctrine
Certaines routes gnres par la classe
sfDoctrineRouteCollection ont la
mme URL. Le framework de routage est en fait
capable de les utiliser car elles possdent toutes
diffrentes mthodes HTTP obligatoires.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
90
muler les mthodes PUT et DELETE
Les routes job_delete et job_update ncessitent lutilisation des
mthodes HTTP DELETE et PUT qui ne sont pas supportes par les navi-
gateurs web. Nanmoins, ces URLs fonctionnent quand mme tant
donn que Symfony arrive les simuler laide de la variable spciale
sf_method. Le template _form.php donne un exemple dimplmentation
de ce mcanisme.
Extrait du fichier apps/frontend/modules/job/templates/_form.php
Tous les helpers de Symfony sont capables dmuler nimporte quelle
mthode HTTP lorquon leur passe le paramtre spcial sf_method. Le
framework possde dautres paramtres particuliers comme sf_method,
qui dbutent tous par le prfixe sf_. Les routes gnres plus haut dans
class: sfDoctrineRoute
options: { model: JobeetJob, type: object }
param: { module: job, action: edit, sf_format: html }
requirements: { sf_method: get }
job_update:
url: /job/:id.:sf_format
class: sfDoctrineRoute
options: { model: JobeetJob, type: object }
param: { module: job, action: update, sf_format: html }
requirements: { sf_method: put }
job_delete:
url: /job/:id.:sf_format
class: sfDoctrineRoute
options: { model: JobeetJob, type: object }
param: { module: job, action: delete, sf_format: html }
requirements: { sf_method: delete }
job_show:
url: /job/:id.:sf_format
class: sfDoctrineRoute
options: { model: JobeetJob, type: object }
param: { module: job, action: show, sf_format: html }
requirements: { sf_method: get }
<form action="..." ...>
<?php if (!$form->getObject()->isNew()): ?>
<input type="hidden" name="sf_method" value="PUT" />
<?php endif; ?>
<?php echo link_to(
'Delete',
'job/delete?id='.$form->getObject()->getId(),
array('method' => 'delete', 'confirm' => 'Are you sure?')
) ?>
5


L
e

r
o
u
t
a
g
e
Groupe Eyrolles, 2008
91
cette section possdent toutes un sf_format qui sera prsent
loccasion dun chapitre consacr aux services web. Le chapitre ddi
linternationalisation et la localisation de Jobeet fera quant lui usage de
la variable spciale sf_culture.
Outils et bonnes pratiques lis au routage
Faciliter le dbogage en listant les routes de lapplication
Plus lapplication dveloppe grandit et plus le nombre de routes dclares
crot, ce qui signifie aussi quil devient de plus en plus difficile de sy
retrouver entre les diffrentes URLs. Cest dautant plus vrai lorsque
lapplication connecte des collections de routes Doctrine. En effet, les col-
lections de routes Doctrine sont dfinies globalement dans le fichier de
configuration routing.yml, mais la configuration des routes Doctrine uni-
taires quelles renferment nest pas visible au travers de ce fichier. Par con-
squent, le dbogage dune route particulire de lapplication peut se
rvler plus complexe. Heureusement, le framework Symfony intgre une
tche automatique app:routes qui permet de connatre lintgralit des
routes connectes lapplication comme en tmoigne le rsultat ci-aprs.
Lexcution de cette commande produit un rsultat comparable celui
en dessous. Cette liste donne pour chaque route connecte son nom, la
mthode qui restreint son accs, et bien sr son motif complet qui tient
compte de la variable spciale sf_format prsente plus tard.
Il est galement possible dobtenir de nombreuses informations de dbo-
gage pour une route donne en passant son nom en tant que second
paramtre additionnel.
$ php symfony app:routes frontend
>> app Current routes for application "frontend"
Name Method Pattern
job GET /job.:sf_format
job_new GET /job/new.:sf_format
job_create POST /job.:sf_format
job_edit GET /job/:id/edit.:sf_format
job_update PUT /job/:id.:sf_format
job_delete DELETE /job/:id.:sf_format
job_show GET /job/:id.:sf_format
job_show_user GET /job/:company_slug/:location_slug/:id/
:position_slug
homepage ANY /
$ php symfony app:routes frontend job_edit
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
92
Lexcution de cette commande a pour effet de produire le rsultat sui-
vant dans le terminal. Cette commande est particulirement intressante
puisquelle donne tout le dtail de la configuration de la route, y compris
les paramtres internes dfinis automatiquement par la classe
sfDoctrineRoute. Les lments mis en exergue sont ceux qui ont dj t
vus tout au long de ce chapitre.
>> app Route "job_edit" for application "frontend"
Name job_edit
Pattern /job/:id/edit.:sf_format
Class sfDoctrineRoute
Defaults action: 'edit'
module: 'job'
sf_format: 'html'
Requirements id: '\\d+'
sf_format: '[^/\\.]+'
sf_method: 'get'
Options context: array ()
debug: false
extra_parameters_as_query_string: true
generate_shortest_url: true
load_configuration: false
logging: false
method: NULL
model: 'JobeetJob'
object_model: 'JobeetJob'
segment_separators: array (0 => '/',1 => '.',)
segment_separators_regex: '(?:/|\\.)'
suffix: ''
text_regex: '.+?'
type: 'object'
variable_content_regex: '[^/\\.]+'
variable_prefix_regex: '(?:\\:)'
variable_prefixes: array (0 => ':',)
variable_regex: '[\\w\\d_]+'
Regex #^
/job
/(?P<id>\d+)
/edit
(?:\.(?P<sf_format>[^/\.]+)
)?
$#x
Tokens separator array (0 => '/',1 => NULL,)
text array (0 => 'job',1 => NULL,)
separator array (0 => '/',1 => NULL,)
variable array (0 => ':id',1 => 'id',)
separator array (0 => '/',1 => NULL,)
text array (0 => 'edit',1 => NULL,)
separator array (0 => '.',1 => NULL,)
variable array (0 => ':sf_format',1 => 'sf_format',)
5


L
e

r
o
u
t
a
g
e
Groupe Eyrolles, 2008
93
Supprimer les routes par dfaut
Une bonne pratique consiste dclarer une route spcifique pour chaque
URL de lapplication, et supprimer les routes par dfaut afin demp-
cher laccs des ressources pour lesquelles aucune route ddie naurait
t dclare dans le fichier de configuration routing.yml. Cela permet
par exemple de se prmunir des pirates qui tenteraient daccder des
modules de lapplication non scuriss en passant par les routes par
dfaut qui correspondent nimporte quel motif dURL.
Par consquent, comme la route job dfinit toutes les routes ncessaires
pour dcrire lapplication Jobeet, les routes par dfaut du fichier de con-
figuration routing.yml peuvent tre supprimes ou mises en commen-
taire en toute scurit. Lapplication Jobeet devrait continuer de
fonctionner normalement comme avant.
Dsactivation des routes par dfaut de lapplication frontend dans le fichier apps/
frontend/config/routing.yml
En rsum
Ce chapitre a prsent de nombreux concepts importants concernant le
framework interne de routage de Symfony comme les routes par dfaut,
basiques, restreintes aux mthodes HTTP, dobjets et de collections
Doctrine, ou encore les collections de routes Doctrine. De plus, ces
pages ont montr comment il est possible de crer des URLs lgantes et
significatives avec Symfony, tout en les dcouplant de leur implmenta-
tion technique. Tous ces aspects seront trs rgulirement remis en
uvre tout au long de cet ouvrage dans la mesure o les URLs jouent un
rle fondamental dans une application web.
Le chapitre suivant quant lui nintroduit pas vritablement de nou-
veaux concepts, ce qui permettra de revenir plus en dtail sur une bonne
partie des notions abordes jusqu prsent : le routage, larchitecture
MVC, les objets Doctrine, le remaniement du code, etc.
#default_index:
# url: /:module
# param: { action: index }
#
#default:
# url: /:module/:action/*
Groupe Eyrolles, 2008
chapitre 6
Groupe Eyrolles, 2008
Optimisation du modle
et refactoring
La majeure partie du code mtier dune application MVC
est conditionne dans la couche du modle. Or, le volume
de code du modle peut trs vite devenir un vritable casse-tte
maintenir et prenniser. Cest pourquoi ce chapitre
sera loccasion de vous familiariser avec les techniques
de factorisation et de simplification du code.
MOTS-CLS :
BModle
BDoctrine Query Language
BRefactoring de code
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
96
Le chapitre prcdent a permis daborder la manire de crer des URLs
lgantes et de voir comment utiliser le framework Symfony pour auto-
matiser de nombreuses choses. Dans les pages qui suivent, le site web
Jobeet va tre amlior en optimisant le code ici et l. Dans la foule, les
fonctionnalits abordes jusqu maintenant seront un peu plus dtailles.
Prsentation de lobjet Doctrine_Query
Parmi les objectifs dfinis au chapitre 2 figure celui-ci :
Quand un utilisateur arrive sur le site web de Jobeet, il dcouvre une
liste des offres demploi actives .
Pour le moment, toutes les offres demploi sont affiches, quelles soient
actives ou non.
Contenu du fichier apps/frontend/modules/job/actions/actions.class.php
Une offre active est une offre qui a t poste il y a moins de 30 jours.
Cest la mthode Doctrine_Query::execute() qui effectue une requte
sur la base de donnes. Dans le code ci-dessus, aucune condition parti-
culire na t spcifie, ce qui signifie que tous les enregistrements sont
rcuprs de la base de donnes.
Avec Doctrine, lajout de conditions dans une requte SQL est ralis
laide de la mthode where(), comme le montre le code modifi de la
mthode executeIndex() de la classe JobActions :
class jobActions extends sfActions
{
public function executeIndex(sfWebRequest $request)
{
$this->jobeet_job_list = Doctrine::getTable('JobeetJob')
->createQuery('a')
->execute();
}
// ...
}
public function executeIndex(sfWebRequest $request)
{
$q = Doctrine_Query::create()
->from('JobeetJob j')
->where('j.created_at > ?', date('Y-m-d h:i:s',
X time() - 86400 * 30));
$this->jobeet_job_list = $q->execute();
}
6


O
p
t
i
m
i
s
a
t
i
o
n

d
u

m
o
d

l
e

e
t

r
e
f
a
c
t
o
r
i
n
g
Groupe Eyrolles, 2008
97
Dboguer le code SQL gnr par Doctrine
Dispensant davoir crire les requtes SQL la main, Doctrine fait
attention aux diffrences entre les moteurs de bases de donnes, et
gnre les ordres SQL optimiss pour le moteur configur au chapitre 3.
Nanmoins, cest parfois une aide indniable que de pouvoir lire le code
SQL gnr par Doctrine ; par exemple, pour dboguer une requte qui
ne fonctionne pas comme on lattend. En environnement de dveloppe-
ment, Symfony enregistre ces requtes (et bien plus encore) dans le
fichier log/frontend_dev.log.
Dcouvrir les fichiers de log
Le rpertoire log/ stocke les fichiers de log par application et par envi-
ronnement, ce qui permet de retrouver et de suivre facilement lensemble
des oprations internes effectues par le framework lorsquune URL est
demande au serveur.
Exemple de log dans le fichier log/frontend_dev.log
Il est ainsi possible de contrler que la requte gnre par Doctrine pos-
sde bel et bien une clause Where sur la colonne created_at (WHERE
j.created_at > ?).
Avoir recours la barre de dbogage
Si laccs ces journaux dvnements reste trs pratique, il ne demeure
pas moins contraignant davoir passer du navigateur lEDI
( environnement de dveloppement intgr ) et au fichier de logs.
Cest pourquoi Symfony comporte une barre de dbogage afin de rendre
disponibles depuis le navigateur toutes les informations pertinentes.
Dec 04 13:58:33 symfony [info] {sfDoctrineLogger} executeQuery
: SELECT
j.id AS j__id, j.category_id AS j__category_id, j.type AS
j__type,
j.company AS j__company, j.logo AS j__logo, j.url AS j__url,
j.position AS j__position, j.location AS j__location,
j.description AS j__description, j.how_to_apply AS
j__how_to_apply,
j.token AS j__token, j.is_public AS j__is_public,
j.is_activated AS j__is_activated, j.email AS j__email,
j.expires_at AS j__expires_at, j.created_at AS j__created_at,
j.updated_at AS j__updated_at FROM jobeet_job j
WHERE j.created_at > ? (2008-11-08 01:13:35)
BONNE PRATIQUE Les requtes prpares
pour lutter contre les attaques par
injections
La chane ? dans la requte indique que
Doctrine gnre des requtes prpares. La
valeur courante de ? ( 2008-11-08
01:13:35 dans lexemple ci-dessus) est passe
au cours de lexcution de la requte afin dtre
traite littralement par le moteur de base de
donnes. Lusage de requtes prpares rduit
drastiquement lexposition de lapplication aux
attaques par injections SQL.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
98
Intervenir sur les proprits dun objet
avant sa srialisation en base de donnes
Mme si le code ci-dessus fonctionne, il est encore loin dtre parfait
dans la mesure o il ne prend pas en compte certaines exigences telle que
la suivante :
Un utilisateur peut revenir pour ractiver ou tendre la validit de
lannonce dune offre demploi pour une nouvelle priode de
30 jours
Or, le code prcdent sappuie sur la valeur du champ created_at, et
parce que cette colonne stocke la date de cration, il est impossible de
satisfaire ce besoin.
Redfinir la mthode save() dun objet Doctrine
Une colonne expires_at a heureusement t prvue dans le schma de
base de donnes (voir chapitre 3). Pour linstant, sa valeur nest pas ren-
seigne puisquelle nest pas dfinie dans le fichier de donnes initiales.
Mais lors de la cration dune nouvelle offre demploi, cette colonne
pourrait automatiquement prendre pour valeur la date courante du ser-
veur, laquelle une priode de 30 jours est ajoute.
Pour raliser une opration juste avant quun objet Doctrine ne soit
srialis en base de donnes, il suffit de redfinir la mthode save() de la
classe de modle.
Redfinition de la mthode save() de lobjet dans le fichier lib/model/doctrine/
JobeetJob.class.php
Figure 61 Requtes SQL gnres par Doctrine dans la barre doutils de dboguage de Symfony
class JobeetJob extends BaseJobeetJob
{
public function save(Doctrine_Connection $conn = null)
{
if ($this->isNew() && !$this->getExpiresAt())
{
$now = $this->getCreatedAt() ?
X strtotime($this->getCreatedAt()) : time();
6


O
p
t
i
m
i
s
a
t
i
o
n

d
u

m
o
d

l
e

e
t

r
e
f
a
c
t
o
r
i
n
g
Groupe Eyrolles, 2008
99
La mthode isNew() retourne true quand lobjet na pas t srialis en
base de donnes et false dans le cas contraire.
Rcuprer la liste des offres demploi actives
Il est maintenant ncessaire de modifier laction afin dutiliser la colonne
expires_at au lieu de created_at pour slectionner les offres demploi
actives.
La requte est ainsi rduite ne rcuprer que les offres demploi ayant
une valeur expires_at dans le futur.
Mettre jour les donnes de test pour
sassurer de la validit des offres affiches
Rafrachir la page daccueil de Jobeet dans un navigateur ne changera
rien puisque les offres demploi enregistres en base de donnes nont t
postes que quelques jours plus tt. Il est donc obligatoire de changer les
donnes de test pour ajouter une nouvelle offre demploi dj expire.
Offre demploi ajouter au fichier data/fixtures/jobs.yml
$this->setExpiresAt(date('Y-m-d h:i:s',
$now + 86400 * 30));
}
return parent::save($conn);
}
// ...
}
public function executeIndex(sfWebRequest $request)
{
$q = Doctrine_Query::create()
->from('JobeetJob j')
->where('j.expires_at > ?', date('Y-m-d h:i:s', time()));
$this->jobeet_job_list = $q->execute();
}
JobeetJob:
# other jobs
expired_job:
JobeetCategory: programming
company: Sensio Labs
IMPORTANT Respecter lindentation
dun fichier YAML
Il faut toujours veiller ne pas rompre lindenta-
tion lorsque lon copie et colle du code dans un
fichier de donnes de tests. Loffre demploi
expired_job ne doit avoir que deux espaces
qui la prcdent.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
100
Dans la dfinition des champs de cette offre nouvellement cre, la valeur
de la colonne created_at a t redfinie explicitement bien quelle soit
automatiquement remplie par Doctrine. La valeur ainsi spcifie surchar-
gera celle par dfaut. En rechargeant les donnes de test, puis en rafra-
chissant le navigateur, loffre demploi expire ne saffichera pas.
La requte SQL suivante permet de contrler que la colonne expires_at
est bien remplie automatiquement par la mthode save() partir de la
valeur de created_at.
Grer les paramtres personnaliss dune
application dans Symfony
Dans la mthode JobeetJob::save(), le nombre de jours restant avant
quune offre demploi nexpire a t cod en dur . Or il vaudrait bien
mieux rendre cette valeur configurable ailleurs pour des raisons de faci-
lit de maintenance et de gnricit. La solution est toute trouve avec le
fichier de configuration app.yml qui permet de dfinir les paramtres
spcifiques une application. Ce fichier YAML, fourni par dfaut par le
framework Symfony, peut dfinir nimporte quel paramtre.
Dcouvrir le fichier de configuration app.yml
En rgle gnrale, il est dconseill de fixer en dur dans un programme des
informations qui peuvent tre configures ailleurs. En effet, en centralisant
les paramtres dans un fichier commun, les modifications se font plus sim-
plement et plus rapidement autant de temps gagn en maintenance.
position: Web Developer
location: Paris, France
description: Lorem ipsum dolor sit amet, consectetur
adipisicing elit.
how_to_apply: Send your resume to lorem.ipsum [at]
dolor.sit
is_public: true
is_activated: true
expires_at: '2005-12-01 00:00:00'
token: job_expired
email: job@example.com
$ php symfony doctrine:data-load
SELECT `position`, `created_at`, `expires_at` FROM
`jobeet_job`;
6


O
p
t
i
m
i
s
a
t
i
o
n

d
u

m
o
d

l
e

e
t

r
e
f
a
c
t
o
r
i
n
g
Groupe Eyrolles, 2008
101
Symfony fournit cet usage un fichier ddi la configuration dune
application. Il sagit du fichier apps/APPLICATION/config/app.yml.
Exemple de fichier de configuration : contenu de apps/frontend/config/app.yml
Dans lapplication, ces paramtres sont disponibles au travers de la classe
globale sfConfig.
Rcuprer une valeur de configuration depuis le modle
Le paramtre a t prfix par app_ parce que la classe sfConfig fournit
aussi un accs aux paramtres de Symfony comme il sera prsent plus
tard. Afin que ce nouveau paramtre soit pris en compte, il est ncessaire
de modifier de nouveau la mthode save() de lobjet JobeetJob.
Le fichier de configuration app.yml est un excellent moyen de centraliser
les paramtres globaux de lapplication. Ceux-ci restent alors disponibles
tout moment et depuis nimporte o grce la classe globale sfConfig.
Remanier le code en continu pour respecter
la logique MVC
Le code crit fonctionne parfaitement, mais il nest pas encore tout fait
correct. O se situe le problme et comment le rsoudre ? Le chapitre 4
a montr comment le modle MVC spare le code en trois couches
distinctes : le modle, la vue et le contrleur.
all:
active_days: 30
sfConfig::get('app_active_days')
public function save(Doctrine_Connection $conn = null)
{
if ($this->isNew() && !$this->getExpiresAt())
{
$now = $this->getCreatedAt() ?
X strtotime($this->getCreatedAt()) : time();
$this->setExpiresAt(date('Y-m-d h:i:s', $now + 86400 *
X sfConfig::get('app_active_days')));
}
return parent::save($conn);
}
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
102
Tout au long du processus de dveloppement de lapplication, il sera
souhaitable de garder cette rgle lesprit afin de penser remanier du
code, voire le dplacer ailleurs lorsque ncessaire.
Exemple de dplacement du contrleur vers le modle
Le code de Doctrine_Query nappartient pas laction (la couche
contrleur) ; il dpend en ralit de la couche du modle. Dans le motif
MVC, le modle dfinit toute la logique mtier, cest--dire celle qui
manipule les donnes, tandis que le contrleur ne fait quappeler celui-ci
pour y rcuprer des donnes, en fonction de la requte de lutilisateur,
quil communique ensuite la vue.
Dans le cas prsent, le code retourne une collection doffres demploi en
guise de rsultat ; cest pourquoi il convient de dplacer ce dernier dans
la classe JobeetJobTable, dans laquelle on cre une mthode
getActiveJobs().
Contenu du fichier lib/model/doctrine/JobeetJobTable.class.php
prsent, laction peut utiliser cette nouvelle mthode pour retrouver
les offres demploi actives.
Avantages du remaniement de code
Ce remaniement du code apporte plusieurs bnfices indniables par
rapport au code prcdent.
Tout dabord, la logique de rcupration des offres demploi actives est
dsormais sa place dans le modle. De ce fait, le contrleur est consid-
rablement allg et rendu beaucoup plus lisible. Dautre part, ce rema-
niement a rendu la mthode getActiveJobs() rutilisable pour une
class JobeetJobTable extends Doctrine_Table
{
public function getActiveJobs()
{
$q = $this->createQuery('j')
->where('j.expires_at > ?', date('Y-m-d h:i:s', time()));
return $q->execute();
}
}
public function executeIndex(sfWebRequest $request)
{
$this->jobeet_job_list =
X Doctrine::getTable('JobeetJob')->getActiveJobs();
}
Les tests automatiques seront prsents au
chapitre 8.
6


O
p
t
i
m
i
s
a
t
i
o
n

d
u

m
o
d

l
e

e
t

r
e
f
a
c
t
o
r
i
n
g
Groupe Eyrolles, 2008
103
ventuelle prochaine utilisation. Enfin, le code est dsormais disponible
pour des tests unitaires.
Ordonner les offres suivant leur date dexpiration
Pour linstant, toutes les offres sont rcupres et affiches dans lordre de
leur cration, cest--dire en suivant la cl primaire. Or notre application
doit montrer linformation la plus rcente le plus souvent possible ; cest
pourquoi les offres doivent tre ordonnes suivant leur date dexpiration
de la plus lointaine dans le futur la plus proche de la date courante.
Le code suivant ordonne la liste des offres demploi suivant la colonne
expires_at.
La mthode orderBy dtermine la clause ORDER BY de la requte SQL
gnre. Il est galement possible dutiliser addOrderBy() pour effectuer
un tri sur plusieurs colonnes.
Classer les offres demploi selon leur catgorie
Parmi les objectifs dfinis au chapitre 2 figure celui-ci :
Les offres demploi sont ordonnes par catgorie et ensuite par date de
publication, des plus rcentes aux plus anciennes .
Jusqu prsent, la catgorie dune offre na pas t prise en compte. Or
cette prise en compte fait partie du cahier des charges : la page daccueil
doit afficher les offres demploi par catgorie. Il est donc ncessaire de
rcuprer dans un premier temps toutes les catgories ayant au moins
une offre demploi active. Pour ce faire, une mthode getWithJobs() doit
tre ajoute la classe JobeetCategoryTable.
Contenu du fichier lib/model/doctrine/JobeetCategoryTable.class.php
public function getActiveJobs()
{
$q = $this->createQuery('j')
->where('j.expires_at > ?', date('Y-m-d h:i:s', time()))
->orderBy('j.expires_at DESC');
return $q->execute();
}
class JobeetCategoryTable extends Doctrine_Table
{
public function getWithJobs()
{
$q = $this->createQuery('c')
->leftJoin('c.JobeetJobs j')
->where('j.expires_at > ?', date('Y-m-d h:i:s', time()));
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
104
Laction index doit aussi tre modifie en consquence :
Dtail de la mthode executeIndex() du fichier apps/frontend/modules/job/actions/
actions.class.php
Dans le template, les offres demploi actives sont dsormais affiches en
itrant travers toutes les catgories.
Contenu du fichier apps/frontend/modules/job/indexSuccess.php
return $q->execute();
}
}
public function executeIndex(sfWebRequest $request)
{
$this->categories = Doctrine::getTable('JobeetCategory')
X ->getWithJobs();
}
<?php use_stylesheet('jobs.css') ?>
<div id="jobs">
<?php foreach ($categories as $category): ?>
<div class="category_<?php echo Jobeet::slugify($category
X ->getName()) ?>">
<div class="category">
<div class="feed">
<a href="">Feed</a>
</div>
<h1><?php echo $category ?></h1>
</div>
<table class="jobs">
<?php foreach ($category->getActiveJobs()
as $i => $job): ?>
<tr class="<?php echo fmod($i, 2) ? 'even' : 'odd' ?>">
<td class="location">
<?php echo $job->getLocation() ?>
</td>
<td class="position">
<?php echo link_to($job->getPosition(),
'job_show_user', $job) ?>
</td>
<td class="company">
<?php echo $job->getCompany() ?>
</td>
</tr>
<?php endforeach; ?>
</table>
</div>
<?php endforeach; ?>
</div>
REMARQUE De lutilit de la mthode
magique __toString()
Pour afficher le nom de la catgorie dans le
modle, nous avons eu recours
echo $category
Cela parat-il trange ? $category est un
objet, comment echo peut-il afficher le nom de
la catgorie ? La rponse a t donne au
chapitre 3 lorsque nous avons dfini la mthode
magique __toString() pour toutes les
classes du modle.
6


O
p
t
i
m
i
s
a
t
i
o
n

d
u

m
o
d

l
e

e
t

r
e
f
a
c
t
o
r
i
n
g
Groupe Eyrolles, 2008
105
Pour que cela fonctionne, il est ncessaire dajouter la mthode
getActiveJobs() la classe JobeetCategory.
Dtail de la mthode getActiveJobs() du fichier lib/model/doctrine/
JobeetCategory.class.php
La mthode JobeetCategory::getActiveJobs() se sert de la mthode
Doctrine::getTable('JobeetJob')->getActiveJobs() pour retrouver les
offres demploi actives pour la catgorie donne.
Le but, en faisant appel Doctrine::getTable('JobeetJob')
->getActiveJobs(), est de prciser la condition en fournissant une catgorie.
Un objet Doctrine_Query est pass la place dun objet JobeetCategory,
cest l en effet la meilleure manire dencapsuler une condition gnrique.
La mthode getActiveJobs() a besoin de fusionner cet objet
Doctrine_Query avec sa propre requte. Cest en fait trs simple raliser
puisque Doctrine_Query est lui-mme un objet.
Dtail de la mthode getActiveJobs() du fichier lib/model/doctrine/
JobeetJobTable.class.php
Limiter le nombre de rsultats affichs
Il y a un autre besoin fonctionnel implmenter pour la page daccueil
de listage des offres demploi :
public function getActiveJobs()
{
$q = Doctrine_Query::create()
->from('JobeetJob j')
->where('j.category_id = ?', $this->getId());
return Doctrine::getTable('JobeetJob')->getActiveJobs($q);
}
public function getActiveJobs(Doctrine_Query $q = null)
{
if (is_null($q))
{
$q = Doctrine_Query::create()->from('JobeetJob j');
}
$q->andWhere('j.expires_at > ?', date('Y-m-d h:i:s', time()))
->addOrderBy('j.expires_at DESC');
return $q->execute();
}
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
106
Pour chaque catgorie, la liste montre seulement les 10 premires offres
demploi et un lien permet de lister toutes les offres de cette dernire .
Cest aussi simple que dajouter une mthode getActiveJobs() :
Mthode getActiveJobs() du fichier lib/model/doctrine/JobeetCategory.class.php
La clause LIMIT approprie est pour linstant code en dur lintrieur
du modle. Or, il serait plus pertinent de rendre cette valeur configu-
rable, comme nous lavons vu plus haut dans ce chapitre. Pour ce faire, il
suffit de changer le template afin de passer un nombre maximum
doffres demploi dfini dans le fichier app.yml.
public function getActiveJobs($max = 10)
{
$q = Doctrine_Query::create()
->from('JobeetJob j')
->where('j.category_id = ?', $this->getId())
->limit($max);
return Doctrine::getTable('JobeetJob')->getActiveJobs($q);
}
Figure 62
Classement des offres demploi par catgorie
6


O
p
t
i
m
i
s
a
t
i
o
n

d
u

m
o
d

l
e

e
t

r
e
f
a
c
t
o
r
i
n
g
Groupe Eyrolles, 2008
107
Code remplacer dans le fichier apps/frontend/modules/job/indexSuccess.php
Puis, le nouveau paramtre doit tre ajout au fichier app.yml :
Modifier les donnes de test
dynamiquement par lajout de code PHP
Mis part rduire le paramtre max_jobs_on_homepage 1, on ne distin-
guera aucune diffrence. Il est donc ncessaire dajouter un ensemble
doffres demploi aux donnes de test. Bien sr, il est tout fait possible
de copier et coller une offre existante dix ou vingt fois la main mais il
existe une bien meilleure faon de faire. La duplication est viter,
mme dans les fichiers de donnes.
Heureusement, les fichiers YAML de Symfony peuvent contenir du
code PHP qui sera valu juste avant lanalyse du fichier, comme le
montre le fichier de donnes jobs.yml modifi :
<?php foreach ($category-
>getActiveJobs(sfConfig::get('app_max_jobs_on_homepage')) as $i
=> $job): ?>
all:
active_days: 30
max_jobs_on_homepage: 10
ATTENTION Indentation du code YAML
Attention ne pas bousculer lindentation de
code YAML ! Linterprteur syntaxique du YAML
naimera pas le dveloppeur si ce dernier dsor-
donne lindentation. Il faut toujours garder en
tte les quelques astuces suivantes lorsque lon
ajoute du code PHP un fichier YAML :
les instructions <?php ?> doivent toujours
tre en dbut de ligne ou bien tre encapsu-
les dans une valeur ;
si une instruction <?php ?> termine une
ligne, une nouvelle ligne (\n) doit explicite-
ment tre imprime en sortie.
JobeetJob:
# Starts at the beginning of the line (no whitespace before)
<?php for ($i = 100; $i <= 130; $i++): ?>
job_<?php echo $i ?>:
JobeetCategory: programming
company: Company <?php echo $i."\n" ?>
position: Web Developer
location: Paris, France
description: Lorem ipsum dolor sit amet, consectetur
adipisicing elit.
how_to_apply: |
Send your resume to lorem.ipsum [at] company_<?php echo $i ?>.sit
is_public: true
is_activated: true
token: job_<?php echo $i."\n" ?>
email: job@example.com
<?php endfor; ?>
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
108
Lors du rechargement des donnes de test avec la tche doctrine:data-
load, on voit que seules dix offres demploi sont affiches sur la page
daccueil pour la catgorie Programming. Dans la capture dcran sui-
vante, le nombre maximum doffres demploi a t fix cinq pour ra-
liser une image plus petite.
Empcher la consultation dune offre
expire
Lorsquune offre demploi expire, mme en ayant connaissance de
lURL, il ne devrait plus tre possible dy accder. Il suffit dessayer
lURL dune offre expire (en remplaant lid par lid courant dans la
base de donnes - SELECT id, token FROM jobeet_job WHERE
expires_at < NOW()) :
Figure 63
Limitation du nombre
doffres demploi par catgorie
/frontend_dev.php/job/sensio-labs/paris-france/
X ID/web-developer-expired
6


O
p
t
i
m
i
s
a
t
i
o
n

d
u

m
o
d

l
e

e
t

r
e
f
a
c
t
o
r
i
n
g
Groupe Eyrolles, 2008
109
Au lieu dafficher une offre demploi, lapplication doit rediriger lutilisa-
teur vers une page derreur 404. Or comment raliser cela, sachant
quune offre est automatiquement retrouve par sa route ?
Dfinition de la route job_show_user dans le fichier apps/frontend/config/
routing.yml
job_show_user:
url: /job/:company_slug/:location_slug/:id/:position_slug
class: sfDoctrineRoute
options:
model: JobeetJob
type: object
method_for_query: retrieveActiveJob
param: { module: job, action: show }
requirements:
id: \d+
sf_method: [GET]
REMARQUE
propos des versions de Symfony
Le paramtre method_for_query ne fonc-
tionne pas pour les versions antrieures
Symfony 1.2.2.
Figure 64 Page derreur 404 dune offre demploi expire
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
110
La mthode retrieveActiveJob() recevra lobjet Doctrine_Query cons-
truit par la route :
Contenu du fichier lib/model/doctrine/JobeetJobTable.class.php
Dsormais, lutilisateur qui tente daccder une offre expire est auto-
matiquement redirig vers une page derreur 404.
Crer une page ddie la catgorie
prsent, il serait pratique de disposer dune page naffichant que les
offres dune certaine catgorie (passe en paramtre), ainsi qu'un lien
pour y accder via la page daccueil.
Mais attendez ! Arrtons l ce sixime chapitre mme si nous navons
pas tant travaill car vous avez suffisamment de connaissances pour
implmenter vous-mme cette fonctionnalit en guise dexercice. La
correction sera prsente au chapitre suivant
En rsum
Ce chapitre a abord plus en dtail ce quil est possible de raliser partir
du modle de donnes du framework Symfony. Nhsitez pas travailler
sur votre copie de travail locale de Jobeet ainsi qu utiliser la documenta-
tion de lAPI en ligne, disponible sur le site officiel de Symfony
ladresse http://www.symfony-project.org/api/1_2/. Limplmentation de la
page de dtail dune catgorie sera, quant elle, dvoile tout au long du
chapitre suivant.
class JobeetJobTable extends Doctrine_Table
{
public function retrieveActiveJob(Doctrine_Query $q)
{
$q->andWhere('a.expires_at > ?', date('Y-m-d h:i:s',
time()));
return $q->fetchOne();
}
// ...
}
Groupe Eyrolles, 2008
chapitre 7
Groupe Eyrolles, 2008
Concevoir et paginer la liste
doffres dune catgorie
Les chapitres prcdents ont montr toute limportance
du motif MVC et de lintrt de ne pas dupliquer le code
dans un projet web. Le framework Symfony recle encore
bien dautres outils pour satisfaire ce besoin dans la vue,
notamment grce aux templates partiels qui seront dcrits
dans ce septime chapitre.
MOTS-CLS :
BRoutage
BTemplates partiels
BPagination
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
114
Au cours du chapitre prcdent, il a t montr que Symfony excelle
dans diffrents domaines : les requtes SQL avec Doctrine, les donnes
de test, le routage, le dbogage ainsi que la configuration sur mesure. Le
dernier chapitre se clturait sur un petit dfi : mettre en place une page
ddie la catgorie . Dans celui-ci, le but est de donner une solution
ce problme, en commenant par la prsentation dune potentielle
implmentation.
Mise en place dune route ddie la page
de la catgorie
Dclarer la route category dans le fichier routing.yml
La premire tape avant de dmarrer limplmentation de cette nouvelle
page consiste tablir une nouvelle route propre la catgorie. Dans la
mesure o la catgorie est un objet Doctrine, le choix de la classe
sfDoctrineRoute pour grer la route ddie en dcoule naturellement.
Le code ci-dessous ajouter au fichier de configuration routing.yml
dcrit la route propre chaque catgorie.
Route ajouter au dbut du fichier apps/frontend/config/routing.yml
Une route peut utiliser nimporte quelle colonne de son objet relatif en tant
que paramtre. Elle peut galement avoir recours nimporte quelle autre
valeur du moment quil existe un accesseur associ dans la classe de lobjet.
Implmenter laccesseur getSlug() dans la classe
JobeetJob
Du fait que le paramtre slug ne dispose daucune correspondance dans
la table des catgories, la classe JobeetCategory doit se voir complte
dun accesseur virtuel afin de rendre la route fonctionnelle.
category:
url: /category/:slug
class: sfDoctrineRoute
param: { module: category, action: show }
options: { model: JobeetCategory, type: object }
7


C
o
n
c
e
v
o
i
r

e
t

p
a
g
i
n
e
r

l
a

l
i
s
t
e

d

o
f
f
r
e
s

d

u
n
e

c
a
t

g
o
r
i
e
Groupe Eyrolles, 2008
115
Mthode getSlug() ajouter au fichier lib/model/doctrine/JobeetCategory.class.php
Personnaliser les conditions daffichage du
lien de la page de catgorie
Intgrer un lien pour chaque catgorie ayant plus de dix
offres valides
Pour linstant, la page daccueil de Jobeet ne dispose daucun lien pour se
rendre directement sur la page de dtail dune catgorie. La page
daccueil est lendroit opportun pour faire figurer un lien vers le dtail de
chaque catgorie liste. Nanmoins, pour ne pas surcharger inutilement
cette page, seules les catgories ayant plus de dix offres demploi actives
se verront ajouter un lien. Pour ce faire, le fichier indexSuccess.php du
module job doit tre modifi pour accueillir ce nouveau lien comme le
montre le code ci-dessous.
public function getSlug()
{
return Jobeet::slugify($this->getName());
}
MTHODE Implmentation
dune nouvelle fonctionnalit
Lorsque lon dmarre limplmentation dune nou-
velle fonctionnalit, une bonne pratique consiste,
dans un premier temps, penser lURL, puis
crer la route associe. Cest dailleurs obligatoire
quand les routes par dfaut ont t supprimes.
<!-- some HTML code -->
<h1>
<?php echo link_to($category, 'category', $category) ?>
</h1>
<!-- some HTML code -->
</table>
<?php if (($count = $category->countActiveJobs() -
sfConfig::get('app_max_jobs_on_homepage')) > 0): ?>
<div class="more_jobs">
and <?php echo link_to($count, 'category', $category) ?>
more...
</div>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
116
Le lien sera ajout la page seulement sil y a plus de 10 offres demploi
afficher pour la catgorie courante. Le lien contient le nombre doffres
non affiches. Afin que ce template puisse se comporter de la sorte, la
mthode countActiveJobs() doit tre implmente dans la classe
JobeetCategory.
Implmenter la mthode countActiveJobs() de la classe
JobeetCategory
Le template indexSuccess.php du module job fait appel la mthode
countActiveJobs() de lobjet de la catgorie courante. Pour linstant, cette
mthode nexiste pas encore dans la classe JobeetCategory. Elle sert
compter le nombre doffres demploi actives linstant t pour la catgorie.
Le code ci-aprs donne le dtail complet de cette nouvelle mthode.
Mthode countActiveJobs() ajouter au fichier lib/model/doctrine/
JobeetCategory.class.php
Implmenter la mthode countActiveJobs() de la classe
JobeetCategoryTable
La mthode countActiveJobs() fait appel une autre mthode
countActiveJobs(), qui elle non plus nexiste pas dans la classe
JobeetJobTable. Cette nouvelle mthode doit tre capable de prendre un
objet Doctrine_Query en argument afin de retourner le nombre doffres
demploi valides qui correspondent aux critres de cette requte. Il faut pour
cela remplacer le code de JobeetJobTable.class.php par le code suivant :
Contenu du fichier lib/model/doctrine/JobeetJobTable.class.php
public function countActiveJobs()
{
$q = Doctrine_Query::create()
->from('JobeetJob j')
->where('j.category_id = ?', $this->getId());
return Doctrine::getTable('JobeetJob')->countActiveJobs($q);
}
class JobeetJobTable extends Doctrine_Table
{
public function retrieveActiveJob(Doctrine_Query $q)
{
return $this->addActiveJobsQuery($q)->fetchOne();
}
7


C
o
n
c
e
v
o
i
r

e
t

p
a
g
i
n
e
r

l
a

l
i
s
t
e

d

o
f
f
r
e
s

d

u
n
e

c
a
t

g
o
r
i
e
Groupe Eyrolles, 2008
117
Le code de la classe JobeetJobTable a t factoris afin dintroduire une
nouvelle mthode partage addActiveJobsQuery() et de rendre ainsi le
code plus DRY (Dont Repeat Yourself ).
La mthode countActiveJobs() utilise directement la mthode count()
plutt que la mthode execute() suivie dun comptage manuel des
rsultats, et ce pour des raisons videntes de performance. En effet,
recourir directement la mthode count() vite la cration et lhydrata-
tion dobjets en mmoire ; seule la valeur rsultante du dnombrement
est renvoye.
Plusieurs fichiers ont d tre modifis dans le but dimplmenter cette
nouvelle fonctionnalit. Cependant, le code crit a systmatiquement t
plac dans la couche adquate de lapplication, notamment pour le
rendre rutilisable ultrieurement. Tout au long du processus, des pices
de code existant ont t factorises. Ce type de processus de travail est
typique des projets et de la philosophie du framework Symfony.
public function getActiveJobs(Doctrine_Query $q = null)
{
return $this->addActiveJobsQuery($q)->execute();
}
public function countActiveJobs(Doctrine_Query $q = null)
{
return $this->addActiveJobsQuery($q)->count();
}
public function addActiveJobsQuery(Doctrine_Query $q = null)
{
if (is_null($q))
{
$q = Doctrine_Query::create()
->from('JobeetJob j');
}
$alias = $q->getRootAlias();
$q->andWhere($alias . '.expires_at > ?',
date('Y-m-d h:i:s', time()))
->addOrderBy($alias . '.expires_at DESC');
return $q;
}
}
BONNE PRATIQUE La refactorisation de code
La premire fois quun bout de code est ruti-
lis, le copier peut paratre suffisant. Or, si on lui
trouve un nouvel usage, cela implique quil faut
refactoriser tous ces emplois dans une mme
fonction ou mthode partage, comme il la t
fait ici.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
118
Mise en place du module ddi aux
catgories
Gnrer automatiquement le squelette du module
Lapplication Jobeet doit se doter dun nouveau module destin
accueillir les spcificits des catgories. Il est bien sr tentant de recourir
la tche doctrine:generate-crud afin de construire un module com-
plet de la mme manire que pour le module job. Nanmoins, prs de
90 % du code gnr aurait t jet au rebut. Pour cette raison, la cra-
tion dun module basique entirement vide est ralise au moyen de la
tche generate:module.
Figure 71
Page daccueil de Jobeet
$ php symfony generate:module frontend category
7


C
o
n
c
e
v
o
i
r

e
t

p
a
g
i
n
e
r

l
a

l
i
s
t
e

d

o
f
f
r
e
s

d

u
n
e

c
a
t

g
o
r
i
e
Groupe Eyrolles, 2008
119
En accdant la page dune catgorie, la route de celle-ci doit trouver
lobjet JobCategory associ laide de la variable slug de la requte. Or,
le slug nest pas stock dans la base de donnes, ce qui rend donc impos-
sible la dduction dune catgorie partir du slug.
Ajouter un champ supplmentaire pour accueillir le slug
de la catgorie
Afin de pouvoir identifier de manire unique une catgorie partir dun
slug, la prsence dun champ slug dans la table jobeet_category est
ncessaire. Grce aux nombreux outils internes livrs avec Doctrine, la
gnration dun slug partir de la valeur dun autre champ de la table
SQL est entirement automatise. Pour ce faire, il suffit dactiver le
comportement Sluggable pour le modle JobeetCategory dans le
schma de description de la base de donnes.
Activation du comportement Sluggable dans le fichier config/doctrine/schema.yml
prsent, la gnration dun slug unique partir de la valeur du champ
name est automatiquement gre par Doctrine. De ce fait, la mthode
getSlug() de la classe JobeetCategory na plus raison dexister et conduit
donc son retrait. Pour finaliser la mise en place du slug, lensemble du
modle ainsi que la base de donnes doivent tre reconstruits laide de la
tche doctrine:build-all-reload. Au chargement des donnes initiales
de test, le champ slug se verra automatiquement renseign par Doctrine.
Cration de la vue de dtail de la catgorie
Mise en place de laction executeShow()
La route category dclare plus haut dans le fichier de configuration
routing.yml sappuie sur laction show du module category. Pour lins-
tant, la mthode executeShow() ne figure pas dans la classe des actions
du module. Le corps de cette mthode se rsume une seule et unique
JobeetCategory:
actAs:
Timestampable: ~
Sluggable:
fields: [name]
columns:
name:
type: string(255)
notnull: true
$ php symfony doctrine:build-all-reload -no-confirmation
CHOIX DE CONCEPTION Crer un nouveau
module category ddi ou ajouter
une action au module job ?
Pourquoi ne pas ajouter une action category au
module job ? Cest en fait parce que le sujet prin-
cipal est la catgorie elle-mme. Pour cette raison,
il semble plus naturel et logique de crer un
module category ddi. Il y a en effet plus de
sens considrer la liste des catgories comme
une entit part entire, plutt que comme une
petite partie dun module dj existant. Savoir
donner du sens la conception gnrale de lappli-
cation et au code crit nest pas chose facile mais
se rvle particulirement important.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
120
ligne de code qui a dj t tudie prcdemment dans cet ouvrage. Il
sagit en effet de rcuprer lobjet JobeetCategory partir de sa route. Le
code ci-dessous prsente le contenu du fichier actions.class.php du
module.
Contenu du fichier apps/frontend/modules/category/actions/actions.class.php
La mthode index() gnre par dfaut lors de la cration du module a
t volontairement supprime du fichier car elle ne sera pas utilise dans
la suite du dveloppement. De ce fait, son template associ
indexSuccess.php peut son tour tre retir du projet. Aprs la mise en
uvre de laction executeShow(), il ne reste plus qu crire le template
correspondant showSuccess.php.
Intgration du template showSuccess.php associ
La finalisation de la nouvelle page de dtail de la catgorie requiert vi-
demment lcriture dun fichier de template showSuccess.php. Ce der-
nier rutilise tel quel le tableau HTML du template indexSuccess.php
du module job pour afficher la liste des offres demploi actives.
Contenu du fichier apps/frontend/modules/category/templates/showSuccess.php
class categoryActions extends sfActions
{
public function executeShow(sfWebRequest $request)
{
$this->category = $this->getRoute()->getObject();
}
}
<?php use_stylesheet('jobs.css') ?>
<?php slot('title', sprintf('Jobs in the %s category',
$category->getName())) ?>
<div class="category">
<div class="feed">
<a href="">Feed</a>
</div>
<h1><?php echo $category ?></h1>
</div>
<table class="jobs">
<?php foreach ($category->getActiveJobs() as $i => $job): ?>
<tr class="<?php echo fmod($i, 2) ? 'even' : 'odd' ?>">
<td class="location">
<?php echo $job->getLocation() ?>
</td>
7


C
o
n
c
e
v
o
i
r

e
t

p
a
g
i
n
e
r

l
a

l
i
s
t
e

d

o
f
f
r
e
s

d

u
n
e

c
a
t

g
o
r
i
e
Groupe Eyrolles, 2008
121
Bien quil soit entirement fonctionnel, ce fichier dispose dun inconvnient
majeur : il duplique le code du template indexSuccess.php du module job.
La philosophie du framework Symfony favorise autant que possible la
sparation du code en plusieurs couches distinctes grce au modle MVC,
mais aussi lisolement du code dans le but dviter la duplication.
La duplication de code ne concerne pas seulement le code PHP, elle
impacte galement le code HTML, ce qui complexifie davantage les
dveloppements et la maintenance de lapplication. La section suivante
prsente quelle solution technique le framework Symfony met en uvre
pour favoriser la factorisation et lisolement du code HTML.
Isoler le HTML redondant dans les templates partiels
Dcouvrir le principe de templates partiels
Le template PHP showSuccess.php du module category copie lintgra-
lit de la balise <table> gnrant la liste doffres demploi du fichier
indexSuccess.php. Bien entendu, cette duplication du code va
lencontre des principes fondamentaux et des bonnes pratiques dfendus
jusqu maintenant. Il est donc temps dapprendre comment remdier
cette problmatique.
Lorsquune petite partie dun template a besoin dtre duplique pour
tre rutilise ailleurs dans un autre, cest le signe quil faut lisoler dans
un fichier part. Dans Symfony, on parle de crer un template partiel
(dit partial). Un partiel est un fragment de code dun template qui a pour
objectif dtre partag par plusieurs autres templates. Dun point de vue
technique, il sagit en ralit de crer un fichier PHP dont le nom dbute
par un tiret soulign (ou underscore, caractre _) et de lenregistrer dans
les rpertoires templates/ du projet.
<td class="position">
<?php echo link_to($job->getPosition(), 'job_show_user',
$job) ?>
</td>
<td class="company">
<?php echo $job->getCompany() ?>
</td>
</tr>
<?php endforeach; ?>
</table>
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
122
Cration dun template partiel _list.php pour les modules job
et category
Lexplication prcdente a introduit la notion de templates partiels.
Comment cela se prsente-t-il plus concrtement dans un projet
Symfony ? Comme il la t dmontr juste avant, la gnration de la
liste des offres demploi dune catgorie dans les templates
showSuccess.php et indexSuccess.php est strictement identique. Le code
dupliqu doit donc tre isol dans un template partiel _list.php du
module job comme le montre le code ci-dessous.
Contenu du fichier apps/frontend/modules/job/templates/_list.php
Faire appel au partiel dans un template
Lutilisation dun template partiel dans un template traditionnel est ra-
lise au moyen du helper Symfony include_partial(). Cette fonction
accepte deux paramtres. Le premier est le nom du partiel appeler. Il
sagit dune chane de caractres qui se compose du nom du module dans
lequel se trouve le partiel, puis dune barre oblique / et enfin du nom du
partiel auquel le tiret soulign de dbut est omis. Le second argument de
include_partial() est un tableau associatif des variables transmettre
au partiel. Les cls du tableau correspondent au nom des variables du
partiel, et les valeurs sont les variables transmettre au partiel. Un bon
exemple valant mieux quun long discours, le code ci-dessous rsume le
fonctionnement du helper include_partial().
Les dveloppeurs familiers du langage PHP se demandent certainement
quelle est la vritable diffrence entre le helper include_partial() et un
simple appel include() ou require(). Le premier avantage concerne le
<table class="jobs">
<?php foreach ($jobs as $i => $job): ?>
<tr class="<?php echo fmod($i, 2) ? 'even' : 'odd' ?>">
<td class="location">
<?php echo $job->getLocation() ?>
</td>
<td class="position">
<?php echo link_to($job->getPosition(), 'job_show_user',
$job) ?>
</td>
<td class="company">
<?php echo $job->getCompany() ?>
</td>
</tr>
<?php endforeach; ?>
</table>
<?php include_partial('job/list', array('jobs' => $jobs)) ?>
7


C
o
n
c
e
v
o
i
r

e
t

p
a
g
i
n
e
r

l
a

l
i
s
t
e

d

o
f
f
r
e
s

d

u
n
e

c
a
t

g
o
r
i
e
Groupe Eyrolles, 2008
123
nommage des variables transmises au partiel. En effet, il nest pas nces-
saire que ces dernires aient le mme nom que celles du partiel. Cest le
tableau associatif pass en second argument qui se charge de faire la cor-
respondance. Dautre part, le helper include_partial() a lavantage de
pouvoir mettre en cache le template partiel quil appelle.
Utiliser le partiel _list.php dans les templates indexSuccess.php
et showSuccess.php
Le template partiel _list.php est prt et nattend qu tre implment
dans un template. De lautre ct, les vues showSuccess.php et
indexSuccess.php des modules category et job nattendent qu tre
simplifies grce ce dernier. Les bouts de code ci-dessous sont les
appels au partiel intgrer aux vues PHP prcdentes la place du code
PHP qui gnre la liste des offres demploi.
Code ajouter au fichier apps/frontend/modules/job/templates/indexSuccess.php
Code ajouter au fichier apps/frontend/modules/category/templates/
showSuccess.php
Paginer une liste dobjets Doctrine
Plus le site volue, plus il grandit et plus son contenu senrichit. Cest
dautant plus vrai lorsque ce sont les utilisateurs eux-mmes qui sont la
source de ce dernier. Pour cette raison, lintgralit du contenu ditorial
doit tre clairement organise dans le but de faciliter la navigation et
lexprience utilisateur. Cela dbute naturellement par la pagination des
listes de rsultats rcuprs de la base de donnes. Cette section explique
pas pas le processus de cration dune liste pagine doffres demploi
pour chaque catgorie.
Que sont les listes pagines et quoi servent-elles ?
Paginer une liste dobjets en provenance dune base de donnes est une
tche rcurrente dans les projets informatiques. Les listes pagines ser-
vent par exemple allger le flux dinformations qui transitent sur le
rseau, optimiser le nombre de donnes affiches simultanment sur
<?php include_partial('job/list', array('jobs' => $category
->getActiveJobs(sfConfig::get('app_max_jobs_on_homepage')))) ?>
<?php include_partial('job/list', array('jobs' => $category
->getActiveJobs())) ?>
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
124
lcran de lutilisateur, ou bien encore rduire les temps de chargement
des pages En somme, elles amliorent le confort dutilisation et
lexprience utilisateur.
Bien que ces listes soient courantes, elles nen demeurent pas moins
complexes et fastidieuses mettre en uvre car elles ncessitent le fran-
chissement de plusieurs tapes successives, telles que la dtermination de
la page courante, le calcul du dbut de la position du curseur dans la liste
de rsultats, le comptage des objets rcuprer, la rcupration des don-
nes, laffichage des liens de pagination
Prparer la pagination laide de sfDoctrinePager
Heureusement, Symfony intgre un composant natif orient objet qui
facilite considrablement la cration de listes pagines de rsultats issus
dune base de donnes. Il sagit de lobjet sfDoctrinePager qui a pour
rle de grer dynamiquement la pagination dune liste dobjets Doctrine.
Laction show du module category est particulirement concerne par la
mise en uvre dun tel mcanisme dans la mesure o le nombre doffres
demploi publies risque de crotre vive allure.
Initialiser la classe de modle et le nombre maximum dobjets
par page
Pour viter de saturer inutilement la page et faire fuir lutilisateur, la liste
des offres doit tre pagine, et des liens en bas de page doivent permettre
de naviguer dans les offres de manire antchronologique. Le code ci-
dessous dtaille la marche suivre pour configurer la pagination dune
liste dobjets Doctrine laide du composant sfDoctrinePager.
Dfinition dun objet sfDoctrinePager dans apps/frontend/modules/category/
actions/actions.class.php
public function executeShow(sfWebRequest $request)
{
$this->category = $this->getRoute()->getObject();
$this->pager = new sfDoctrinePager(
'JobeetJob',
sfConfig::get('app_max_jobs_on_category')
);
$this->pager->setQuery($this->category
->getActiveJobsQuery());
$this->pager->setPage($request->getParameter('page', 1));
$this->pager->init();
}
7


C
o
n
c
e
v
o
i
r

e
t

p
a
g
i
n
e
r

l
a

l
i
s
t
e

d

o
f
f
r
e
s

d

u
n
e

c
a
t

g
o
r
i
e
Groupe Eyrolles, 2008
125
Le constructeur de la classe sfDoctrinePager accepte deux arguments.
Le premier est le nom de la classe modle des objets rcuprer. Dans le
cas prsent, il sagit de la classe JobeetJob. Le second est le nombre
maximum dobjets prsents sur chaque page.
Pour des raisons videntes de personnalisation ultrieure, cette valeur est
stocke dans une constante de configuration du fichier app.yml. Il sera
ainsi plus ais dajuster le nombre maximum dobjets par page si besoin.
Contenu du fichier apps/frontend/config/app.yml
Spcifier lobjet Doctrine_Query de slection des rsultats
Par ailleurs, la mthode setQuery() de lobjet sfDoctrinePager reoit en
paramtre un objet de type Doctrine_Query. Celui-ci correspond effecti-
vement la requte SQL excuter pour rapatrier la liste dobjets Doc-
trine de la base de donnes.
Passer une Doctrine_Query en argument permet ainsi dobtenir la libert
de crer des requtes aussi bien simples que complexes. Cest l lune des
forces et toute la souplesse de lORM Doctrine. Dans le cas prsent,
lobjet Doctrine_Query est issu de la mthode getActiveJobsQuery() de
la classe JobeetCategory. Cette mthode est implmente quelques
lignes plus bas.
Configurer le numro de la page courante de rsultats
Enfin, lobjet sfDoctrinePager a besoin de connatre le numro de la page
courante afin de dterminer quels enregistrements il doit rcuprer et com-
bien il y a de pages au total. La mthode setPage() accepte comme seul et
unique argument le numro de la page courante. Il est ici fourni laide de
la mthode getParameter() de lobjet sfRequest. Dans le cas prsent, le
numro de la page en cours est transmis par lURL dans la variable page.
Le second argument de la mthode getParameter() correspond la valeur
par dfaut retourner si la variable page nexiste pas ou est nulle.
Initialiser le composant de pagination
Enfin, la mthode init() de lobjet sfDoctrinePager se charge dinitialiser
la liste pagine. ce stade, elle calcule le nombre de pages total, la position
du curseur dans la liste ainsi que le nombre exact de rsultats paginer.
all:
active_days: 30
max_jobs_on_homepage: 10
max_jobs_on_category: 20
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
126
Simplifier les mthodes de slection des rsultats
Implmenter la mthode getActiveJobsQuery de lobjet
JobeetCategory
Comme il la t dcrit plus haut, le fonctionnement de lobjet
sfDoctrinePager repose sur lutilisation dun objet de type
Doctrine_Query pour effectuer le comptage du nombre total de rsultats,
ainsi que pour slectionner les vingt objets par page. Pour linstant, la
mthode getActiveJobsQuery() de la classe JobeetCategory reste
implmenter. Cette dernire retourne la requte de slection de toutes
les offres actives de la catgorie courante tries par ordre antchronolo-
gique suivant leur date dexpiration. Le code ci-dessous dtaille limpl-
mentation de cette mthode.
Mthode getActiveJobsQuery() du fichier lib/model/doctrine/
JobeetCategory.class.php
Remanier les mthodes existantes de JobeetCategory
Maintenant que la mthode getActiveJobsQuery() est clairement dfinie,
on peut se poser la question de savoir dans quelle mesure elle peut tre ruti-
lise par dautres mthodes de la classe. En effet, la requte SQL reprsente
par lobjet Doctrine_Query quelle renvoie est suffisamment gnrique et
surtout commune aux mthodes getActiveJobs() et countActiveJobs()
pour quil soit intressant de procder un lger remaniement du code de
ces dernires, afin de les simplifier en faisant en sorte quelles implmentent
cette nouvelle mthode. prsent, les deux mthodes getActiveJobs() et
countActiveJobs() se rsument au code ci-aprs.
public function getActiveJobsQuery()
{
$q = Doctrine_Query::create()
->from('JobeetJob j')
->where('j.category_id = ?', $this->getId());
return Doctrine::getTable('JobeetJob')
->addActiveJobsQuery($q);
}
7


C
o
n
c
e
v
o
i
r

e
t

p
a
g
i
n
e
r

l
a

l
i
s
t
e

d

o
f
f
r
e
s

d

u
n
e

c
a
t

g
o
r
i
e
Groupe Eyrolles, 2008
127
Refactorisation de mthodes dans le fichier lib/model/doctrine/JobeetCategory.class.php
La dernire tape ncessaire la finalisation de la pagination de la liste des
offres demploi de la catgorie courante est bien videmment lintgration
des liens de navigation lintrieur du template showSuccess.php. La
partie suivante explique tout cela avant de clturer ce chapitre.
Intgrer les lments de pagination dans le template
showSuccess.php
Le template showSuccess.php doit implmenter plusieurs lments afin
dtre complet. Les lignes qui suivent expliquent pas pas les morceaux
de code ajouter ou modifier dans le template pour y parvenir. La pre-
mire tape consiste mettre jour lappel au partiel _list.php.
Passer la collection dobjets Doctrine au template partiel
Tout dabord, le template showSuccess.php a besoin de mettre jour
lappel au template partiel _list.php car la variable qui lui est transmise
jusqu prsent nexiste plus. En effet, la vue de laction show reoit main-
tenant de la part de laction la variable $pager qui contient lensemble de
la pagination, y compris la collection dobjets Doctrine passer au tem-
plate partiel.
Pour y parvenir, lobjet sfDoctrinePager intgre la mthode
getResults() qui se charge dexcuter la requte SQL de lobjet
Doctrine_Query, puis dhydrater la collection dobjets JobeetJob avant de
la stocker dans une proprit prive et enfin de la retourner. Cette
mthode renvoie un objet Doctrine_Collection qui est affect la cl
jobs du tableau associatif comme le montre le code ci-dessous.
Appel au template partiel remplacer dans le fichier apps/frontend/modules/
category/templates/showSuccess.php
public function getActiveJobs($max = 10)
{
return $this->getActiveJobsQuery()->limit($max)->execute();
}
public function countActiveJobs()
{
return $this->getActiveJobsQuery()->count();
}
<?php include_partial('job/list',
array('jobs' => $pager->getResults())) ?>
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
128
Afficher les liens de navigation entre les pages
Ltape suivante consiste afficher les liens permettant lutilisateur de
naviguer entre les diffrentes pages de la catgorie en cours. Une fois de
plus, cest grce au composant sfDoctrinePager et ses mthodes trs
pratiques que lon est capable de gnrer un systme de navigation com-
plet sans se poser de questions.
Le code suivant est placer tout en bas de la vue showSuccess.php. Il
gnre un systme de pagination intgrant les liens pour accder aux
premire et dernire pages, aux pages prcdente et suivante, ainsi quaux
pages intermdiaires en prenant garde dsactiver le lien de la page sur
laquelle se trouve lutilisateur. La description de chacune des mthodes
de lobjet de pagination appel, est explique plus loin.
Cration dun systme de pagination complet dans le fichier apps/frontend/
modules/category/templates/showSuccess.php
<?php if ($pager->haveToPaginate()): ?>
<div class="pagination">
<a href="<?php echo url_for('category', $category) ?>?page=1">
<img src="/images/first.png" alt="First page" />
</a>
<a href="<?php echo url_for('category', $category) ?>?page=
X <?php echo $pager->getPreviousPage() ?>">
<img src="/images/previous.png" alt="Previous page"
title="Previous page" />
</a>
<?php foreach ($pager->getLinks() as $page): ?>
<?php if ($page == $pager->getPage()): ?>
<?php echo $page ?>
<?php else: ?>
<a href="<?php echo url_for('category', $category) ?>?page=
X <?php echo $page ?>"><?php echo $page ?></a>
<?php endif; ?>
<?php endforeach; ?>
<a href="<?php echo url_for('category', $category) ?>?page=
X <?php echo $pager->getNextPage() ?>">
<img src="/images/next.png" alt="Next page"
title="Next page" />
</a>
<a href="<?php echo url_for('category', $category) ?>?page=
X <?php echo $pager->getLastPage() ?>">
<img src="/images/last.png" alt="Last page"
title="Last page" />
</a>
</div>
<?php endif; ?>
7


C
o
n
c
e
v
o
i
r

e
t

p
a
g
i
n
e
r

l
a

l
i
s
t
e

d

o
f
f
r
e
s

d

u
n
e

c
a
t

g
o
r
i
e
Groupe Eyrolles, 2008
129
Afficher le nombre total doffres publies et de pages
Enfin, lidal est dinformer lutilisateur du nombre total doffres
demploi publies dans la catgorie courante ainsi que le nombre
maximum de pages quil est en mesure de parcourir. Un petit indicateur
supplmentaire lui permet de savoir tout moment sur quelle page il se
trouve par rapport aux autres comme le montre le bout de code ci-des-
sous placer en bas du template.
Indicateurs du nombre total de rsultats et de pages dans le fichier apps/frontend/
modules/category/templates/showSuccess.php
Description des mthodes de lobjet sfDoctrinePager utilises
dans le template
Lobjet sfDoctrinePager fournit un certain nombre de mthodes particu-
lirement utiles pour retrouver des informations sur ltat de la pagination.
Cela va du numro de la page courante au nombre total de rsultats, en pas-
sant par les numros des pages suivantes et prcdentes. Toutes les
mthodes listes et dcrites ci-dessous figurent dans le template
showSuccess.php et ont permis llaboration de la pagination de ce dernier.
getResults() : retourne une collection dobjets Doctrine pour la page
courante ;
getNbResults() : retourne le nombre total de rsultats ;
haveToPaginate() : retourne vrai sil y a plus quune page ;
getLinks() : retourne la liste des liens des pages afficher ;
getPage() : retourne le numro de la page courante ;
getPreviousPage() : retourne le numro de la page prcdente ;
getNextPage() : retourne le numro de la page suivante ;
getLastPage() : retourne le numro de la dernire page.
<div class="pagination_desc">
<strong><?php echo $pager->getNbResults() ?></strong> jobs in
this category
<?php if ($pager->haveToPaginate()): ?>
- page <strong><?php echo $pager->getPage() ?>/<?php echo
$pager->getLastPage() ?></strong>
<?php endif; ?>
</div>
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
130
Code final du template showSuccess.php
Contenu du fichier apps/frontend/modules/category/templates/showSuccess.php
<?php use_stylesheet('jobs.css') ?>
<?php slot('title', sprintf('Jobs in the %s category', $category-
>getName())) ?>
<div class="category">
<div class="feed">
<a href="">Feed</a>
</div>
<h1><?php echo $category ?></h1>
</div>
<?php include_partial('job/list',
array('jobs' => $pager->getResults())) ?>
<?php if ($pager->haveToPaginate()): ?>
<div class="pagination">
<a href="<?php echo url_for('category', $category) ?>?page=1">
<img src="/images/first.png" alt="First page" />
</a>
<a href="<?php echo url_for('category', $category)?>?page=
<?php echo $pager->getPreviousPage() ?>">
<img src="/images/previous.png" alt="Previous page"
title="Previous page" />
</a>
<?php foreach ($pager->getLinks() as $page): ?>
<?php if ($page == $pager->getPage()): ?>
<?php echo $page ?>
<?php else: ?>
<a href="<?php echo url_for('category', $category) ?>?page=
<?php echo $page ?>"><?php echo $page ?></a>
<?php endif; ?>
<?php endforeach; ?>
<a href="<?php echo url_for('category', $category) ?>?page=
<?php echo $pager->getNextPage() ?>">
<img src="/images/next.png" alt="Next page" title="Next page" />
</a>
<a href="<?php echo url_for('category', $category) ?>?page=
<?php echo $pager->getLastPage() ?>">
<img src="/images/last.png" alt="Last page" title="Last page" />
</a>
</div>
<?php endif; ?>
<div class="pagination_desc">
<strong><?php echo $pager->getNbResults() ?></strong> jobs in
this category
7


C
o
n
c
e
v
o
i
r

e
t

p
a
g
i
n
e
r

l
a

l
i
s
t
e

d

o
f
f
r
e
s

d

u
n
e

c
a
t

g
o
r
i
e
Groupe Eyrolles, 2008
131
En rsum
Si vous aviez travaill sur votre propre implmentation et si vous avez res-
senti que vous navez pas appris grand chose aprs ces quelques pages, cela
signifie que vous vous tes progressivement habitu la philosophie du fra-
mework Symfony. Le processus dajout dune nouvelle fonctionnalit un
site web Symfony reste toujours le mme : songer dabord aux URLs, crer
les actions, mettre jour le modle et enfin crire quelques templates. Et, si
vous pouvez appliquer quelques bonnes pratiques de dveloppement au pas-
sage, vous matriserez trs vite le framework. Le chapitre suivant sintresse
un sujet cl du dveloppement dapplications web professionnelles, savoir
la notion de tests unitaires et fonctionnels automatiss. Le prochain chapitre
se focalise sur les tests unitaires tandis que les tests fonctionnels seront pr-
sents en dtail dans le chapitre 9
<?php if ($pager->haveToPaginate()): ?>
- page <strong><?php echo $pager->getPage() ?>/<?php
echo $pager->getLastPage() ?></strong>
<?php endif; ?>
</div>
Figure 72
Rsultat de la pagination
de la liste des offres demploi
Groupe Eyrolles, 2008
chapitre 8
Groupe Eyrolles, 2008
Les tests unitaires
Trop souvent ngligs, oublis, voire laisss de ct
faute de temps, les tests unitaires sont pourtant des garants
de la qualit et de la prennit dune application web.
Symfony intgre nativement un framework de tests unitaires
et fonctionnels. Ce huitime chapitre se consacre
la dcouverte du premier et lcriture des premiers
tests automatiss de Jobeet.
MOTS-CLS :
BTests unitaires
BFramework Lime
BCouverture de code
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
134
Les deux prcdents chapitres ont permis de revoir lensemble des fonc-
tionnalits acquises jusqu prsent, den personnaliser certaines mais
aussi den ajouter dautres.
Ce chapitre aborde quelque chose de compltement diffrent : les tests
automatiss. Dans la mesure o ce sujet est particulirement large, deux
chapitres entiers y sont consacrs afin de pouvoir couvrir lessentiel des
fondamentaux.
Prsentation des types de tests dans
Symfony
Il existe deux types de tests automatiss dans Symfony : les tests uni-
taires et les tests fonctionnels.
Les tests unitaires vrifient que chaque fonction ou mthode fonctionne
correctement. Chaque test doit tre aussi indpendant que possible des
autres.
En revanche, les tests fonctionnels sassurent que lapplication rsultante
se comporte correctement dans sa globalit.
Dans un projet Symfony, tous les tests se situent dans le rpertoire test/
du projet. Il contient deux sous-dossiers : un pour les tests unitaires
(test/unit/) et lautre pour les tests fonctionnels (test/functional/).
Ce chapitre naborde que les tests unitaires tandis que les tests fonction-
nels seront prsents au chapitre suivant.
De la ncessit de passer par des tests
unitaires
crire des tests unitaires est probablement lune des bonnes pratiques les
plus importantes du dveloppement web mettre en application. En
effet, les dveloppeurs ne sont pas toujours sensibiliss tester leur tra-
vail. Ainsi, cela permet de soulever plusieurs interrogations : faut-il
crire des tests avant dimplmenter une fonctionnalit ? Quels outils
sont ncessaires pour tester efficacement ? Les tests ont-ils besoin de
couvrir tous les cas possibles ? Comment sassurer que tout lensemble
du projet est bien test ? Cependant, la toute premire question qui se
pose est gnralement beaucoup plus triviale : par o commencer ?
8


L
e
s

t
e
s
t
s

u
n
i
t
a
i
r
e
s
Groupe Eyrolles, 2008
135
Si tester massivement est toujours une bonne pratique, lapproche de
Symfony nen demeure pas moins pragmatique : il est en effet prfrable
davoir seulement quelques tests sous la main plutt que rien du tout
Le projet a-t-il dj beaucoup de code non test ? Il nest pas ncessaire
davoir une suite complte de tests pour profiter des avantages des tests
automatiss. Dans un premier temps, il est bon de commencer par
ajouter des tests chaque fois quun bogue est dcouvert. Au fil du
temps, le code samliore, la couverture de code slargit, et la confiance
en celui-ci crot. Tout cela est rendu possible grce cette approche
pragmatique. Ltape suivante consiste donc crire des tests lors de
limplmentation de nouvelles fonctionnalits. Ces derniers se montre-
ront trs vite indispensables dans la suite du projet !
Le problme avec la plupart des librairies de tests reste leur courbe
dapprentissage particulirement raide. Cest pourquoi Symfony fournit
une librairie de tests trs simple, lime, afin de simplifier lcriture de tests.
Si ce chapitre dcrit en profondeur la librairie intgre lime, rien
nempche den utiliser une autre comme lexcellent PHPUnit.
Prsentation du framework de test lime
Initialisation dun fichier de tests unitaires
Tous les tests crits partir du framework lime dbutent avec le mme
code :
Tout dabord, le fichier dinitialisation unit.php est inclus afin de
charger la configuration du projet et de la bibliothque lime. Puis, un
nouvel objet lime_test est cr, et le nombre de tests excuter est
pass comme argument au constructeur de la classe. Le nombre de tests
unitaires prvus permet lime dimprimer un message derreur en sortie
au cas o trop peu de tests seraient excuts (par exemple, lorsquun test
provoque une erreur fatale PHP).
Dcouverte des outils de tests de lime
Tester les fonctionnements dune mthode ou bien dune fonction,
cest appeler cette dernire avec des points dentre prdfinis, puis com-
parer la valeur quelle retourne avec la valeur de la sortie attendue .
Cette comparaison dtermine alors si un test passe ou bien sil choue.
require_once dirname(__FILE__).'/../bootstrap/unit.php';
$t = new lime_test(1, new lime_output_color());
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
136
Pour faciliter cette comparaison, lobjet lime_test fournit plusieurs
mthodes :
Le fait que lime dfinisse autant de mthodes de test peut paratre trange
dans la mesure o tous les tests peuvent tre crits laide de la mthode
ok(). Lavantage de ces mthodes alternatives rside dans les messages
derreur beaucoup plus explicites quelles produisent lorsque le test choue.
De plus, elles permettent de faciliter la relecture des tests unitaires. Le
tableau suivant liste quelques mthodes utiles de lobjet lime_test.
Enfin, la mthode comment($msg) imprime un commentaire en sortie
mais nexcute aucun test.
Excuter une suite de tests unitaires
Tous les tests unitaires sont stocks dans le rpertoire test/unit. Par
convention, les tests sont nomms avec le nom de la classe (ou de la
fonction) quils testent, suffixs par Test. Sil est toujours possible
dorganiser les fichiers du rpertoire test/unit sa guise, il est conseill
de rpliquer la structure du rpertoire lib/.
Afin dillustrer la mise en application des tests unitaires, la classe Jobeet
sera teste via un nouveau fichier test/unit/JobeetTest.php qui con-
tient le code suivant :
Tableau 81 Liste des mthodes disponibles de lobjet lime_test
Nom de la mthode Description
ok($test) Teste une condition et passe si elle est vrifie
is($value1, $value2) Compare deux valeurs et passe si elles sont gales (==)
isnt($value1, $value2) Compare deux valeurs et passent si elles sont diffrentes (!=)
like($string, $regexp) Teste si la chane correspond lexpression rgulire
unlike($string, $regexp) Teste si la chane ne correspond pas lexpression rgulire
is_deeply($array1, $array2) Vrifie que deux tableaux ont les mmes valeurs
Tableau 82 Liste des mthodes de test de lobjet lime_test
Nom de la mthode Description
fail() choue toujours pratique pour tester des exceptions
pass() Passe toujours pratique pour tester des exceptions
skip($msg, $nb_tests) Compte comme $nb_tests pratique pour les tests conditionnels
todo() Compte comme un test pratique pour les tests non encore crits
8


L
e
s

t
e
s
t
s

u
n
i
t
a
i
r
e
s
Groupe Eyrolles, 2008
137
Contenu du fichier test/unit/JobeetTest.php
Il existe deux manires de lancer les tests. La premire consiste ex-
cuter directement le fichier PHP laide du binaire php :
La seconde mthode permet quant elle dexcuter la suite de tests uni-
taires grce la commande test:unit.
Sous Windows, la ligne de commande ne permet malheureusement pas
de mettre en vidence les rsultats des tests en vert et rouge.
Tester unitairement la mthode slugify()
Afin dillustrer plus concrtement les principes de tests unitaires, cest la
mthode Jobeet::slugify() qui sera teste dans un premier temps. Il
sagit en effet de montrer les outils du framework lime ainsi que les
bonnes pratiques mettre en uvre lorsque lon teste du code.
Dterminer les tests crire
La mthode slugify() a t cre au cinquime jour pour nettoyer une
chane afin que celle-ci soit utilise en toute scurit dans une URL. La
modification de la chane dorigine consiste en quelques transformations
basiques comme la conversion des caractres non ASCII par un tiret (-)
ou le passage de la chane en lettres minuscules.
require_once dirname(__FILE__).'/../bootstrap/unit.php';
$t = new lime_test(1, new lime_output_color());
$t->pass('This test always passes.');
$ php test/unit/JobeetTest.php
$ php symfony test:unit
Figure 81
Rsultat dexcution dune suite de tests unitaires
Tableau 83 Tableau de prsentation du fonctionnement de la mthode slugify
Entre Sortie
Sensio Labs sensio-labs
Paris, France paris-france
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
138
crire les premiers tests unitaires de la mthode
Il est temps de contrler que la mthode slugify() actuelle de la classe
Jobeet ralise correctement la transformation de la chane de caractres
qui lui est passe en argument. Pour ce faire, le contenu du fichier de test
doit tre remplac par celui-ci :
Contenu du fichier test/unit/JobeetTest.php
En prtant attention aux tests crits, il est important de constater que
chaque ligne teste seulement un cas particulier. Cest un principe dont il
faut toujours se souvenir lors de lcriture de tests unitaires. Il est impor-
tant de tester une seule chose la fois !
Il ne reste maintenant plus qu excuter le fichier de tests. Si tous les tests
passent comme prvu, la barre verte fera son apparition. Dans le cas con-
traire, ce sera la barre rouge qui alertera que certains tests ont chou.
Si un test choue, la sortie donnera quelques informations pour en connatre
les raisons. Cependant, si le fichier contient une centaine de tests, il sera
probablement difficile didentifier rapidement le comportement inhabituel.
Commenter explicitement les tests unitaires
Toutes les mthodes de tests de lime prennent une chane comme der-
nier argument qui sert de description pour un test. Cest l un outil trs
pratique dans la mesure o cela oblige le dveloppeur dcrire lui-mme
la portion de code teste. Cela peut galement servir de documentation
du comportement attendu dune mthode. Le code ci-dessous illustre un
require_once dirname(__FILE__).'/../bootstrap/unit.php';
$t = new lime_test(6, new lime_output_color());
$t->is(Jobeet::slugify('Sensio'), 'sensio');
$t->is(Jobeet::slugify('sensio labs'), 'sensio-labs');
$t->is(Jobeet::slugify('sensio labs'), 'sensio-labs');
$t->is(Jobeet::slugify('paris,france'), 'paris-france');
$t->is(Jobeet::slugify(' sensio'), 'sensio');
$t->is(Jobeet::slugify('sensio '), 'sensio');
Figure 82
Rsultat dexcution
des tests unitaires de Jobeet::slugify()
8


L
e
s

t
e
s
t
s

u
n
i
t
a
i
r
e
s
Groupe Eyrolles, 2008
139
exemple dune srie de tests de la mthode slugify(), commente par de
courtes phrases :
require_once dirname(__FILE__).'/../bootstrap/unit.php';
$t = new lime_test(6, new lime_output_color());
$t->comment('::slugify()');
$t->is(Jobeet::slugify('Sensio'), 'sensio', '::slugify()
converts all characters to lower case');
$t->is(Jobeet::slugify('sensio labs'), 'sensio-labs',
'::slugify() replaces a white space by a -');
$t->is(Jobeet::slugify('sensio labs'), 'sensio-labs',
'::slugify() replaces several white spaces by a single -');
$t->is(Jobeet::slugify(' sensio'), 'sensio', '::slugify()
removes - at the beginning of a string');
$t->is(Jobeet::slugify('sensio '), 'sensio', '::slugify()
removes - at the end of a string');
$t->is(Jobeet::slugify('paris,france'), 'paris-france',
'::slugify() replaces non-ASCII characters by a -');
Figure 83
Commentaires descriptifs des tests unitaires de Jobeet::slugify()
REMARQUE La couverture de code
Lorsque lon crit des tests, il arrive souvent den oublier pour certaines parties du code.
Pour aider vrifier que tout le code est bien test, Symfony fournit la tche
test:coverage. En passant comme arguments de cette commande un fichier de test, ou
un rpertoire et un fichier, ou encore un rpertoire de bibliothque, cette dernire renverra en
rsultat le taux de couverture global du code.
$ php symfony test:coverage test/unit/JobeetTest.php
lib/Jobeet.class.php
Si lon souhaite savoir quelles lignes ne sont pas couvertes par les tests, il suffit de passer
loption -detailed.
$ php symfony test:coverage --detailed test/unit/JobeetTest.php
lib/Jobeet.class.php
Toutefois, il est trs important de garder lesprit que lorsque la tche indique que le code est
compltement test unitairement, cela signifie seulement que chaque ligne a t excute
mais pas que tous les cas possibles ont t tests.
Comme la tche test:coverage dpend de XDebug pour rcolter ces informations,
lextension doit imprativement tre installe et active en premier lieu.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
140
La chane de description du test est aussi un outil prcieux lorsque lon
essaie de savoir ce quil faut tester. En effet, elle doit permettre dexpli-
quer succinctement comment doit se comporter la fonction ou la
mthode teste. Ce bref rsum du test suit un formalisme bien dfini
pour faciliter la lecture des rsultats. Les tests de mthodes statiques
dbutent tous par :: suivis du nom de la mthode, tandis que les
mthodes classiques dinstance sont prfixes par ->.
Implmenter de nouveaux tests unitaires
au fil du dveloppement
Ajouter des tests pour les nouvelles fonctionnalits
Le slug dune chane vide est une chane vide. Un test unitaire peut le
vrifier. Or, une chane vide dans une URL nest pas conseille. La
mthode slugify() doit tre modifie afin quelle retourne la chane n-a
en cas de chane vide.
La rsolution de ce cas critique peut tre ralise en crivant le test avant
limplmentation de la fonctionnalit ou bien aprs. Cest simplement
une question de got. Nanmoins, crire le test avant permet davoir la
certitude que le code implmente ce qui a t prvu.
prsent, en excutant la suite de tests unitaires, la barre rouge fait son
apparition. Si ce nest pas le cas, soit la fonctionnalit est dj impl-
mente, soit le test ne contrle pas ce quil est suppos tester. Le code
suivant corrige le slug rsultant lorsquune chane de caractres vide est
passe en argument.
Dbut de la mthode slugify() de la classe lib/Jobeet.class.php
$t->is(Jobeet::slugify(''), 'n-a', '::slugify() converts the
empty string to n-a');
static public function slugify($text)
{
if (empty($text))
{
return 'n-a';
}
// ...
}
8


L
e
s

t
e
s
t
s

u
n
i
t
a
i
r
e
s
Groupe Eyrolles, 2008
141
Le test doit maintenant passer comme prvu, et afficher la barre verte
tant attendue, condition davoir pens mettre jour le nombre de
tests planifis dans le constructeur de lobjet lime_test. Si ce nest pas le
cas, il y aura un message informant que 6 tests ont t planifis mais que
7 ont t excuts au total. Garder le compteur de tests planifis jour
est important car il permet de tenir le dveloppeur inform si le script de
test meurt trop tt.
Ajouter des tests suite la dcouverte dun bug
Il est possible quun utilisateur du site rapporte un bug lors de sa naviga-
tion sur le site : les liens prsentant certaines offres demploi pointent
vers une page derreur 404. Cette erreur provient du fait que des offres
possdent un slug dont le nom de la socit, le poste ou la situation go-
graphique est vide. Aprs vrification, aucun champ de la base de don-
nes na pourtant t laiss vide.
Ce bogue survient en fait cause dun problme dans la mthode sta-
tique slugify(). En effet, lorsque la chane passe en paramtre ne con-
tient que des caractres dits non-ASCII , cette mthode convertit la
chaine en une chane vide. Un rflexe bien naturel serait de corriger
directement la mthode slugify() et ainsi de ne plus en entendre parler.
Ce rflexe est oublier dans un projet implmentant un framework de
tests unitaires. dfaut de corriger directement le bogue identifi, il va
dabord sagir de crer un test, afin de sassurer par la suite que ce pro-
blme ne se produise plus.
$t->is(Jobeet::slugify(' - '), 'n-a', '::slugify() converts a
string that only contains non-ASCII characters to n-a');
Figure 84
Rsultats des tests unitaires de
Jobeet::slugify() suite au bug dcouvert
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
142
Ce nest quaprs avoir vrifi que le test ne passe pas quil sera ncessaire
dditer la classe Jobeet et de dplacer le contrle de la chane vide la
fin de la mthode comme ci-dessous.
Dsormais, le nouveau test ainsi que tous les autres passent correcte-
ment. La mthode slugify() avait bel et bien un bogue malgr une cou-
verture de code de 100 %.
Il est impossible de penser tous les cas de bogues possibles lors de
lcriture de tests. Nanmoins, lorsquun problme est dcouvert, un test
pour celui-ci doit, dans lidal, tre crit avant de corriger le code. Cela
signifie aussi que le code gagnera de plus en plus en qualit, ce qui est
toujours une bonne chose. De plus, il ne faut pas oublier que le temps
gagn sur le dveloppement de grosses applications est considrable. En
nayant plus tester ce genre de cas de figure la main, le dveloppeur
peut se concentrer davantage sur des portions de code plus critiques.
Implmenter une meilleure mthode slugify
Il est important de rappeler que Symfony a t cr par des Franais. De
ce fait, les chanes contenant des caractres accentus sont monnaie cou-
rante et doivent donc tre pris en charge par la mthode slugify(). Pour
ce faire, la premire tape consiste crire un nouveau test unitaire avec
une chane contenant des caractres accentus.
Bien videmment, ce test doit chouer car les caractres accentus sont
automatiquement remplacs par des tirets et non par leur correspondant
non accentu respectif. Cest un problme rcurent appel translittra-
tion . Heureusement, si la librairie iconv est installe sur le serveur,
elle ralisera automatiquement le travail permettant dviter ce bogue.
static public function slugify($text)
{
// ...
if (empty($text))
{
return 'n-a';
}
return $text;
}
$t->is(Jobeet::slugify('Dveloppeur Web'), 'developpeur-web',
'::slugify() removes accents');
8


L
e
s

t
e
s
t
s

u
n
i
t
a
i
r
e
s
Groupe Eyrolles, 2008
143
Pour viter ce genre de problmes, il est important de sauvegarder les
fichiers PHP avec lencodage UTF-8 dans la mesure o cest lencodage
par dfaut de Symfony et quil est le seul utilis par iconv pour faire
de la translittration.
Sachant que ce problme se posera uniquement si la librairie iconv nest
pas disponible, le test ne sera excut qu cette condition. Aussi, le
fichier de test doit tre modifi.
// code driv de http://php.vrana.cz/vytvoreni-pratelskeho-
url.php
static public function slugify($text)
{
// replace non letter or digits by
$text = preg_replace('~[^\\pL\d]+~u', '-', $text);
// trim
$text = trim($text, '-');
// transliterate
if (function_exists('iconv'))
{
$text = iconv('utf-8', 'us-ascii//TRANSLIT', $text);
}
// lowercase
$text = strtolower($text);
// remove unwanted characters
$text = preg_replace('~[^-\w]+~', '', $text);
if (empty($text))
{
return 'n-a';
}
return $text;
}
if (function_exists('iconv'))
{
$t->is(Jobeet::slugify('Dveloppeur Web'), 'developpeur-web',
'::slugify() removes accents');
}
else
{
$t->skip('::slugify() removes accents - iconv not installed');
}
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
144
Implmentation des tests unitaires dans le
framework ORM Doctrine
Configuration de la base de donnes
Tester unitairement un modle Doctrine est un peu plus complexe dans
la mesure o une connexion la base de donnes est ncessaire.
Bien sr, il conviendrait dutiliser la connexion la base de donnes uti-
lise en environnement de dveloppement, mais cest une bonne habi-
tude prendre que de crer une base de donnes ddie aux tests. De
cette manire, il est possible dutiliser des donnes source de bogues dans
les prcdents dveloppements et ainsi de vrifier la validit des tests.
Au cours du premier chapitre, les environnements ont t introduits
comme un moyen de varier les paramtres dune application. Par dfaut,
tous les tests sont excuts dans lenvironnement de test ; une base de
donnes doit donc tre configure pour ce dernier.
Loption env indique la tche que la configuration de la base de donnes
est valable uniquement pour lenvironnement de test. Lorsque cette com-
mande a t utilise au chapitre 3, loption env navait pas t utilise, cest
pourquoi la configuration a t applique pour tous les environnements.
Maintenant que la base de donnes a t configure, il est ncessaire de
linitialiser en utilisant la commande doctrine:insert-sql.
$ php symfony configure:database --name=doctrine
X --class=sfDoctrineDatabase --env=test
X "mysql:host=localhost;dbname=jobeet_test" root mYsEcret
$ mysqladmin -uroot -pmYsEcret create jobeet_test
$ php symfony doctrine:insert-sql --env=test
EN COULISSE Organisation du fichier de
configuration de Symfony
Le fichier de configuration config/
databases.yml est trs instructif. Il permet de
constater comment Symfony simplifie le changement
de configuration en fonction dun environnement.
IMPORTANT Principes de configuration dans Symfony
Le quatrime chapitre montrait que des paramtres provenant de fichiers
de configuration pouvaient tre dfinis plusieurs niveaux.
Ces paramtres de configuration peuvent galement tre dpendants
dun environnement. Cest vrai pour la plupart des fichiers de configura-
tion utiliss jusqu prsent : databases.yml, app.yml,
view.yml et settings.yml. La cl principale prsente dans ces
fichiers correspond lenvironnement et indique que les paramtres sont
dfinis pour chaque environnement.
# config/databases.yml
dev:
doctrine:
class: sfDoctrineDatabase
test:
doctrine:
class: sfDoctrineDatabase
param:
dsn: 'mysql:host=localhost;dbname=jobeet_test'
all:
doctrine:
class: sfDoctrineDatabase
param:
dsn: 'mysql:host=localhost;dbname=jobeet'
username: root
password: null
8


L
e
s

t
e
s
t
s

u
n
i
t
a
i
r
e
s
Groupe Eyrolles, 2008
145
Mise en place dun jeu de donnes de test
Maintenant que la base de donnes ddie aux tests a t mise en place,
il est temps de trouver une manire pour y charger des informations de
test. Lors du troisime jour, la commande doctrine:data-load a t
aborde, mais pour les tests, les donnes devront tre recharges
chaque excution afin de fixer la base de donnes dans un tat connu.
La tche doctrine:data-load utilise intrieurement la mthode
Doctrine::loadData pour charger les donnes.
La classe globale sfConfig peut tre utilise pour obtenir le chemin
complet vers un sous-rpertoire du projet. Lutiliser permet notamment
de pouvoir personnaliser la structure des dossiers par dfaut.
La mthode loadData() prend un rpertoire ou bien un nom de fichier
comme premier argument. Elle peut aussi recevoir un tableau de dossiers
et/ou de fichiers.
Quelques donnes initiales ont dj t cres dans le rpertoire data/
fixtures/. Pour les tests, les donnes utilises seront dposes dans le
rpertoire test/fixtures/. Ces fichiers de donnes seront ensuite uti-
liss par Doctrine pour les tests unitaires et fonctionnels.
Pour linstant, il suffit simplement de copier les fichiers du rpertoire
data/fixtures/ dans le rpertoire test/fixtures.
Vrifier lintgrit du modle par des tests unitaires
Initialiser les scripts de tests unitaires de modles Doctrine
Voici quelques tests unitaires pour la classe de modle JobeetJob. Comme
tous les tests unitaires Doctrine dbuteront par le mme code, et pour res-
pecter le principe DRY, un fichier Doctrine.php dans le rpertoire de test
bootstrap/ doit tre cr. Ce dernier contient le code suivant :
Contenu du fichier test/bootstrap/Doctrine.php
Doctrine::loadData(sfConfig::get('sf_test_dir').'/fixtures');
include(dirname(__FILE__).'/unit.php');
$configuration = ProjectConfiguration::getApplicationConfiguration( 'frontend', 'test', true);
new sfDatabaseManager($configuration);
Doctrine::loadData(sfConfig::get('sf_test_dir').'/fixtures');
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
146
Le script est suffisamment explicite de lui-mme :
comme pour les front controllers, un objet de configuration est initia-
lis pour lenvironnement de test :
puis un gestionnaire de base de donnes est cr. Ce dernier initialise
la connexion Doctrine en chargeant le fichier de configuration
databases.yml :
enfin, les donnes de tests sont charges en base de donnes grce
Doctrine::dataLoad() :
Tester la mthode getCompanySlug() de lobjet JobeetJob
Maintenant que tout est en place, les tests de la classe JobeetJob peuvent
dmarrer. Pour commencer, un fichier JobeetJobTest.php doit tre cr
dans le rpertoire test/unit/model.
Contenu du fichier test/unit/model/JobeetJobTest.php
Au passage, il est important de remarquer que le test porte seulement sur
la mthode getCompanySlug(), aucune vrification ntant effectue pour
savoir si le slug est correct ou non. En effet, cest un test qui a dj t
effectu ailleurs.
Tester la mthode save() de lobjet JobeetJob
crire des tests pour la mthode save() est lgrement plus complexe.
En effet, il sagit ici de vrifier que la cration de lobjet sest bien pro-
duite mais surtout que les donnes insres dans la base de donnes sont
bien celles auxquelles on sattend.
$configuration =
ProjectConfiguration::getApplicationConfiguration( 'frontend',
'test', true);
new sfDatabaseManager($configuration);
Doctrine::loadData(sfConfig::get('sf_test_dir').'/fixtures');
include(dirname(__FILE__).'/../../bootstrap/Doctrine.php');
$t = new lime_test(1, new lime_output_color());
$t->comment('->getCompanySlug()');
$job = Doctrine::getTable('JobeetJob')->createQuery()-
>fetchOne();
$t->is($job->getCompanySlug(), Jobeet::slugify($job-
>getCompany()), '->getCompanySlug() return the slug for the
company');
EN COULISSE
Connexion au serveur par Doctrine
Doctrine se connecte la base de donnes unique-
ment sil y a des requtes SQL excuter.
MTHODE Tenir jour
le compteur de tests planifis
Chaque fois que des tests sont ajouts, il ne faut
pas oublier de mettre jour le compteur de tests
planifis dans le constructeur de la classe
lime_test. Pour JobeetJobTest, il suffit
de remplacer 1 par 3.
8


L
e
s

t
e
s
t
s

u
n
i
t
a
i
r
e
s
Groupe Eyrolles, 2008
147
Implmentation des tests unitaires dans dautres
classes Doctrine
Il est ds maintenant possible dajouter des tests unitaires pour chacune
des classes Doctrine. Le processus dcriture de tests unitaires tant
facile assimiler, la tche devrait donc tre tout aussi aise.
$t->comment('->save()');
$job = create_job();
$job->save();
$expiresAt = date('Y-m-d', time() + 86400 * sfConfig::get('app_active_days'));
$t->is(date('Y-m-d', strtotime($job->getExpiresAt())), $expiresAt, '->save() updates expires_at
if not set');
$job = create_job(array('expires_at' => '2008-08-08'));
$job->save();
$t->is(date('Y-m-d', strtotime($job->getExpiresAt())), '2008-08-08', '->save() does not update
expires_at if set');
function create_job($defaults = array())
{
static $category = null;
if (is_null($category))
{
$category = Doctrine::getTable('JobeetCategory')
->createQuery()
->limit(1)
->fetchOne();
}
$job = new JobeetJob();
$job->fromArray(array_merge(array(
'category_id' => $category->getId(),
'company' => 'Sensio Labs',
'position' => 'Senior Tester',
'location' => 'Paris, France',
'description' => 'Testing is fun',
'how_to_apply' => 'Send e-Mail',
'email' => 'job@example.com',
'token' => rand(1111, 9999),
'is_activated' => true,
), $defaults));
return $job;
}
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
148
Lancer lensemble des tests unitaires du
projet
La tche test:unit peut aussi servir lancer tous les tests du projet :
Elle indique en sortie si chaque fichier de tests est pass ou bien a
chou.
Si la tche test:unit retourne le statut dubious pour un fichier de tests,
cela indique que le script sest interrompu avant la fin. Excuter le fichier
de tests seul donnera le message derreur exact.
En rsum
Tester une application est une chose ncessaire ; et pourtant, certains
lecteurs auront sans doute t tents de faire limpasse sur ce chapitre
Nous sommes ravis de constater que ce nest pas le cas !
Bien sr, seule une pratique intense de Symfony vous en rvlera les
fonctionnalits les plus intressantes, ainsi que la philosophie de dve-
loppement et les bonnes pratiques quil induit. Tester fait justement
partie de ces pratiques vertueuses et un jour ou lautre, les tests unitaires
sauveront vos applications du dsastre.
Les tests, en effet, garantissent la solidit du code et offrent la libert de
le remanier sans risque de tout casser. Ils sont de vritables garde-fous
car ils vous alertent en cas de modification intempestive cassant le fonc-
tionnement de lensemble ou induisant une rgression ailleurs.
Le chapitre suivant sera consacr aux tests fonctionnels. Ils seront
dailleurs abords et implments dans les modules job et category.
Prenez un peu de temps dabord pour crire quelques tests unitaires pour
les classes de modle de Jobeet ; cet exercice sera excellent pour vous
prparer au chapitre suivant
$ php symfony test:unit
Figure 85
Rsultats dexcution
de tous les tests unitaires du projet
EN COULISSE
9 000 tests unitaires pour Symfony
Le framework Symfony lui-mme possde plus de
9 000 tests unitaires son actif pour en valider la
fiabilit et la robustesse !
Groupe Eyrolles, 2008
chapitre 9
Groupe Eyrolles, 2008
Les tests fonctionnels
Parmi les autres types de tests automatiss figurent
les tests fonctionnels. Ces derniers permettent de valider
le comportement gnral des fonctionnalits dune application
en simulant la navigation dun utilisateur dans son navigateur
Internet. Symfony intgre par dfaut un sous-framework
de tests fonctionnels puissant qui simplifie lcriture de
scnarios de tests fonctionnels grce une interface fluide.
MOTS-CLS :
BTests fonctionnels
BObjets sfBrowser
et sfTestFunctional
BTesteurs Request et Response
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
152
Le chapitre prcdent visait expliquer comment tester unitairement les
classes de Jobeet partir de la librairie lime embarque dans Symfony.
Dans cette neuvime partie, ce sera loccasion de dcouvrir et dcrire des
tests pour les fonctionnalits dj implmentes dans les modules job et
category.
Dcouvrir limplmentation des tests
fonctionnels
Les tests fonctionnels constituent un outil efficace pour tester une appli-
cation du dbut la fin, cest--dire de la requte construite par le navi-
gateur jusqu la rponse envoye par le serveur. Leur champ daction
stend toutes les couches de lapplication comme le routage, le
modle, les actions et les templates. Cette section dfinit dans un pre-
mier temps la notion de tests fonctionnels puis explique comment ils
sont intgrs au sein du framework Symfony.
En quoi consistent les tests fonctionnels ?
Concrtement, les tests fonctionnels ressemblent de trs prs aux tests
manuels quun dveloppeur ralise dans son navigateur. Lapproche
manuelle reste encore le schma le plus couramment employ pour tester
une application. Ds lors quune fonctionnalit est ajoute ou modifie, il
est coutume douvrir le navigateur pour vrifier que celle-ci se comporte
correctement en contrlant les diffrents objets qui composent la page de
rendu final. Les liens, les images, les tableaux, les messages dinforma-
tions, etc., figurent parmi ces lments qui servent de points de contrle.
Toutefois, les tests manuels posent quelques problmes car ils sont avant
tout pnibles et fastidieux raliser. Il en rsulte alors tout naturellement
de multiples consquences sur leur efficacit et leur viabilit. Au bout dun
certain temps pass dvelopper une application, le dveloppeur aura ten-
dance se lasser et survoler certaines fonctionnalits de lapplication,
do la ncessit dautomatiser la procdure de test. La question qui se
pose alors est la suivante : quest-ce quun test efficace et viable ?
Un test viable, cest avant tout un plan daction que lon doit tre capable
de rpter lidentique et linfini. Cest donc l tout lintrt de faire
appel aux services de la machine plutt quaux comptences de ltre
humain. En effet, ce dernier ne sera jamais en mesure de reproduire
lidentique et sur une priode infinie le mme test. Les tests fonctionnels
sont donc apparus pour rpondre cette problmatique, et cest pour cette
raison quils sintgrent parfaitement dans lenvironnement de Symfony.
9


L
e
s

t
e
s
t
s

f
o
n
c
t
i
o
n
n
e
l
s
Groupe Eyrolles, 2008
153
Implmentation des tests fonctionnels
Dans Symfony, les tests fonctionnels offrent une manire simple de
dcrire les scnarios de test. Chaque scnario peut tre jou automati-
quement volont en simulant lexprience que possde un utilisateur
dans son navigateur. Au mme titre que les tests unitaires, ils apportent
lassurance que le code est fonctionnel et sans dysfonctionnements.
Il faut tout de mme garder lesprit que le framework de tests fonction-
nels ne remplace en aucun cas des outils tels que Selenium. Selenium
sexcute directement dans le navigateur afin dautomatiser les tests sur
plusieurs plateformes, navigateurs et autres. Cet outil est galement connu
pour sa capacit pouvoir tester le code Javascript dune application.
Manipuler les composants de tests
fonctionnels
Le framework interne de tests fonctionnels de Symfony fournit de nom-
breux composants pour faciliter lcriture de tests de diffrentes natures.
Le premier mis ltude dans ces prochaines lignes permet de simuler le
comportement dun navigateur web.
Simuler le navigateur grce lobjet sfBrowser
Les tests fonctionnels de Symfony sexcutent travers un navigateur un
peu spcial implment par la classe sfBrowser. Celui-ci agit comme un
navigateur taill sur mesure pour lapplication et directement connect
celle-ci, sans ncessiter de serveur web.
Ce pseudo navigateur donne par exemple accs tous les objets internes
de Symfony avant et aprs chaque requte, ce qui offre lopportunit de
les introspecter et deffectuer les vrifications souhaites en cours dex-
cution du programme.
Tester la navigation en simulant le comportement dun vritable
navigateur
La classe sfBrowser dispose dun certain nombre de mthodes qui simu-
lent la navigation comme le permet un navigateur web classique. Le
tableau ci-dessous dcrit celles qui sont principalement utilises dans
lcriture de scnarios de tests.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
154
Le code suivant illustre quelques exemples dutilisation des mthodes de
la classe sfBrowser.
Modifier le comportement du simulateur de navigateur
La classe sfBrowser contient aussi quelques mthodes complmentaires
qui offrent la possibilit de configurer le comportement du navigateur,
comme le montre le tableau suivant.
Tableau 91 Liste des mthodes de lobjet sfBrowser
Nom de la mthode Description
get() Rcupre une URL
post() Poste des donnes vers une URL
call() Appelle une URL (utilis pour les mthodes PUT et DELETE)
back() Retourne vers la page prcdente de lhistorique
forward() Dirige vers la page suivante de lhistorique
reload() Recharge la page courante
click() Clique sur un bouton ou un lien
select() Slectionne un bouton radio ou une case cocher
deselect() Dslectionne un bouton radio ou une case cocher
restart() Rinitialise le navigateur
$browser = new sfBrowser();
$browser->
get('/')->
click('Design')->
get('/category/programming?page=2')->
get('/category/programming', array('page' => 2))->
post('search', array('keywords' => 'php'));
Tableau 92 Liste des mthodes de lobjet sfBrowser qui permettent
de configurer le comportement du navigateur
Nom de la mthode Description
setHttpHeader() Dfinit un en-tte HTTP
setAuth() Dfinit les droits dauthentification de base
setCookie() Fixe un cookie
removeCookie() Retire un cookie
clearCookies() Nettoie tous les cookies en cours
followRedirect() Suit la redirection dclenche
9


L
e
s

t
e
s
t
s

f
o
n
c
t
i
o
n
n
e
l
s
Groupe Eyrolles, 2008
155
Pour fonctionner, les tests fonctionnels ncessitent lutilisation dun
autre objet : lobjet sfTestFunctional. Cet dernier contient un ensemble
de testeurs capables danalyser les diffrents objets internes du fra-
mework comme la requte, la rponse, le routage, les formulaires et bien
dautres encore.
Prparer et excuter des tests fonctionnels
La plupart des tests fonctionnels peuvent tre raliss laide des objets
sfBrowser et lime, et leurs mthodes respectives telles que getRequest()
ou getResponse(). Nanmoins, lidal est de possder un moyen dins-
trospecter les objets internes de Symfony pour le scnario en cours.
Heureusement, le framework fournit son lot de mthodes de test laide
de la classe sfTestFunctional. Le constructeur de cette dernire accepte
une instance de la classe sfBrowser comme argument.
Comprendre la structure des fichiers de tests
Lobjet sfTestFunctional dlgue tous les tests des objets testeurs ,
dont la plupart sont embarqus par dfaut dans Symfony. Chaque tes-
teur est en ralit un objet qui tend la classe sfTester, ce qui permet par
exemple de crer ses propres testeurs ou bien denrichir les existants en
profitant de lhritage de classes.
Dans un projet Symfony, tous les fichiers de tests fonctionnels se trou-
vent dans le rpertoire test/functional/. Dans le cas prsent de lappli-
cation dveloppe, tous sont situs dans le sous-dossier test/
functional/frontend puisque chaque application dispose de son propre
rpertoire. Celui-ci contient dj deux fichiers qui ont t automatique-
ment gnrs lorsque les deux modules job et category ont t crs. Les
fichiers categoryActionsTest.php et jobActionsTest.php renferment
chacun quelques exemples de tests fonctionnels trs basiques comme le
prsente le listing de code ci-aprs.
Mettre en place un jeu de tests fonctionnels en utilisant le chanage
de mthodes
Tests fonctionnels autognrs pour le module category dans le fichier test/
functional/frontend/categoryActionsTest.php
include(dirname(__FILE__).'/../../bootstrap/functional.php');
$browser = new sfTestFunctional(new sfBrowser());
$browser->
get('/category/index')->
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
156
premire vue, ce script peut paratre trange et peu commode pour la
plupart des dveloppeurs car sa syntaxe est peu singulire. Toutes les
mthodes appeles sur lobjet sfTestFunctional ($browser) sont en effet
chanes afin dassurer une interface fluide dcriture de scnarios mais
aussi une meilleure lisibilit du code.
Comment cette syntaxe est-elle rendue possible ? Cest techniquement
enfantin puisque toutes les mthodes implmentes dans les classes
sfBrowser et sfTestFunctional retournent toujours la rfrence lobjet
lui-mme, conserve dans la variable $this.
Mettre en place un jeu de tests fonctionnels sans chanage de mthodes
Le code ci-dessous est strictement identique au dernier avec le chanage
des mthodes en moins. Le rsultat de la comparaison des deux syntaxes
est clair : la premire facilite grandement la lisibilit du code tandis que
la seconde la rduit en raison de limportance de bruit gnr par la rp-
tition de la variable $browser.
Effectuer des tests dans le contexte dun bloc testeur
Tous les tests sont excuts dans le contexte dun bloc testeur.
Le bloc dun testeur commence par with('TESTER NAME')->begin() et
sachve par end() comme le montre lexemple de code suivant.
with('request')->begin()->
isParameter('module', 'category')->
isParameter('action', 'index')->
end()->
with('response')->begin()->
isStatusCode(200)->
checkElement('body', '!/This is a temporary page/')->
end()
;
include(dirname(__FILE__).'/../../bootstrap/functional.php');
$browser = new sfTestFunctional(new sfBrowser());
$browser->get('/category/index');
$browser->with('request')->begin();
$browser->isParameter('module', 'category');
$browser->isParameter('action', 'index');
$browser->end();
$browser->with('response')->begin();
$browser->isStatusCode(200);
$browser->checkElement('body', '!/This is a temporary page/');
$browser->end();
9


L
e
s

t
e
s
t
s

f
o
n
c
t
i
o
n
n
e
l
s
Groupe Eyrolles, 2008
157
Ce code vrifie si le paramtre module de la requte (testeur request)
quivaut bien la valeur category, et que le paramtre action contient
lui aussi la valeur index. Lorsque lon souhaite appeler seulement une
mthode sur un testeur, il nest pas ncessaire de crer un bloc de tests :
with('request')->isParameter('module', 'category').
Dcouvrir le testeur sfTesterRequest
Le testeur sfTesterRequest fournit des mthodes qui permettent
dintrospecter et de tester les valeurs des proprits de lobjet
sfWebRequest. Le tableau ci-dessous prsente quelques-unes dentre elles.
Un testeur sfTesterResponse existe aussi afin de pouvoir contrler les
diffrents paramtres que le serveur envoie au client en guise de rponse
une requte HTTP.
Dcouvrir le testeur sfTesterResponse
Le testeur sfTesterResponse fournit quant lui des mthodes qui per-
mettent dintrospecter et de tester les valeurs des proprits de lobjet
sfWebResponse. Le tableau ci-dessous en prsente quelques unes.
$browser->
with('request')->begin()->
isParameter('module', 'category')->
isParameter('action', 'index')->
end();
Tableau 93 Liste des mthodes de lobjet sfTesterRequest
Nom de la mthode Description
isParameter() Contrle la valeur dun paramtre de la requte
isFormat() Vrifie le format dune requte
isMethod() Vrifie la mthode (GET, POST, PUT, DELETE)
hasCookie() Indique si la requte a un cookie correspondant au nom donn en paramtre
isCookie() Teste la valeur dun cookie
Tableau 94 Liste des mthodes de lobjet sfTesterResponse
Nom de la mthode Description
checkElement() Vrifie si un slecteur CSS de la rponse correspond un critre
isHeader() Contrle la valeur dun en-tte
isStatusCode() Contrle le code de statut de la rponse (200, 301, 404, 500)
isRedirected() Vrifie si la rponse courante est redirige
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
158
Excuter les scnarios de tests fonctionnels
Les tests fonctionnels sexcutent de la mme manire que les tests uni-
taires vus au chapitre prcdent. Il existe trois manires de lancer des
tests fonctionnels :
la premire consiste appeler directement le fichier PHP et de lex-
cuter laide du binaire PHP comme le prsente le code suivant :
une commande Symfony permet de raliser exactement la mme
chose en simplifiant la syntaxe :
enfin, comme pour les tests unitaires, la commande test:functional
sert lancer tous les tests fonctionnels dune mme application en
omettant simplement le second argument :
Charger des jeux de donnes de tests
Au mme titre que les tests unitaires pour le modle de donnes Doc-
trine, des jeux de donnes de tests doivent tre chargs en base de don-
nes chaque fois quun fichier de tests fonctionnels est excut. Le code
crit au chapitre prcdent qui remplit cette tche est rutilisable ici de la
mme manire.
$ php test/functional/frontend/categoryActionsTest.php
$ php symfony test:functional frontend categoryActions
Figure 91
Rsultat dexcution
des tests fonctionnels
du module category
$ php symfony test:functional frontend
include(dirname(__FILE__).'/../../bootstrap/functional.php');
$browser = new JobeetTestFunctional(new sfBrowser());
Doctrine::loadData(sfConfig::get('sf_test_dir').'/fixtures');
9


L
e
s

t
e
s
t
s

f
o
n
c
t
i
o
n
n
e
l
s
Groupe Eyrolles, 2008
159
Charger des donnes dans un fichier de tests fonctionnels est un peu
plus simple quavec les tests unitaires puisque la base de donnes est dj
initialise par le script damorage. Comme pour les tests unitaires, il est
bien videmment inutile de copier et coller ce bout de code dans chaque
fichier de test. La meilleure manire de procder est de mutualiser ce
code dans une classe de tests fonctionnels ddie qui hrite de
sfTestFunctional.
Contenu du fichier lib/test/JobeetTestFunctional.class.php
crire des tests fonctionnels pour le
module doffres
crire des tests fonctionnels revient exactement jouer un scnario dans un
navigateur web. Il sagit en effet de tester que chaque fonctionnalit teste
ragit comme le dfinit son cahier des charges. De plus, les tests fonction-
nels ont pour objectifs de vrifier que le rendu final de chaque page corres-
pond exactement ce que le dveloppeur a prvu dans son code.
Le second chapitre de cet ouvrage dcrit de manire non exhaustive tous
les besoins fonctionnels de lapplication. En y rflchissant bien, ces cas
dutilisation sont exactement la description littrale des scnarios de tests
fonctionnels crire. Pour la page daccueil du module doffres demploi,
on ne compte pas moins de cinq tests obligatoires qui seront dvelopps
juste aprs :
les offres demploi expires ne sont plus affiches ;
seulement N offres demploi actives sont listes par catgorie ;
une catgorie possde un lien vers sa page ddie sil y a trop doffres
demploi ;
les offres demploi sont listes par date ;
chaque offre demploi de la page daccueil est cliquable.
Il est temps de dmarrer avec le premier scnario de cette liste.
class JobeetTestFunctional extends sfTestFunctional
{
public function loadData()
{
Doctrine::loadData(sfConfig::get('sf_test_dir').'/fixtures');
return $this;
}
}
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
160
Les offres demploi expires ne sont pas affiches
Ce test fonctionnel tient en quelques lignes de code PHP puisquil sagit
tout simplement de vrifier que la page daccueil ne contient aucune
occurrence du slecteur CSS 3 pass en paramtre de la mthode
checkElement() du testeur response.
Contenu du fichier test/functional/frontend/jobActionsTest.php
Comme avec lime, un message dinformation peut tre insr en appe-
lant la mthode info() dans le but de rendre la sortie plus lisible et com-
prhensible. Le contrle de lexclusion des offres demploi de la page
daccueil se traduit par le fait que le slecteur CSS 3 .jobs
td.position:contains("expired") ne doit en aucun cas tre prsent
dans le contenu HTML de la rponse.
Si lon se souvient des fichiers de donnes de tests, la seule offre demploi
expire contient la chane expired en guise de poste. Lorsque le
second argument de la mthode checkElement() est un boolen, la
mthode teste lexistence des nuds qui correspondent au slecteur
CSS 3. La mthode checkElement() supporte dailleurs la plupart des
slecteurs CSS 3 existants.
Seulement N offres sont listes par catgorie
Tester quune catgorie prsente bien un certain nombre fini doffres est
relativement simple avec les slecteurs CSS et la mthode
checkElement(). En effet, il sagit ici de compter le nombre de lignes du
tableau HTML pour une mme catgorie. Pour faciliter davantage le
test, il suffit de sappuyer sur le nom de la catgorie qui est gnr dans le
code HTML sous forme dune classe CSS.
include(dirname(__FILE__).'/../../bootstrap/functional.php');
$browser = new JobeetTestFunctional(new sfBrowser());
$browser->loadData();
$browser->info('1 - The homepage')->
get('/')->
with('request')->begin()->
isParameter('module', 'job')->
isParameter('action', 'index')->
end()->
with('response')->begin()->
info(' 1.1 - Expired jobs are not listed')->
checkElement('.jobs td.position:contains("expired")',
false)->
end()
;
9


L
e
s

t
e
s
t
s

f
o
n
c
t
i
o
n
n
e
l
s
Groupe Eyrolles, 2008
161
Suite du contenu du fichier test/functional/frontend/jobActionsTest.php
La mthode checkElement() est galement capable de vrifier quun
slecteur CSS correspond un nombre fini de nuds dans le document,
en passant un entier comme second argument.
Un lien vers la page dune catgorie est prsent
lorsquil y a trop doffres
Dans Jobeet, lorsquune catgorie regroupe sur la page daccueil plus
doffres demploi que le nombre maximum doffres autoris pour cette der-
nire, un lien more jobs est affich. Lobjectif de ce test fonctionnel con-
siste vrifier la prsence ou labsence du lien en fonction du nombre
doffres demploi dans chaque catgorie. Daprs les fichiers de donnes de
tests, seule la catgorie Programming est cense accueillir ledit lien.
Suite du fichier test/functional/frontend/jobActionsTest.php
Ces tests vrifient quil ny a pas de lien more jobs pour la catgorie
design , cest--dire que le slecteur CSS .category_design
.more_jobs existe nulle part dans le document. Ils testent en revanche
que le lien more jobs est bien prsent pour la catgorie
programming , donc que le slecteur CSS .category_programming
.more_jobs existe.
$max = sfConfig::get('app_max_jobs_on_homepage');
$browser->info('1 - The homepage')->
get('/')->
info(sprintf(' 1.2 - Only %s jobs are listed for a category',
$max))->
with('response')->
checkElement('.category_programming tr', $max)
;
$browser->info('1 - The homepage')->
get('/')->
info(' 1.3 - A category has a link to the category page only
if too many jobs')->
with('response')->begin()->
checkElement('.category_design .more_jobs', false)->
checkElement('.category_programming .more_jobs')->
end()
;
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
162
Les offres demploi sont tries par date
Ce test est un peu plus complexe mettre en uvre que les prcdents
En effet, pour tester si les offres demploi sont effectivement ordonnes
par date, il faut vrifier que la premire offre demploi qui apparat dans
la page daccueil correspond bien celle que lon attend. Ce rsultat peut
tre obtenu en contrlant que lURL contient la cl primaire attendue.
Or, les cls primaires ne sont pas fixes et peuvent donc varier entre deux
excutions dun mme fichier de tests fonctionnels. Cela est d au fait
que Doctrine recharge les donnes de test dans la base de donnes
chaque nouvelle excution du script. Lastuce pour y parvenir est en fait
triviale puisquil sagit de rcuprer lobjet Doctrine de la base de don-
nes comme le montre le code suivant.
Quelques explications simposent malgr tout dans la mesure o le code
prsent se rvle un peu complexe. Tout dabord, la requte Doctrine
rcupre loffre demploi la plus rcente pour la catgorie dont le slug a
pour valeur programming .
Puis, la mthode checkElement() teste la prsence du slecteur CSS 3 pass
en paramtre. Ce dernier est format par la fonction sprintf() de manire
signifier que la premire offre demploi (tr:first) de la catgorie
programming (.category_programming) possde un lien dont lattribut
href contienne la cl primaire de lobjet attendu (a[href*="/%d/"]).
Bien que ce test fonctionne parfaitement, un besoin de remaniement se
fait ressentir. En effet, la rcupration de la premire offre demploi de la
catgorie programming est potentiellement rutilisable ailleurs dans
les tests. Le code ne peut en revanche tre dplac vers la couche du
modle dans la mesure o il est purement spcifique aux tests. De ce
$q = Doctrine_Query::create()
->select('j.*')
->from('JobeetJob j')
->leftJoin('j.JobeetCategory c')
->where('c.slug = ?', 'programming')
->andWhere('j.expires_at > ?', date('Y-m-d', time()))
->orderBy('j.created_at DESC');
$job = $q->fetchOne();
$browser->info('1 - The homepage')->
get('/')->
info(' 1.4 - Jobs are sorted by date')->
with('response')->begin()->
checkElement(sprintf('.category_programming tr:first
a[href*="/%d/"]', $job->getId()))->
end()
;
9


L
e
s

t
e
s
t
s

f
o
n
c
t
i
o
n
n
e
l
s
Groupe Eyrolles, 2008
163
postulat, on en dduit clairement que la meilleure place lui consacrer
est la classe JobeetTestFunctional cre plus tt dans ce chapitre. Celle-
ci se comporte comme une classe de tests fonctionnels propre lenvi-
ronnement de test.
Mthode ajouter au fichier lib/test/JobeetTestFunctional.class.php
Le code des tests fonctionnels prcdent peut alors tre rduit celui qui
suit.
Suite du fichier test/functional/frontend/jobActionsTest.php
Chacune des offres de la page daccueil est cliquable
Pour tester le lien dune offre de la page daccueil, il suffit de simuler un
clic sur le texte Web Developper . Comme il y en a plusieurs possibles
sur cette page, le test force explicitement le navigateur cliquer sur le
premier quil trouve (array('position' => 1)).
class JobeetTestFunctional extends sfTestFunctional
{
public function getMostRecentProgrammingJob()
{
$q = Doctrine_Query::create()
->select('j.*')
->from('JobeetJob j')
->leftJoin('j.JobeetCategory c')
->where('c.slug = ?', 'programming');
$q = Doctrine::getTable('JobeetJob')-
>addActiveJobsQuery($q);
return $q->fetchOne();
}
// ...
}
$browser->info('1 - The homepage')->
get('/')->
info(' 1.4 - Jobs are sorted by date')->
with('response')->begin()->
checkElement(sprintf('.category_programming tr:first
a[href*="/%d/"]',
$browser->getMostRecentProgrammingJob()->getId()))->
end()
;
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
164
Chaque paramtre de la requte est ensuite test pour sassurer que le
routage a correctement fait son travail.
Autres exemples de scnarios de tests pour
les pages des modules job et category
Cette section fournit tout le code ncessaire pour tester les pages des
modules job et category. En lisant le code avec attention, on apprend
quelques nouvelles astuces pratiques.
Contenu du fichier lib/test/JobeetTestFunctional.class.php
$browser->info('2 - The job page')->
get('/')->
info(' 2.1 - Each job on the homepage is clickable and give
detailed information')->
click('Web Developer', array(), array('position' => 1))->
with('request')->begin()->
isParameter('module', 'job')->
isParameter('action', 'show')->
isParameter('company_slug', 'sensio-labs')->
isParameter('location_slug', 'paris-france')->
isParameter('position_slug', 'web-developer')->
isParameter('id', $browser->getMostRecentProgrammingJob()-
>getId())->
end()
;
class JobeetTestFunctional extends sfTestFunctional
{
public function loadData()
{
Doctrine::loadData(sfConfig::get('sf_test_dir').'/fixtures');
return $this;
}
public function getMostRecentProgrammingJob()
{
$q = Doctrine_Query::create()
->select('j.*')
->from('JobeetJob j')
->leftJoin('j.JobeetCategory c')
->where('c.slug = ?', 'programming');
$q = Doctrine::getTable('JobeetJob')->addActiveJobsQuery($q);
return $q->fetchOne();
}
9


L
e
s

t
e
s
t
s

f
o
n
c
t
i
o
n
n
e
l
s
Groupe Eyrolles, 2008
165
Contenu du fichier test/functional/frontend/jobActionsTest.php
// Rcupration dune offre expire
public function getExpiredJob()
{
$q = Doctrine_Query::create()
->from('JobeetJob j')
->where('j.expires_at < ?', date('Y-m-d', time()));
return $q->fetchOne();
}
}
include(dirname(__FILE__).'/../../bootstrap/functional.php');
$browser = new JobeetTestFunctional(new sfBrowser());
$browser->loadData();
$browser->info('1 - The homepage')->
get('/')->
with('request')->begin()->
isParameter('module', 'job')->
isParameter('action', 'index')->
end()->
with('response')->begin()->
info(' 1.1 - Expired jobs are not listed')->
checkElement('.jobs td.position:contains("expired")', false)->
end()
;
$max = sfConfig::get('app_max_jobs_on_homepage');
$browser->info('1 - The homepage')->
info(sprintf(' 1.2 - Only %s jobs are listed for a category',
$max))->
with('response')->
checkElement('.category_programming tr', $max)
;
$browser->info('1 - The homepage')->
get('/')->
info(' 1.3 - A category has a link to the category page only if
too many jobs')->
with('response')->begin()->
checkElement('.category_design .more_jobs', false)->
checkElement('.category_programming .more_jobs')->
end()
;
$browser->info('1 - The homepage')->
info(' 1.4 - Jobs are sorted by date')->
with('response')->begin()->
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
166
Contenu du fichier test/functional/frontend/categoryActionsTest.php
checkElement(sprintf('.category_programming tr:first a[href*=
"/%d/"]', $browser->getMostRecentProgrammingJob()->getId()))->
end()
;
$browser->info('2 - The job page')->
info(' 2.1 - Each job on the homepage is clickable and give
detailed information')->
click('Web Developer', array(), array('position' => 1))->
with('request')->begin()->
isParameter('module', 'job')->
isParameter('action', 'show')->
isParameter('company_slug', 'sensio-labs')->
isParameter('location_slug', 'paris-france')->
isParameter('position_slug', 'web-developer')->
isParameter('id', $browser->getMostRecentProgrammingJob()->
getId())->
end()->
info(' 2.2 - A non-existent job forwards the user to a 404')->
get('/job/foo-inc/milano-italy/0/painter')->
with('response')->isStatusCode(404)->
info(' 2.3 - An expired job page forwards the user to a 404')->
get(sprintf('/job/sensio-labs/paris-france/%d/web-developer',
$browser->getExpiredJob()->getId()))->
with('response')->isStatusCode(404)
;
include(dirname(__FILE__).'/../../bootstrap/functional.php');
$browser = new JobeetTestFunctional(new sfBrowser());
$browser->loadData();
$browser->info('1 - The category page')->
info(' 1.1 - Categories on homepage are clickable')->
get('/')->
click('Programming')->
with('request')->begin()->
isParameter('module', 'category')->
isParameter('action', 'show')->
isParameter('slug', 'programming')->
end()->
info(sprintf(' 1.2 - Categories with more than %s jobs also have
a "more" link', sfConfig::get('app_max_jobs_on_homepage')))->
get('/')->
click('22')->
with('request')->begin()->
isParameter('module', 'category')->
isParameter('action', 'show')->
9


L
e
s

t
e
s
t
s

f
o
n
c
t
i
o
n
n
e
l
s
Groupe Eyrolles, 2008
167
Dboguer les tests fonctionnels
Il arrive parfois quun test fonctionnel choue. Comme Symfony simule
un navigateur sans interface graphique, il peut devenir difficile de dia-
gnostiquer rapidement lorigine du problme. Heureusement, le fra-
mework fournit la mthode debug() pour imprimer le contenu et len-
tte de la rponse sur la sortie standard.
La mthode debug() peut tre insre nimporte o dans le bloc du testeur
de la rponse. Son appel forcera larrt immdiat de lexcution du script.
Excuter successivement des tests
fonctionnels
La tche test:functional peut aussi tre utilise pour lancer tous les
tests fonctionnels dune mme application.
La tche affiche en sortie une ligne de rsultat pour chaque fichier test.
isParameter('slug', 'programming')->
end()->
info(sprintf(' 1.3 - Only %s jobs are listed',
sfConfig::get('app_max_jobs_on_category')))->
with('response')->checkElement('.jobs tr',
sfConfig::get('app_max_jobs_on_category'))->
info(' 1.4 - The job listed is paginated')->
with('response')->begin()->
checkElement('.pagination_desc', '/32 jobs/')->
checkElement('.pagination_desc', '#page 1/2#')->
end()->
click('2')->
with('request')->begin()->
isParameter('page', 2)->
end()->
with('response')->checkElement('.pagination_desc', '#page 2/2#')
;
$browser->with('response')->debug();
$ php symfony test:functional frontend
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
168
Excuter les tests unitaires et fonctionnels
Il est bien videmment possible dexcuter tous les tests unitaires et
fonctionnels d'une application. Cest en effet la tche test:all qui a le
rle de lancer la suite tous les fichiers de tests et de gnrer en sortie un
rapport pour chacun deux.
En rsum
Ce chapitre achve le tour de prsentation des outils de tests de Sym-
fony. Dsormais, il sera dlicat de trouver un prtexte pour ne pas tester
votre application ! Grce lime et au framework de tests fonctionnels,
Symfony apporte des outils puissants pour raliser lcriture de tests avec
peu deffort.
Pour linstant, les tests fonctionnels ont t relativement survols, cest
pourquoi de nouveaux seront crits au cours du projet chaque fois que
de nouvelles fonctionnalits seront implmentes ; ils seront loccasion
de dcouvrir de nouveaux atouts du framework de test.
Le chapitre suivant aborde une fonctionnalit essentielle de Symfony : le
framework de formulaires
Figure 92
Rsultat dexcution des tests fonctionnels
de lapplication frontend
$ php symfony test:all
Figure 93
Rsultat dexcution
de tous les tests de Jobeet
Groupe Eyrolles, 2008
chapitre 10
GET /frontend_dev.php/job/new
POST /frontend_dev.php/job
GET /frontend_dev.php/job/TOKEN
newSuccess
showSuccess
$this->form = new JobeetJobForm(); <?php echo $form ?>
$this->form->bind($request->getParameter($form->getName()));
if ($this->form->isValid())
$this->form->save();
$this->redirect($this->generateUrl('job_show', $job));
Formulaire valide
3
2
Formulaire invalide
1
Afchage du formulaire
2
Soumission du formulaire
Prvisualisation de lofre
Groupe Eyrolles, 2008
Acclrer la gestion
des formulaires
Les formulaires de saisie sont depuis toujours les principaux
outils dinteraction avec lutilisateur dans le but de rcolter
des informations. Bien quils soient largement connus
des dveloppeurs, leur manipulation nen reste pas moins
difficile et est souvent lorigine de nombreuses failles
de scurit du systme dinformation.
Ce chapitre montre comment Symfony simplifie grandement
tant la cration et la validation des formulaires que leur
traitement automatique.
MOTS-CLS :
BFormulaires
BValidation des donnes
utilisateurs
BScurit contre les failles CSRF
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
172
Ce dixime chapitre aborde lune des principales grandes nouveauts de
Symfony, apparue depuis la version 1.1 et enrichie dans cette nouvelle
version. Il sagit en effet du framework interne de cration et de valida-
tion des formulaires. Celui-ci simplifie la gestion des formulaires web en
remplissant lui-mme les tches de gnration, de contrle dintgrit
des donnes, de traitement automatique de ces dernires lorsquelles sont
valides, et de scurit.
la dcouverte des formulaires avec Symfony
Nimporte quel site Internet possde des formulaires. Cela va bien sr du
simple formulaire de contact aux plus complexes composs dune pliade
de champs divers et varis. Pour un dveloppeur, crire des formulaires
est aussi lune des tches les plus rbarbatives, les plus ardues et aussi les
plus fastidieuses. Cette tche ncessite en effet dcrire le code HTML
du formulaire, puis dimplmenter les rgles de validation pour chaque
champ, de traiter les valeurs pour les sauvegarder en base de donnes,
dafficher les messages derreur, de repeupler le formulaire en cas
derreur, et bien plus encore
Bien videmment, au lieu de rinventer la roue chaque fois, Symfony
fournit un framework ddi aux formulaires afin den simplifier leur ges-
tion. Celui-ci se compose en trois parties distinctes :
la validation : le sous-framework de validation contient toutes les
classes ncessaires la validation des entres (entiers, chanes de
caractres, adresses e-mail, dates) ;
les widgets : le sous-framework de widgets possde quant lui les
classes utiles la gnration du code HTML de chaque champ du
formulaire (input, textarea, select) ;
les formulaires : les classes du sous-framework de formulaires repr-
sentent les formulaires conus partir des widgets et des validateurs,
et fournissent les mthodes pour faciliter leur gestion. Chaque champ
du formulaire dispose de son propre widget et de son ou ses propres
validateurs.
Les formulaires de base
Dans Symfony, un formulaire nest ni plus ni moins quune classe dans
laquelle sont dclars les champs. Chaque champ possde un nom, un
widget et un ou plusieurs validateurs. Ainsi, un simple formulaire de
contact peut tre dfini daprs la classe ContactForm suivante.
1
0


A
c
c

r
e
r

l
a

g
e
s
t
i
o
n

d
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
173
Les champs du formulaire sont tous dclars dans la mthode
configure() de la classe au moyen des mthodes setWidgets() et
setValidators().
Le framework de formulaires est par dfaut livr avec un certain nombre
de widgets et de validateurs prts lemploi. LAPI les dcrit tous trs
largement en indiquant pour chacun toutes les options, erreurs et mes-
sages derreur par dfaut.
Les noms des classes de widgets et de validateurs sont eux aussi trs expli-
cites. Le champ email sera rendu par une balise HTML <input>
(sfWidgetFormInput) et valid comme une adresse e-mail
(sfValidatorEmail). Le champ message quant lui sera rendu sous la
forme dune balise HTML <textarea> (sfWidgetFormTextarea). Sa
valeur doit tre une chane de caractres dune longueur strictement inf-
rieure ou gale 255 caractres. Par dfaut, tous les champs sont consi-
drs comme obligatoires car la valeur par dfaut de loption required est
true. Ainsi, la dfinition de la rgle de validation du champ email est
quivalente new sfValidatorEmail(array('required' => true)).
Un formulaire peut galement tre fusionn avec un autre en utilisant la
mthode mergeForm(), ou bien embarquer un autre formulaire imbriqu
grce la mthode embedForm().
<?php
class ContactForm extends sfForm
{
public function configure()
{
$this->setWidgets(array(
'email' => new sfWidgetFormInput(),
'message' => new sfWidgetFormTextarea(),
));
$this->setValidators(array(
'email' => new sfValidatorEmail(),
'message' => new sfValidatorString(array('max_length' =>
255)),
));
}
}
$this->mergeForm(new AnotherForm());
$this->embedForm('name', new AnotherForm());
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
174
Les formulaires gnrs par les tches Doctrine
La plupart du temps, un formulaire a pour vocation dtre srialis en
base de donnes. Symfony connat dj tout du modle de base de don-
nes, ce qui lui permet dautomatiser la gnration de formulaires bass
sur ces informations. En fait, lorsque la tche doctrine:build-all a t
excute au chapitre 3, Symfony a automatiquement fait appel la tche
doctrine:build-forms.
La tche doctrine:build-forms gnre les classes de modle des formu-
laires dans le rpertoire lib/form/. Lorganisation de ces fichiers gnrs
est semblable celle de lib/model/. Chaque classe de modle possde une
classe de formulaire associe. Cette dernire est vide par dfaut puisquelle
hrite des proprits et des mthodes de la superclasse de base qui contient
lensemble de la configuration du formulaire. La classe JobeetJobForm est
un exemple de formulaire Doctrine li au modle JobeetJob.
Contenu du fichier lib/form/doctrine/JobeetJobForm.class.php
Cest en parcourant les fichiers gnrs du rpertoire lib/form/
doctrine/base/ que lon dcouvre des exemples tonnants dusage des
widgets et des validateurs natifs de Symfony.
Personnaliser le formulaire dajout ou de modification
dune offre
Le formulaire des offres est lexemple parfait pour apprendre person-
naliser des classes de formulaires. Afin de garantir une meilleure com-
prhension du processus de personnalisation, ce dernier sera prsent pas
pas dans les prochaines sections. La premire tape consiste changer
le lien Post a job du layout afin de contrler les modifications directe-
ment dans le navigateur.
Code placer dans le fichier apps/frontend/templates/layout.php
$ php symfony doctrine:build-forms
class JobeetJobForm extends BaseJobeetJobForm
{
public function configure()
{
}
}
<a href="<?php echo url_for('@job_new') ?>">Post a Job</a>
1
0


A
c
c

r
e
r

l
a

g
e
s
t
i
o
n

d
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
175
Lobjectif suivant prsente la manire de retirer des champs dun formu-
laire autognr.
Supprimer les champs inutiles du formulaire gnr
Par dfaut, les formulaires Doctrine affichent tous les champs pour
chaque colonne dune table de la base de donnes. Or, pour le formulaire
de cration doffre demploi, certaines valeurs de la table jobeet_job ne
doivent pas tre dites par lutilisateur final. Il faut donc retirer du for-
mulaire leur champ associ. La manipulation est triviale : Il sagit tout
simplement de supprimer ces champs directement dans la mthode
configure() du formulaire comme le montre lexemple ci-dessous.
Contenu du fichier lib/form/doctrine/JobeetJobForm.class.php
Supprimer un champ avec unset() revient supprimer aussi bien son
widget que son validateur. La syntaxe prsente ci-dessus peut paratre
exotique et surprenante premire vue. Comment un objet peut-il se
comporter comme un tableau ? La rponse se trouve dans la dclaration
de la classe sfForm. Celle-ci implmente effectivement linterface
ArrayAccess de la SPL de PHP 5, en redfinissant les quatre mthodes
de cette dernire. Cette syntaxe a lavantage dtre la fois plus simple et
plus familire pour les dveloppeurs PHP.
Redfinir plus prcisment la configuration dun champ
La configuration dun formulaire doit parfois tre plus prcise que celle
qui a t gnre par lanalyse interne du modle de la base de donnes.
Par exemple, la colonne email est dclare comme tant un varchar dans
le schma, mais sa valeur doit, comme son nom lindique, tre valide
comme une vritable adresse de courrier lectronique. Il en va de mme
pour le type doffre. Ce champ est dfini comme un varchar, mais dans
la ralit, son widget associ se prsentera sous la forme dune liste
droulante de choix prdfinis.
class JobeetJobForm extends BaseJobeetJobForm
{
public function configure()
{
unset(
$this['created_at'], $this['updated_at'],
$this['expires_at'], $this['is_activated']
);
}
}
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
176
Utiliser le validateur sfValidatorEmail
La brve introduction prcdente explique clairement que la valeur du
champ email doit tre accepte uniquement si son format correspond
celui dune adresse lectronique valide. Heureusement, Symfony fournit
un validateur prt lemploi pour remplir cette tche. Il suffit donc sim-
plement de surcharger la configuration du champ email en lui appli-
quant ce nouveau validateur sfValidatorEmail.
Dfinition du validateur sfValidatorEmail pour le champ email dans le fichier lib/
form/doctrine/JobeetJobForm.class.php
Remplacer le champ permettant le choix du type doffre par une
liste droulante
Bien que le type de la colonne type de la table jobeet_job est un varchar,
la valeur de ce champ doit tre restreinte une liste prdfinie de choix :
full time , part time ou bien freelance . Le widget le mieux adapt
pour ce cas de figure est sans aucun doute une liste droulante identifie
par une balise HTML <select>. Une liste de boutons radio ferait gale-
ment trs bien laffaire, du fait quil y a peu de choix possibles.
Dfinir la liste des valeurs autorises
La premire tape consiste tout dabord dfinir la liste des choix possi-
bles sous la forme dun simple tableau associatif PHP. Lendroit le plus
appropri pour tablir celle-ci nest pas forcment la classe du formulaire
mais le modle, et plus exactement la classe JobeetJobTable comme le
montre lexemple suivant.
Dclaration de la liste des types de poste dans le fichier lib/model/doctrine/
JobeetJobTable.class.php
public function configure()
{
// ...
$this->validatorSchema['email'] = new sfValidatorEmail();
}
class JobeetJobTable extends Doctrine_Table
{
static public $types = array(
'full-time' => 'Full time',
'part-time' => 'Part time',
'freelance' => 'Freelance',
);
1
0


A
c
c

r
e
r

l
a

g
e
s
t
i
o
n

d
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
177
Le tableau associatif $types possde en cl la valeur sauvegarder en
base de donnes, donc la valeur de chaque nud <option> de la liste
droulante, associe la chane afficher en guise de label dans chaque
balise <option>.
Implmenter le widget sfWidgetFormChoice
La seconde tape de personnalisation du champ type consiste mainte-
nant instancier un objet sfWidgetFormChoice, auquel est attribu la
liste des types prdfinis pour remplir la liste droulante.
sfWidgetFormChoice reprsente un widget de choix qui peut tre rendu
par un autre widget diffrent selon la dfinition de ses options de confi-
guration (expanded ou multiple). La liste ci-dessous fait le bilan de
toutes les possibilits de configuration de ce widget, et la forme quil
prendra lorsquil sera rendu dans sa version HTML.
Liste droulante (<select>) : array('multiple' => false,
'expanded' => false)
Liste droulante multiple (<select multiple="multiple">) :
array('multiple' => true, 'expanded' => false)
Liste de boutons radio : array('multiple' => false, 'expanded' =>
true)
Liste de cases cocher : array('multiple' => true, 'expanded' =>
true)
Pour forcer la slection dun bouton radio par dfaut (full-time par
exemple), il suffit de changer la valeur par dfaut dans le schma de des-
cription de la base de donnes.
Valider la valeur choisie par lutilisateur avec sfValidatorChoice
La dernire tape de personnalisation concerne dsormais la validation
de la donne transmise par ce champ. Contrairement ce quon pourrait
croire, le fait dutiliser une liste droulante pour conditionner les choix
possibles ne garantit en rien la fiabilit de la valeur soumise. En effet,
public function getTypes()
{
return self::$types;
}
// ...
}
$this->widgetSchema['type'] = new sfWidgetFormChoice(array(
'choices' => Doctrine::getTable('JobeetJob')->getTypes(),
'expanded' => true,
));
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
178
nimporte qui peut soumettre une valeur non valide. Un hacker, par
exemple, outrepassera facilement la liste droulante en utilisant des
outils comme Curl (Client URL Request Library, permettant de rcu-
prer le contenu dune ressource accessible une URL) ou la clbre
Firefox Web Developper Toolbar. Le code qui suit ajoute un validateur
sfValidatorChoice qui permet de contrler que la valeur saisie corres-
pond bien lune des valeurs autorises du widget.
La configuration du validateur est trs aise puisquil sagit de fournir
une liste des valeurs autorises loption choices de ce dernier. Cest
exactement ce que ralise la fonction array_keys() de PHP qui retourne
un tableau classique dont les valeurs sont cette fois-ci les cls du tableau
$types dfinis plus haut.
Personnaliser le widget permettant lenvoi du logo associ une
offre
Le champ logo stocke la valeur du nom de fichier du logo associ
loffre. Or, pour permettre lutilisateur dajouter un logo son offre, le
widget du champ logo doit imprativement tre transform en un
champ input de type file. Bien sr, chaque fichier transmis doit gale-
ment tre contrl pour viter le tlchargement de documents poten-
tiellement dangereux pour lapplication. De ce fait, le fichier tlcharg
devra imprativement tre une image.
Implmenter le widget sfWidgetFormInputFile
Dans Symfony, un champ de tlchargement de fichier est dclar au
moyen du widget sfWidgetFormInputFile. Ce dernier retourne le code
HTML dun champ <input> de type file qui permet lutilisateur de
proposer des fichiers tlcharger depuis le disque dur de son ordinateur.
Il ne reste enfin qu spcifier un validateur pour contrler automatique-
ment les caractristiques du fichier transmis. Cest le rle du validateur
sfValidatorFile.
$this->validatorSchema['type'] = new sfValidatorChoice(array(
'choices' => array_keys(Doctrine::getTable('JobeetJob')-
>getTypes()),
));
$this->widgetSchema['logo'] = new sfWidgetFormInputFile(array(
'label' => 'Company logo',
));
1
0


A
c
c

r
e
r

l
a

g
e
s
t
i
o
n

d
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
179
Valider les fichiers avec sfValidatorFile
En proposant lutilisateur denvoyer un fichier pour illustrer son offre,
cest aussi l une ouverture du serveur aux fichiers potentiellement mal-
veillants. Cest pour cette raison quil faut sassurer que chaque fichier
transmis est sain et ne comporte pas de risque pour le systme dinfor-
mation qui laccueille. Dans le cas de Jobeet, les fichiers autoriss sont
uniquement des images, ce qui limite considrablement les risques
dinfection du systme. Nanmoins, les images ont des caractristiques
propres, comme lextension ou le type de contenu, qui sont faciles
valider. Pour ce faire, Symfony met disposition le validateur
sfValidatorFile qui contient par dfaut une configuration spcifique
pour la validation des images.
Le code ci-dessus prsente une option mime_types avec la valeur
web_images. Il sagit en fait de la dfinition de la configuration du valida-
teur pour le contrle des fichiers dimages.
Que se passe-t-il concrtement lorsquun utilisateur soumet un fichier
depuis le formulaire ? Tout dabord, le validateur sfValidatorFile
sassure que le fichier transmis est bien une image au format web. Il le
renomme ensuite avec un nom arbitraire et unique afin de supprimer les
espaces et autres caractres accentus. Puis, il copie ce fichier dans le
rpertoire dfini par loption path avant de mettre finalement jour la
valeur de la colonne logo avec le nouveau nom du fichier.
Implmenter laffichage du logo dans le template
Sachant que le validateur sauvegarde le chemin relatif dans la base de
donnes, le chemin utilis dans le template showSuccess.php doit tre
dit en consquence.
Code remplacer dans le template apps/frontend/modules/job/template/
showSuccess.php
Si une mthode generateLogoFilename() existe dans le modle, elle sera
automatiquement appele par le validateur afin de redfinir le processus
natif de gnration du nom de fichier. Cette mthode prend un objet de
type sfValidatedFile comme argument.
$this->validatorSchema['logo'] = new sfValidatorFile(array(
'required' => false,
'path' => sfConfig::get('sf_upload_dir').'/jobs',
'mime_types' => 'web_images',
));
<img src="/uploads/jobs/<?php echo $job->getLogo() ?>"
alt="<?php echo $job->getCompany() ?> logo" />
REMARQUE Cration du rpertoire
dupload des logos
La cration du rpertoire web/uploads/
jobs/ nest pas gre par Symfony. Cela doit tre
fait manuellement en sassurant que le rpertoire
possde les droits dcriture ncessaires.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
180
Modifier plusieurs labels en une seule passe
Symfony attribue par dfaut un label (balise HTML <label>) spcifique
chaque champ du formulaire, en se basant sur leur nom respectif. Bien
videmment, chaque valeur de label est entirement personnalisable, et
peut tre spcifie soit dans la configuration du widget grce loption
label, soit directement et pour plusieurs champs la fois grce la
mthode setLabels(). La premire mthode a t prsente lors de la
configuration du widget du champ logo quelques lignes plus haut. La
seconde quant elle est explique dans le code ci-dessous.
La mthode setLabels() prend un tableau associatif en paramtre dont
la cl correspond au nom du champ, et la valeur la chane qui doit tre
affiche dans la balise <label> gnre.
Ajouter une aide contextuelle sur un champ
De la mme manire que le label dun champ peut tre surcharg, Sym-
fony propose un moyen dajouter une aide contextuelle aux champs du
formulaire. Lobjectif de cette dernire est dapporter une information
la fois plus significative et plus pertinente que le label du champ lui-
mme. Par exemple, le champ is_public du formulaire en ncessite une.
Dans ce cas prcis, cette aide indique lutilisateur sil souhaite publier ou
non son offre sur les sites web partenaires pour quelle ait plus de visibilit.
Prsentation de la classe finale de configuration du formulaire
dajout dune offre
Aprs toutes les modifications apportes dans ces dernires sections, la
classe JobeetJobForm est dsormais la suivante.
Contenu du fichier lib/form/doctrine/JobeetJobForm.class.php
$this->widgetSchema->setLabels(array(
'category_id' => 'Category',
'is_public' => 'Public?',
'how_to_apply' => 'How to apply?',
));
$this->widgetSchema->setHelp('is_public', 'Whether the job can
also be published on affiliate websites or not.');
<?php
class JobeetJobForm extends BaseJobeetJobForm
{
public function configure()
1
0


A
c
c

r
e
r

l
a

g
e
s
t
i
o
n

d
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
181
Il est temps de dcouvrir comment sont rendus les formulaires dans les
templates, et de quelle manire leur habillage peut tre personnalis pour
rpondre aux besoins de la maquette graphique de lapplication.
{
unset(
$this['created_at'], $this['updated_at'],
$this['expires_at'], $this['is_activated']
);
$this->validatorSchema['email'] = new sfValidatorEmail();
$this->widgetSchema['type'] = new sfWidgetFormChoice(array(
'choices' => Doctrine::getTable('JobeetJob')->getTypes(),
'expanded' => true,
));
$this->validatorSchema['type'] = new
sfValidatorChoice(array(
'choices' => array_keys(Doctrine::getTable('JobeetJob')-
>getTypes()),
));
$this->widgetSchema['logo'] = new
sfWidgetFormInputFile(array(
'label' => 'Company logo',
));
$this->widgetSchema->setLabels(array(
'category_id' => 'Category',
'is_public' => 'Public?',
'how_to_apply' => 'How to apply?',
));
$this->validatorSchema['logo'] = new sfValidatorFile(array(
'required' => false,
'path' => sfConfig::get('sf_upload_dir').'/jobs',
'mime_types' => 'web_images',
));
$this->widgetSchema->setHelp('is_public', 'Whether the job
can also be published on affiliate websites or not.');
}
}
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
182
Manipuler les formulaires directement dans
les templates
Gnrer le rendu dun formulaire
Maintenant que la classe du formulaire est personnalise, il ne reste plus
qu lafficher. Dans Jobeet, le template du formulaire est exactement le
mme, que lon veuille crer une nouvelle offre ou que lon souhaite en
diter une existante. En fait, les deux fichiers newSuccess.php et
editSuccess.php sont sensiblement similaires.
Extrait du fichier apps/frontend/modules/job/templates/newSuccess.php
Le formulaire est lui-mme rendu dans le template partiel _form.php.
Lapplication Jobeet ncessite un rendu lgrement diffrent pour ce for-
mulaire, cest pourquoi le contenu du fichier _form.php doit tre rem-
plac par le code suivant.
Contenu du fichier apps/frontend/modules/job/templates/_form.php
Les helpers include_stylesheets_for_form() et
include_javascripts_for_form() importent les fichiers CSS et JavaS-
cript ncessaires au bon fonctionnement de certains widgets du formu-
laire. Bien que le formulaire doffre ne ncessite ni CSS ni JavaScript
particulier, la bonne pratique consiste toujours conserver ces helpers
<?php use_stylesheet('job.css') ?>
<h1>Post a Job</h1>
<?php include_partial('form', array('form' => $form)) ?>
<?php include_stylesheets_for_form($form) ?>
<?php include_javascripts_for_form($form) ?>
<?php echo form_tag_for($form, '@job') ?>
<table id="job_form">
<tfoot>
<tr>
<td colspan="2">
<input type="submit" value="Preview your job" />
</td>
</tr>
</tfoot>
<tbody>
<?php echo $form ?>
</tbody>
</table>
</form>
REMARQUE La feuille de style des offres
Si la feuille de style job.css na pas encore t
ajoute aux templates newSuccess.php et
editSuccess.php, cest le moment de le faire
pour ces derniers en utilisant <?php
use_stylesheet('job.css') ?>
1
0


A
c
c

r
e
r

l
a

g
e
s
t
i
o
n

d
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
183
dans le template titre prventif. Ce sera, en effet, un rel gain de temps
le jour o de vritables widgets spcifiques seront intgrs au formulaire.
Lapproche pragmatique reste bien souvent la meilleure !
Le helper form_tag_for() se charge de gnrer la balise <form> pour le for-
mulaire et la route donns. Dautre part, il fixe la mthode POST ou PUT
selon que lobjet associ au formulaire est nouveau ou non. Enfin, il prend
garde bien implmenter lattribut spcial multipart si le formulaire con-
tient au moins un widget de transfert de fichier. Dans le code prcdent, le
formulaire est entirement rendu grce linstruction <?php echo
$form ?>. La structure du langage echo appelle implicitement la mthode
__toString() du formulaire qui gnre tous les widgets, labels, messages
derreur et autres informations daide dclars dans la classe JobeetJobForm.
Cependant, la plupart des formulaires que lon trouve sur lInternet sont
trs spcifiques et peuvent tre, pour certains, relativement complexes
mettre en page. La section suivante apporte la documentation de base
pour gnrer un formulaire Symfony la main, champ par champ.
Personnaliser le rendu des formulaires
Par dfaut, linstruction <?php echo $form ?> gnre le formulaire sous
la forme dun tableau HTML o chaque ligne correspond un widget.
Or, la plupart du temps, le layout dun formulaire se rvle diffrent et
plus complexe selon laspect gnral souhait. Par exemple, il sera parfois
ncessaire dajouter des balises <fieldset> pour sparer les blocs de
mme nature ou bien simplement pour afficher deux champs particuliers
lun ct de lautre.
Dcouvrir les mthodes de lobjet sfForm
Lobjet de formulaire fournit plusieurs mthodes utiles pour faciliter la
personnalisation du rendu afin de ne pas tre contraint uniquement
une structure en tableau. Le tableau ci-dessous rsume les principales
mthodes quil est possible dutiliser.
Tableau 101 Liste des mthodes utiles au rendu de lobjet sfForm
Mthode Description
render() Gnre le formulaire (quivalent pour echo $form)
renderHiddenFields() Gnre les champs cachs
hasErrors() Retourne true si le formulaire a des erreurs
hasGlobalErrors() Retourne true si le formulaire a des erreurs globales
getGlobalErrors() Retourne un tableau derreurs globales
renderGlobalErrors() Gnre les erreurs globales
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
184
Comprendre et implmenter les mthodes de lobjet sfFormField
Au mme titre que pour lobjet sfForm, chaque champ peut tre rendu
individuellement grce aux mthodes de la classe sfFormField. Dans un
template, nimporte quel champ du formulaire peut tre rcupr unitai-
rement grce la syntaxe ArrayAccess de lobjet sfForm. Par exemple,
$form['company'] retourne lobjet sfFormField correspondant au champ
company du formulaire. Le tableau qui suit dresse une liste des mthodes
de rendu applicables nimporte quel champ dun formulaire.
Lexemple de code suivant est une syntaxe quivalente echo $form.
Cet autre bout de code illustre la manire de gnrer individuellement
un champ de formulaire complet dans un template au moyen des
mthodes de lobjet sfFormField.
Manipuler les formulaires dans les actions
Les parties prcdentes ont montr comment, dans Symfony, un formu-
laire est dclar, puis configur et enfin rendu dans un template. La der-
nire tape consiste donc expliquer de quelle manire un formulaire est
rendu dynamique dans les actions dun module.
Tableau 102 Liste des mthodes utiles au rendu de lobjet sfFormField
Mthode Description
renderRow() Gnre la ligne complte du champ
render() Retourne le code HTML du widget
renderLabel() Retourne le code HTML de la balise <label>
renderError() Gnre le message derreur du champ
renderHelp() Gnre laide du champ
<?php foreach ($form as $widget): ?>
<?php echo $widget->renderRow() ?>
<?php endforeach; ?>
<div class="form_field">
<?php echo $form['company']->renderError(); ?>
<?php echo $form['company']->renderLabel(); ?>
<?php echo $form['company']->render(); ?>
<div class="form_field_help">
<?php echo $form['company']->renderHelp(); ?>
</div>
</div>
1
0


A
c
c

r
e
r

l
a

g
e
s
t
i
o
n

d
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
185
Dcouvrir les mthodes autognres du module job
utilisant les formulaires
Pour linstant, Jobeet dispose dun formulaire capable de grer la cration
et ldition dune offre. Ce dernier se rsume toujours une classe de
configuration des champs ainsi qu un template partiel pour lafficher. Il
est dsormais temps de le mettre en uvre au sein des actions.
Le formulaire des offres est actuellement gr par cinq mthodes des
actions du module job :
new : affiche un formulaire vide pour crer une nouvelle offre ;
edit : affiche un formulaire prrempli pour diter une offre existante ;
create : cre la nouvelle offre partir des donnes saisies par
lutilisateur ;
update : met jour loffre existante partir des donnes saisies par
lutilisateur ;
processForm: appele par create et update, cette mthode se charge
de traiter le formulaire (validation, repeuplement du formulaire et
srialisation vers la base de donnes).
Tous les formulaires dcrivent le mme cycle de vie comme lillustre le
schma suivant.
Figure 101
Cycle de vie des formulaires
dans Symfony
GET /frontend_dev.php/job/new
POST /frontend_dev.php/job
GET /frontend_dev.php/job/TOKEN
newSuccess
showSuccess
$this->form = new JobeetJobForm(); <?php echo $form ?>
$this->form->bind($request->getParameter($form->getName()));
if ($this->form->isValid())
$this->form->save();
$this->redirect($this->generateUrl('job_show', $job));
Formulaire valide
3
2
Formulaire invalide
1
Afchage du formulaire
2
Soumission du formulaire
Prvisualisation de lofre
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
186
Traiter les formulaires dans les actions
Les sections suivantes sintressent la mise en pratique du formulaire
des offres dans les actions. Elles prsentent comment le framework
Symfony simplifie la manipulation du formulaire tout au long de son
cycle de vie en quelques lignes de code lmentaires. La premire tape
consiste rduire la mthode de traitement du formulaire
processForm().
Simplifier le traitement du formulaire dans le module job
Au chapitre 5, une route Doctrine ddie au module job a t cre. De ce
fait, le code de gestion du formulaire peut tre simplifi celui ci-dessous :
Contenu du fichier apps/frontend/modules/job/actions/actions.class.php
public function executeNew(sfWebRequest $request)
{
$this->form = new JobeetJobForm();
}
public function executeCreate(sfWebRequest $request)
{
$this->form = new JobeetJobForm();
$this->processForm($request, $this->form);
$this->setTemplate('new');
}
public function executeEdit(sfWebRequest $request)
{
$this->form = new JobeetJobForm($this->getRoute()-
>getObject());
}
public function executeUpdate(sfWebRequest $request)
{
$this->form = new JobeetJobForm($this->getRoute()-
>getObject());
$this->processForm($request, $this->form);
$this->setTemplate('edit');
}
public function executeDelete(sfWebRequest $request)
{
$request->checkCSRFProtection();
$job = $this->getRoute()->getObject();
$job->delete();
$this->redirect('job/index');
}
1
0


A
c
c

r
e
r

l
a

g
e
s
t
i
o
n

d
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
187
Comprendre le cycle de vie du formulaire
Quelques explications simposent pour comprendre tout le sens de ce
code. Que se passe-t-il lorsque lutilisateur cre ou dite une offre ?
Lorsque celui-ci navigue sur la page /job/new, une nouvelle instance du for-
mulaire est cre et passe au template (action new). Puis, ds que lutilisa-
teur soumet le formulaire (action create), ce dernier est initialis avec les
valeurs quil a saisies et le processus de validation est mis en route.
partir du moment o le formulaire est initialis, il est possible de con-
trler sa validit laide de la mthode isValid(). Si le formulaire est
valide (isValid() retourne true), alors la nouvelle offre est enregistre
en base de donnes ($form->save()) et lutilisateur est automatiquement
redirig vers la page de prvisualisation. Dans le cas contraire, le tem-
plate newSuccess.php est raffich avec les valeurs saisies ainsi que les
messages derreur gnrs.
La modification dune offre existante est sensiblement la mme. La seule
diffrence entre les actions new et edit rside dans le fait que lobjet
JobeetJob modifier est pass comme premier argument du construc-
teur du formulaire. Il servira alors dfinir les valeurs par dfaut de
chaque widget dans le template. Lensemble de ces valeurs correspond
un objet pour les formulaires Doctrine, alors quil sagit dun simple
tableau pour les formulaires traditionnels.
Dfinir les valeurs par dfaut dun formulaire gnr par Doctrine
Il existe deux manires de dfinir les valeurs par dfaut dun formulaire
de cration. La premire consiste dclarer les valeurs dans le schma de
dfinition de la base de donnes, tandis que la seconde invite passer un
objet JobeetJob prmodifi au constructeur du formulaire.
protected function processForm(sfWebRequest $request, sfForm
$form)
{
$form->bind(
$request->getParameter($form->getName()),
$request->getFiles($form->getName())
);
if ($form->isValid())
{
$job = $form->save();
$this->redirect($this->generateUrl('job_show', $job));
}
}
ASTUCE Changer le template par dfaut
dune action
La mthode setTemplate() change le tem-
plate utilis par dfaut pour une action donne. Si
le formulaire soumis nest pas valide, les mthodes
create et update utilisent le mme template
dans la mesure o les actions new et edit raffi-
chent le formulaire avec ses messages derreur.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
188
Le code ci-dessous initialise la valeur par dfaut (full-time) du widget
type du formulaire laide dun objet prrempli.
Redfinition de la mthode executeNew() du fichier apps/frontend/modules/job/
actions/actions.class.php
Quand le formulaire est initialis avec les valeurs postes, les valeurs par
dfaut sont automatiquement remplaces par les donnes saisies. En effet,
en cas derreur de validation, ces dernires servent repeupler le formulaire.
Protger le formulaire des offres par limplmentation
dun jeton
prsent, tout doit fonctionner correctement mais il reste encore un
dernier point rgler : le jeton correspondant ici au champ token. Pour
le moment, ce dernier doit tre rempli manuellement par lutilisateur.
Bien videmment, le principe du jeton, cest dtre gnr automatique-
ment lorsque la nouvelle offre est cre. Ce nest donc plus lutilisateur
de fournir lui-mme cette donne.
Gnrer le jeton automatiquement la cration
Le moyen le plus simple et le plus sr pour y parvenir est de raliser la
gnration du jeton dans la mthode save() de la classe JobeetJob. La
valeur du jeton doit tre dfinie juste avant que lobjet soit srialis en
base de donnes.
Surcharge de la mthode save() du fichier // lib/model/doctrine/
JobeetJob.class.php
public function executeNew(sfWebRequest $request)
{
$job = new JobeetJob();
$job->setType('full-time');
$this->form = new JobeetJobForm($job);
}
public function save(Doctrine_Connection $con = null)
{
// ...
if (!$this->getToken())
{
$this->setToken(sha1($this->getEmail().rand(11111, 99999)));
}
return parent::save($conn);
}
1
0


A
c
c

r
e
r

l
a

g
e
s
t
i
o
n

d
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
189
Dsormais, le champ token peut tre retir de la configuration du for-
mulaire en toute scurit.
Suppression du champ token dans le fichier lib/form/doctrine/
JobeetJobForm.class.php
Redfinir la route ddition de loffre grce au jeton
Si lon se souvient des cas dutilisation du chapitre 2, une offre demploi
est ditable condition que lutilisateur connaisse le jeton associ. Pour
le moment, il est trs facile dditer ou de supprimer nimporte quelle
offre en devinant seulement son URL. En effet, lURL qui mne au for-
mulaire ddition repose sur le schma job/ID/edit o la valeur de ID
correspond la cl primaire de loffre dans la base de donnes.
Par dfaut, une route sfDoctrineRouteCollection compose les URLs
partir de la cl primaire, mais il est bien sr possible de remplacer cette
dernire par nimporte quelle colonne unique en passant loption column.
Dfinition de la route job dans le fichier apps/frontend/config/routing.yml
Il faut remarquer au passage que la contrainte du paramtre token a t
modifie galement pour correspondre nimporte quelle chane de
caractres. En effet, le format obligatoire par dfaut de loption column
doit tre un entier positif en guise de cl unique.
class JobeetJobForm extends BaseJobeetJobForm
{
public function configure()
{
unset(
$this['created_at'], $this['updated_at'],
$this['expires_at'], $this['is_activated'],
$this['token']
);
// ...
}
// ...
}
job:
class: sfDoctrineRouteCollection
options: { model: JobeetJob, column: token }
requirements: { token: \w+ }
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
190
Dsormais, toutes les routes relatives aux offres demploi, mis part la
route job_show_user, intgrent le jeton. Par exemple, la route qui mne
ldition dune offre est formate de la faon suivante :
Enfin, il ne reste plus qu modifier le lien Edit du template
showSuccess.php.
Construire la page de prvisualisation
La page de prvisualisation est exactement la mme que la page de con-
sultation dune offre. Grce au routage, si lutilisateur arrive avec le bon
jeton, ce dernier sera accessible dans le paramtre token de la requte.
Dautre part, si lutilisateur entre avec une URL contenant le bon jeton,
une barre dadministration sera ajoute au-dessus de loffre. Il suffit alors
tout simplement de modifier le dbut du template showSuccess.php afin
que celui-ci puisse accueillir la barre dadministration. Aprs cela, le lien
edit situ en pied de page na plus qu tre retir.
Code ajouter au dbut du fichier apps/frontend/modules/job/templates/
showSuccess.php
Ltape suivante consiste crer le template partiel _admin.php en lui
ajoutant le contenu suivant.
http://jobeet.localhost/job/TOKEN/edit
<?php if ($sf_request->getParameter('token') == $job-
>getToken()): ?>
<?php include_partial('job/admin', array('job' => $job)) ?>
<?php endif; ?>
<!-- apps/frontend/modules/job/templates/_admin.php -->
<div id="job_actions">
<h3>Admin</h3>
<ul>
<?php if (!$job->getIsActivated()): ?>
<li><?php echo link_to('Edit', 'job_edit', $job) ?></li>
<li><?php echo link_to('Publish', 'job_edit', $job) ?></li>
<?php endif; ?>
<li><?php echo link_to('Delete', 'job_delete', $job,
array('method' => 'delete', 'confirm' => 'Are you sure?')) ?></li>
<?php if ($job->getIsActivated()): ?>
<li<?php $job->expiresSoon() and print
' class="expires_soon"' ?>>
1
0


A
c
c

r
e
r

l
a

g
e
s
t
i
o
n

d
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
191
Il y a beaucoup de code tudier. Nanmoins la plupart de celui-ci est
trs facile comprendre. Afin de rendre le template plus lisible et com-
prhensible, un certain nombre de mthodes raccourcies a t ajout la
classe JobeetJob.
Nouvelles mthodes de la classe lib/model/doctrine/JobeetJob.class.php
La barre dadministration affiche les diffrentes actions en fonction du
statut de loffre demploi.
<?php if ($job->isExpired()): ?>
Expired
<?php else: ?>
Expires in <strong><?php echo
$job->getDaysBeforeExpires() ?></strong> days
<?php endif; ?>
<?php if ($job->expiresSoon()): ?>
- <a href="">Extend</a> for another <?php echo
sfConfig::get('app_active_days') ?> days
<?php endif; ?>
</li>
<?php else: ?>
<li>
[Bookmark this <?php echo link_to('URL', 'job_show',
$job, true) ?> to manage this job in the future.]
</li>
<?php endif; ?>
</ul>
</div>
public function getTypeName()
{
$types = Doctrine::getTable('JobeetJob')->getTypes();
return $this->getType() ? $types[$this->getType()] : '';
}
public function isExpired()
{
return $this->getDaysBeforeExpires() < 0;
}
public function expiresSoon()
{
return $this->getDaysBeforeExpires() < 5;
}
public function getDaysBeforeExpires()
{
return floor((strtotime($this->getExpiresAt()) - time()) /
86400);
}
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
192
La barre dactivation du second cran sera mise en place ds la prochaine
section.
Activer et publier une offre
Prparer la route vers laction de publication
Dans la section prcdente se trouve un lien pour publier une offre
demploi. Celui-ci a besoin dtre modifi pour pointer vers laction
publish. Au lieu de crer une nouvelle route, il suffit de configurer la
route existante job comme le montre le code ci-dessous.
Configuration de la route job dans le fichier apps/frontend/config/routing.yml
Loption object_actions prend un tableau des actions additionnelles
pour lobjet donn. Ainsi, le lien de publication dune offre peut dsor-
mais tre modifi.
Figure 102
tat de la barre
dadministration
dune offre non active
Figure 103
tat de la barre
dadministration
dune offre active
job:
class: sfDoctrineRouteCollection
options:
model: JobeetJob
column: token
object_actions: { publish: put }
requirements:
token: \w+
1
0


A
c
c

r
e
r

l
a

g
e
s
t
i
o
n

d
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
193
Extrait du contenu du fichier apps/frontend/modules/job/templates/_admin.php
Implmenter la mthode executePublish()
La dernire tape consiste crer laction executePublish() dans le
fichier actions.class.php du module job.
Mthode executePublish() ajouter au fichier apps/frontend/modules/job/actions/
actions.class.php
Le lecteur assidu aura remarqu que le lien Publish est soumis laide
de la mthode HTTP PUT. Pour simuler cette mthode PUT, le lien est
automatiquement converti en un formulaire quand on clique dessus
grce au JavaScript.
Dautre part, comme la protection CSRF (Cross-Site Request Forgeries)
a t active pour Jobeet lors du premier chapitre, le helper link_to()
intgre un jeton CSRF au lien, tandis que la mthode
checkCSRFProtection() de lobjet requte sassure de sa validit au
moment de son envoi.
Implmenter la mthode publish() de lobjet JobeetJob
La mthode executePublish() du contrleur utilise une nouvelle
mthode publish() applique sur lobjet JobeetJob. Celle-ci se rsume
simplement au code ci-dessous.
<li>
<?php echo link_to('Publish', 'job_publish', $job,
array('method' => 'put')) ?>
</li>
public function executePublish(sfWebRequest $request)
{
$request->checkCSRFProtection();
$job = $this->getRoute()->getObject();
$job->publish();
$this->getUser()->setFlash('notice', sprintf('Your job is now
online for %s days.', sfConfig::get('app_active_days')));
$this->redirect($this->generateUrl('job_show_user', $job));
}
BONNE PRATIQUE
Prcisions sur la faille de scurit CSRF
Il sagit dune faille de scurit trs rpandue mais
malheureusement trs rarement prise en compte
dans le dveloppement dapplications. Cette faille
profite de la confiance qua lutilisateur dans les
systmes dauthentification pour effectuer cer-
taines actions son insu. Lun des moyens les plus
efficaces pour se protger de ces attaquesreste
limplmentation dun jeton, comme le fait le
helper link_to() par dfaut.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
194
Dclaration de la mthode publish() dans le fichier lib/model/doctrine/
JobeetJob.class.php
La nouvelle fonctionnalit de publication doffre demploi peut mainte-
nant tre teste dans un navigateur. Nanmoins, il reste encore quelque
chose fixer : Les offres non actives ne doivent pas tre accessibles, ce
qui signifie quelles ne doivent plus tre affiches sur la page daccueil de
Jobeet, et ne doivent pas non plus tre atteignables par leur URL.
Empcher la publication et laccs aux offres non actives
Dans les prcdents chapitres, une mthode addActiveJobsQuery() avait
t cre pour restreindre un critre aux seules offres actives. Il suffit
alors dditer et dajouter de nouvelles contraintes la fin de la mthode,
comme le montre le code suivant.
Mthode addActiveJobsQuery() du fichier lib/model/doctrine/JobeetJobTable.class.php
Cest fini ! Il ne reste plus qu tester dans le navigateur que lensemble
se comporte correctement. Dsormais, toutes les offres non actives ont
disparu de la page daccueil et ne sont plus accessibles en devinant leur
URL. Elles restent en revanche atteignables si quelquun connat le jeton
de loffre et sen sert dans lURL. Dans ce cas prcis, ce sera la page de
prvisualisation de loffre et sa barre dadministration qui seront affi-
ches lcran.
Cest lun des principaux avantages du motif de conception MVC et de
la refactorisation qui a t ralise tout au long de ce parcours. Seule-
ment un unique changement dans une seule mthode a permis dappli-
quer la nouvelle contrainte sur lensemble de lapplication.
public function publish()
{
$this->setIsActivated(true);
$this->save();
}
public function addActiveJobsQuery(Doctrine_Query $q = null)
{
// ...
$q->andWhere($alias . '.is_activated = ?', 1);
return $q;
}
1
0


A
c
c

r
e
r

l
a

g
e
s
t
i
o
n

d
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
195
Lorsque la mthode getWithJobs() a t cre, lutilisation de la
mthode addActiveJobsQuery() a t oublie, ce qui signifie quil faille
lditer et ajouter la nouvelle contrainte.
En rsum
Ce chapitre a abord toute une srie de nouvelles informations, qui vous
ont permis dacqurir une meilleure connaissance du framework de for-
mulaires de Symfony.
Un point fondamental reste encore non trait au terme de ces quelques
pages puisquen effet, aucun nouveau test na t implment pour les
nouvelles fonctionnalits. Dans la mesure o lcriture de tests est un
enjeu crucial dans le dveloppement dune application, ce sera le tout
premier thme abord au chapitre suivant.
class JobeetCategoryTable extends Doctrine_Table
{
public function getWithJobs()
{
// ...
$q->andWhere('j.is_activated = ?', 1);
return $q->execute();
}
}
Groupe Eyrolles, 2008
chapitre 11
Groupe Eyrolles, 2008
Tester les formulaires
Les formulaires sont des composants complexes grer
dans une application web. Heureusement, le chapitre
prcdent a montr comment Symfony en facilite grandement
la gestion laide de son framework interne.
Ce chapitre approfondit encore les connaissances lies
aux formulaires en montrant les manires de les tester
fonctionnellement.
MOTS-CLS :
BTests fonctionnels
BScurit XSS et CSRF
BCrer des tches automatiques
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
198
ce stade, les utilisateurs ont la capacit de naviguer sur lapplication
la recherche doffres demploi, et den ajouter de nouvelles grce aux for-
mulaires. Nanmoins, le formulaire de cration dune nouvelle offre na
pas encore t test pour sassurer quil se comporte normalement. Ce
chapitre couvre avant tout les notions de tests fonctionnels des formu-
laires, et apporte quelques astuces supplmentaires propos du fra-
mework de formulaire.
Utiliser le framework de formulaires de
manire autonome
Les composants de Symfony sont relativement dcoupls, ce qui signifie
que la plupart dentre eux sont exploitables de manire autonome
lextrieur du framework. Le framework de formulaire en fait partie
puisquil ne dispose daucune dpendance avec le reste de Symfony. Les
classes de formulaire, les widgets ainsi que les validateurs sont ainsi ru-
tilisables hors de lenvironnement Symfony. Pour ce faire, il suffit de
rcuprer les rpertoires lib/form/, lib/widget/ et lib/validator/ qui
se trouvent dans le rpertoire lib/vendor/symfony/ dun projet.
Parmi ces composants autonomes figure galement le framework interne
de routage, accessible dans le rpertoire lib/routing/. Celui-ci permet
de profiter pleinement et gratuitement des URLs bien formes.
Aprs cette brve introduction concernant la facilit dutilisation du fra-
mework de formulaire en dehors de lenvironnement Symfony, il est
temps dentrer dans le vif du sujet. Il sagit de poursuivre le dveloppe-
ment de lapplication en sintressant aux tests fonctionnels des formu-
laires. Le neuvime chapitre a prsent globalement le but et le principe
de fonctionnement des scnarios de tests fonctionnels. Jusqu mainte-
Figure 111
Diagramme des composants
indpendants de Symfony
1
1


T
e
s
t
e
r

l
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
199
nant, ces derniers ont essentiellement servi analyser le contenu HTML
de la rponse grce notamment aux slecteurs CSS 3. Cet outil recle
encore bien dautres fonctionnalits puisquil offre galement la capacit
de tester nimporte quelle classe de formulaire.
crire des tests fonctionnels pour les
classes de formulaire
Les prochaines sections prsentent les diffrents outils du framework de
tests fonctionnels qui permettent de tester les formulaires. Ces derniers assu-
rent au dveloppeur que le formulaire quil a crit se comporte convenable-
ment en simulant le remplissage des champs ainsi que les tlchargements
de fichiers, et en diagnostiquant les erreurs gnres par les validateurs.
Tester lenvoi du formulaire de cration doffre
La premire tape du processus de test du formulaire de cration dune
nouvelle offre consiste sassurer que ce dernier est bien envoy avec
toutes les valeurs obligatoires renseignes. Pour commencer, le code ci-
dessous doit tre ajout la fin du fichier de tests fonctionnels
jobActionsTest.php.
Code ajouter la fin du fichier test/functional/frontend/jobActionsTest.php
Au neuvime chapitre, la mthode click() de lobjet sfTestFunctional
a t utilise pour simuler les clics sur des liens prsents dans la page.
Cette mthode click() fonctionne de la mme manire pour soumettre
un formulaire. En effet, elle accepte en second argument un tableau
associatif des valeurs envoyer pour chaque champ. Tel un vritable
navigateur, lobjet $browser fusionnera les valeurs par dfaut du formu-
laire avec celles qui ont t soumises.
$browser->info('3 - Post a Job page')->
info(' 3.1 - Submit a Job')->
get('/job/new')->
with('request')->begin()->
isParameter('module', 'job')->
isParameter('action', 'new')->
end()
;
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
200
Renommer le nom des champs du formulaire
Cependant, il faut connatre lavance le nom des champs pour lesquels
les valeurs doivent tre transmises. En ouvrant le code source HTML ou
bien en utilisant la fonctionnalit Formulaires > Afficher les dtails du for-
mulaire de la Firefox Web Developer Toolbar sur le formulaire en ques-
tion, on remarque que le nom du champ company est en fait
jobeet_job[company].
Lorsque PHP rencontre un champ de saisie avec un nom comme
jobeet_job[company], il le convertit automatiquement en un tableau
dont le nom est jobeet_job. Ce format de nommage des champs est un
peu complexe et ne facilite pas forcment la tche. En ajoutant la ligne
de code suivante la fin de la mthode configure() de la classe
JobeetJobForm, les noms des champs suivront alors le format $job[%s].
Soumettre le formulaire laide de la mthode click()
Aprs ce changement, le nom du champ company devrait tre
job[company]. Il est maintenant lheure de tester le clic sur le bouton Pre-
view your job en fournissant un tableau de valeurs valides au formulaire,
par le biais du second argument de la mthode click().
Code de test de lenvoi du formulaire ajouter au fichier test/functional/frontend/
jobActionsTest.php
$this->widgetSchema->setNameFormat('job[%s]');
$browser->info('3 - Post a Job page')->
info(' 3.1 - Submit a Job')->
get('/job/new')->
with('request')->begin()->
isParameter('module', 'job')->
isParameter('action', 'new')->
end()
;
click('Preview your job', array('job' => array(
'company' => 'Sensio Labs',
'url' => 'http://www.sensio.com/',
'logo' => sfConfig::get('sf_upload_dir')
.'/jobs/sensio-labs.gif',
'position' => 'Developer',
'location' => 'Atlanta, USA',
'description' => 'You will work with symfony to develop
websites for our customers.',
'how_to_apply' => 'Send me an email',
'email' => 'for.a.job@example.com',
'is_public' => false,
)))->
1
1


T
e
s
t
e
r

l
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
201
Le code ci-dessus ne ralise rien dextraordinaire. En effet, il se charge
simplement de transmettre les donnes du formulaire lorsque lon clique
sur le bouton Preview your job, puis de vrifier que la page suivante cor-
respond bien laction create du module job. La simulation des envois
de fichiers est elle aussi prise en compte en fournissant le chemin absolu
vers un fichier comme le montre le champ logo.
Dcouvrir le testeur sfTesterForm
Lobjet sfTesterForm est un testeur qui permet de vrifier les donnes
dun formulaire comme le ralise le testeur sfTesterResponse avec la
rponse gnre.
Tester si le formulaire est erron
Le testeur sfTesterForm fournit une mthode bien pratique,
hasErrors(), pour tester si oui ou non le formulaire a gnr des erreurs
daprs les donnes qui lui ont t transmises. Lexemple de code ci-des-
sous illustre son utilisation.
Les mthodes de lobjet sfTesterForm
Lobjet sfTesterForm dispose de bien dautres mthodes utiles pour rcu-
prer des informations sur ltat du formulaire, comme les erreurs gn-
res. Le tableau suivant dresse une liste exhaustive des mthodes de cet
objet.
with('request')->begin()->
isParameter('module', 'job')->
isParameter('action', 'create')->
end() ;
with('form')->begin()->
hasErrors(false)->
end();
Tableau 111 Liste des mthodes de lobjet sfTesterForm
Rpertoire Description
getForm() Retourne lobjet de formulaire courant
hasErrors() Retourne si oui ou non le formulaire a des erreurs
hasGlobalError() Retourne si oui ou non le formulaire a des erreurs globales
isError($field) Retourne si oui ou non le champ donn a des erreurs
debug() Affiche tout ltat du formulaire en cours
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
202
Dboguer un formulaire
Lorsque un test choue, le premier moyen de faciliter la dcouverte du
bug est dutiliser la mthode debug() sur le testeur sfTesterResponse
afin dobtenir la rponse gnre par le serveur. Dans le cas dun formu-
laire, ce nest vritablement pas pratique dans la mesure o il faut
plonger soi-mme dans le code HTML la recherche des erreurs gn-
res pour chaque champ.
Heureusement, le testeur sfTesterForm fournit la mthode debug() qui
permet dimprimer en sortie ltat complet du formulaire avec les erreurs
leves. Le bout de code ci-dessous montre comment utiliser cette
mthode sur le testeur de formulaire.
Tester les redirections HTTP
Dans Jobeet, lorsque le formulaire soumis par lutilisateur est valide, ce
dernier est automatiquement redirig vers la page de consultation de
loffre se trouvant laction show. De ce fait, il est ncessaire de sassurer
que la redirection a bien eu lieu grce aux mthodes isRedirected() et
followRedirect() comme le montre lexemple de code ci-dessous.
La mthode isRedirected() indique si oui ou non il y a eu une redirec-
tion tandis que la mthode followRedirect() suit cette dernire. Pour-
quoi la classe du navigateur ne suit-elle pas automatiquement la
redirection ? La raison est simple : cest pour laisser au dveloppeur la
libert danalyser ltat des objets avant la redirection.
Tester les objets gnrs par Doctrine
Dans la plupart des cas, les formulaires sont destins rcuprer les
informations de lutilisateur afin de les stocker dans la base de donnes.
Dans Symfony, cest exactement ce que font les formulaires Doctrine
puisquils permettent de sauvegarder automatiquement les informations
saisies dans une table. Cependant, comment sassurer que les informa-
tions ont bien t enregistres et que les valeurs de chaque champ corres-
pondent ce que lon souhaite ?
with('form')->debug()
isRedirected()->
followRedirect()->
with('request')->begin()->
isParameter('module', 'job')->
isParameter('action', 'show')->
end();
1
1


T
e
s
t
e
r

l
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
203
Activer le testeur sfTesterDoctrine
Le framework interne de tests fonctionnels de Symfony introduit un
nouveau testeur, lobjet sfTesterDoctrine, qui permet dinterroger une
base de donnes. Contrairement aux testeurs vus jusqu prsent, le tes-
teur Doctrine nest pas rfrenc par dfaut dans lobjet
sfTestFunctional. Pour lactiver, il suffit de le dfinir explicitement
comme lexplique le code ci-dessous.
Tester lexistence dun objet Doctrine dans la base de donnes
Dans le cadre de lapplication Jobeet, il est intressant de contrler que
loffre demploi a bien t cre et que la colonne is_activated contient
la valeur false dans la mesure o lutilisateur ne la pas encore publie.
Grce au testeur sfTesterDoctrine et sa mthode check(), il est tout
fait possible de vrifier lexistence dun ou de plusieurs objets dans la
base de donnes qui correspondent un critre donn. Le code suivant
illustre lutilisation du testeur Doctrine pour contrler la cration de la
nouvelle offre demploi.
Le critre de recherche de la mthode check() peut sexprimer de deux
manires diffrentes. Il sagit en effet de passer soit un tableau associatif
comme dans lexemple ci-dessus, soit une instance de la classe
Doctrine_Query dans le cas de requtes plus complexes. De plus, un troi-
sime argument facultatif peut lui tre transmis. Si ce dernier est boo-
len, il spcifie sil faut tester lexistence (true par dfaut) ou bien
labsence (false) de lobjet dans la base de donnes. En revanche, si un
nombre entier est pass en paramtre, la mthode check() vrifiera que
le critre correspond au nombre de rsultats.
Tester les erreurs des champs du formulaire
Tester un formulaire avec des valeurs valides ne suffit pas. Il faut aussi
sassurer quil se comporte correctement lorsque des donnes invalides,
voire potentiellement dangereuses, lui sont transmises. La premire vri-
fication consiste donc sassurer que ces donnes ne sont pas considres
$browser->setTester('doctrine', 'sfTesterDoctrine');
with('doctrine')->begin()->
check('JobeetJob', array(
'location' => 'Atlanta, USA',
'is_activated' => false,
'is_public' => false,
))->
end()
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
204
comme valides par les validateurs quelles traversent, et que ces derniers
gnrent effectivement les messages derreur adquats.
La mthode isError() pour le contrle des champs
La mthode isError() du testeur sfTesterForm permet de tester si un
champ du formulaire est erron en lui passant en paramtres le nom du
champ concern et le code derreur du validateur (required, invalid,
max_length, min_length).
Le code qui suit vrifie que le formulaire lve trois erreurs pour les
champs description, how_to_apply et email. Pour les deux premiers, il
sagit de sassurer que leur valeur respective ne peut rester vide tandis que
pour le champ email, on vrifie que la valeur soumise ne correspond pas
un format dadresse e-mail valide.
En prenant une valeur entire plutt quun boolen comme argument, la
mthode hasErrors() vrifie que le formulaire a gnr exactement ce
nombre derreurs. Dans le cas du test dun formulaire avec des valeurs
invalides, il est inutile de tester chaque champ. Seuls les plus sensibles ou
les plus spcifiques peuvent tre contrls pour valider que le formulaire
est bien mis en chec avec des donnes invalides. De plus, les compo-
sants du framework interne de formulaire ont dj t tests, ce qui
donne lassurance quils se comportent correctement.
Les vritables messages derreur peuvent galement tre tests en utilisant
la mthode checkElement() du testeur sfTesterResponse. Cette tech-
nique est particulirement recommande lorsque les formulaires ont un
layout personnalis. Dans lapplication Jobeet, le layout du formulaire est
celui par dfaut, cest pourquoi les messages derreur ne sont pas tests.
$browser->
info(' 3.2 - Submit a Job with invalid values')->
get('/job/new')->
click('Preview your job', array('job' => array(
'company' => 'Sensio Labs',
'position' => 'Developer',
'location' => 'Atlanta, USA',
'email' => 'not.an.email',
)))->
with('form')->begin()->
hasErrors(3)->
isError('description', 'required')->
isError('how_to_apply', 'required')->
isError('email', 'invalid')->
end()
;
1
1


T
e
s
t
e
r

l
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
205
Tester la barre dadministration dune offre
Ltape suivante consiste tester les liens de la barre dadministration de la
page de prvisualisation dune offre. Lorsquune offre nest pas encore
active, lutilisateur a toujours la capacit de lditer, de la publier ou bien
de la supprimer en cliquant sur le lien correspondant. Afin de tester
chacun de ces liens, il est ncessaire de crer une nouvelle offre. Bien
entendu, il est hors de question de copier/coller le code de cration dune
offre pour chacun des cas. Ce serait en effet laborieux, difficilement main-
tenable et une vritable perte de temps. Lidal est donc de mutualiser ce
code dans une nouvelle mthode de la classe JobeetTestFunctional
comme le montre le code suivant.
Mthode createJob() ajouter au fichier lib/test/JobeetTestFunctional.class.php
La nouvelle mthode createJob() cre une nouvelle offre, suit la redi-
rection et retourne le navigateur afin de ne pas casser linterface fluide.
Un tableau de valeurs peut galement lui tre fourni en paramtre.
Celui-ci sera fusionn avec les valeurs par dfaut afin de pouvoir red-
finir certaines dentre elles facilement.
class JobeetTestFunctional extends sfTestFunctional
{
public function createJob($values = array())
{
return $this->
get('/job/new')->
click('Preview your job',
array('job' => array_merge(array(
'company' => 'Sensio Labs',
'url' => 'http://www.sensio.com/',
'position' => 'Developer',
'location' => 'Atlanta, USA',
'description' => 'You will work with symfony to develop
websites for our customers.',
'how_to_apply' => 'Send me an email',
'email' => 'for.a.job@example.com',
'is_public' => false,
), $values)))->
followRedirect()
;
}
// ...
}
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
206
Forcer la mthode HTTP dun lien
Forcer lutilisation de la mthode HTTP PUT
Au chapitre prcdent, le lien Publish a t configur pour fonctionner
avec la mthode HTTP PUT. Comme les navigateurs ne supportent pas
les requtes PUT, le helper link_to() convertit le lien en un formulaire
laide dun script JavaScript.
Cependant, le navigateur de tests fonctionnels est incapable dexcuter le
moindre code JavaScript, cest pourquoi la mthode PUT doit tre force
manuellement en la passant comme troisime argument facultatif de la
mthode click().
De plus, le helper link_to() embarque un jeton CSRF puisque la pro-
tection CSRF a t active lors du premier chapitre. Loption _with_csrf
simule ce jeton.
Il est alors possible de reproduire cet exemple pour tester le lien Delete
qui ncessite, quant lui, le recours la mthode HTTP DELETE.
Forcer lutilisation de la mthode HTTP DELETE
Le fonctionnement du lien Delete est exactement le mme que celui du
bouton Publish. Il sagit en effet de fournir cette fois-ci la valeur delete
au paramtre method, puis de sassurer que lobjet a bien t supprim de
la base de donnes grce au testeur sfTesterDoctrine.
$browser->info(' 3.3 - On the preview page, you can publish the
job')->
createJob(array('position' => 'FOO1'))->
click('Publish', array(), array('method' => 'put',
'_with_csrf' => true))->
with('doctrine')->begin()->
check('JobeetJob', array(
'position' => 'FOO1',
'is_activated' => true,
))->
end()
;
$browser->info(' 3.4 - On the preview page, you can delete the
job')->
createJob(array('position' => 'FOO2'))->
click('Delete', array(), array('method' => 'delete',
'_with_csrf' => true))->
with('doctrine')->begin()->
check('JobeetJob', array(
1
1


T
e
s
t
e
r

l
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
207
La section suivante explique en quoi lcriture de tests fonctionnels est
un excellent moyen de dcouvrir des bogues ou bien de dtecter des
fonctionnalits encore non implmentes
crire des tests fonctionnels afin de
dcouvrir des bogues
Lorsquune offre est publie, il devient impossible de lditer par la suite.
Bien que le lien Edit ait totalement disparu de la page de prvisualisation,
il est toujours possible davoir accs au formulaire ddition de loffre via
lURL. Le seul moyen efficace de le vrifier est bien sr dcrire quelques
tests automatiques.
Simuler lautopublication dune offre
La premire tape consiste diter la mthode createJob() en lui ajou-
tant un nouvel argument facultatif permettant de forcer lautopublica-
tion de loffre avant dcrire une nouvelle mthode getJobByPosition().
Cette dernire se charge de rcuprer et de retourner une offre demploi
partir de la valeur de la colonne position.
Mthodes createJob() et getJobByPosition() du fichier lib/test/
JobeetTestFunctional.class.php
'position' => 'FOO2',
), false)->
end()
;
class JobeetTestFunctional extends sfTestFunctional
{
public function createJob($values = array(), $publish = false)
{
$this->
get('/job/new')->
click('Preview your job',
array('job' => array_merge(array(
'company' => 'Sensio Labs',
'url' => 'http://www.sensio.com/',
'position' => 'Developer',
'location' => 'Atlanta, USA',
'description' => 'You will work with symfony to develop
websites for our customers.',
'how_to_apply' => 'Send me an email',
'email' => 'for.a.job@example.com',
'is_public' => false,
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
208
Contrler la redirection vers une page derreur 404
Lorsquune offre est publie, laccs au formulaire ddition ne doit plus
tre possible et doit conduire vers une page derreur 404. Pour sen
assurer, il est ncessaire dcrire quelques tests fonctionnels comme le
prsente le morceau de code ci-dessous.
Les trois lignes de code en exergue dcrivent le scnario de test suivant :
1 une nouvelle offre est cre et autopublie ;
2 le jeton de celle-ci est rcupr pour construire lURL du formulaire
ddition et sy rendre ;
), $values)))->
followRedirect()
;
if ($publish)
{
$this->
click('Publish', array(), array('method' => 'put',
'_with_csrf' => true))->
followRedirect()
;
}
return $this;
}
public function getJobByPosition($position)
{
$q = Doctrine_Query::create()
->from('JobeetJob j')
->where('j.position = ?', $position);
return $q->fetchOne();
}
// ...
}
$browser->info(' 3.5 - When a job is published, it cannot be
edited anymore')->
createJob(array('position' => 'FOO3'), true)->
get(sprintf('/job/%s/edit', $browser-
>getJobByPosition('FOO3')->getToken()))->
with('response')->begin()->
isStatusCode(404)->
end()
;
1
1


T
e
s
t
e
r

l
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
209
3 le testeur de rponse vrifie que le statut HTTP de la rponse est bel
et bien gal 404 (page introuvable).
En excutant la suite de tests fonctionnels, le test du statut HTTP de la
rponse choue. Ce nest finalement pas si tonnant puisque limplmen-
tation de cette fonctionnalit a t oublie lors du chapitre prcdent.
En somme, les tests automatiques sont un excellent moyen de faire
apparatre des effets de bord, des bogues ou bien encore des fonctionna-
lits importantes non implmentes. Lcriture de tests demande aux
dveloppeurs de penser tous les cas de figure possibles, ce qui nest pas
systmatique lorsquils dveloppent chaque fonctionnalit.
Empcher laccs au formulaire ddition lorsque loffre
est publie
Fixer le bogue dcouvert dans la section prcdente est prsent trs
simple dans la mesure o le test fonctionnel en donne la solution : redi-
riger vers une page derreur 404 lorsque loffre demande est dj
publie. Pour ce faire, il suffit davoir recours la mthode
forward404If() dans laction executeEdit() du module job.
Mthode executeEdit() du fichier apps/frontend/modules/job/actions/
actions.class.php
Le correctif est trivial mais ne garantit pas que tout fonctionne toujours
correctement. Un moyen simple de sen assurer est douvrir le navigateur
et de tester toutes les combinaisons possibles pour accder au formulaire
ddition. Cest une mthode somme toute fastidieuse
La meilleure faon de procder consiste bien videmment relancer
toute la suite de tests fonctionnels car ces derniers permettent dune part
de vrifier que le correctif rpond cette fois-ci au test crit, et dautre
part quaucune rgression fonctionnelle na aussi t entrane suite la
modification de laction executeEdit().
public function executeEdit(sfWebRequest $request)
{
$job = $this->getRoute()->getObject();
$this->forward404If($job->getIsActivated());
$this->form = new JobeetJobForm($job);
}
BONNE PRATIQUE
viter les tches fastidieuses
Les tches fastidieuses sont nombreuses dans le
dveloppement dune application web. Nan-
moins, il faut se rappeler que si une tche est fasti-
dieuse et rbarbative, il existe probablement un
outil permettant de les raliser la place du dve-
loppeur. Cest un tat desprit garder constam-
ment, surtout lors de lutilisation doutils aussi
complets que Symfony.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
210
Tester la prolongation dune offre
Dans Jobeet, lorsquune offre demploi expire dans les cinq prochains
jours, ou lorsquelle est dj arrive expiration, lutilisateur a la possibi-
lit de prolonger sa dure de vie de trente jours supplmentaires
compter de la date courante.
Comprendre le problme des offres expires ractiver
Tester cette contrainte dans un navigateur nest pas si facile dans la mesure
o la date dexpiration est automatiquement dfinie trente jours lors de la
cration de loffre. Ainsi, lorsque lon accde la page de consultation de
loffre, le lien pour la prolonger nest pas prsent. Bien sr, il est possible de
pirater la date dexpiration dans la base de donnes, ou bien de person-
naliser le template pour quil affiche toujours le lien, mais cest la fois fas-
tidieux et annonciateur derreur. On laura devin, crire des tests est une
fois de plus une aide particulirement apprcie.
Une route ddie pour prolonger la dure dune offre
Pour permettre lauteur de prolonger son annonce sur le site, la pre-
mire tape consiste comme dhabitude penser puis dfinir la route
correspondante. Cest exactement ce que ralise le code ci-dessous en
ajoutant une nouvelle route la collection de routes Doctrine de lobjet.
Ajout de la nouvelle route extend la collection de routes dune offre dans le fichier
apps/frontend/config/routing.yml
Aprs cela, il suffit de mettre jour le template partiel_admin.php afin de
lui spcifier la mthode PUT pour le lien Extend.
job:
class: sfDoctrineRouteCollection
options:
model: JobeetJob
column: token
object_actions: { publish: PUT, extend: PUT }
requirements:
token: \w+
1
1


T
e
s
t
e
r

l
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
211
Extrait du fichier apps/frontend/modules/job/templates/_admin.php
Implmenter la mthode executeExtend() aux actions
du module job
Maintenant que la route est proprement dfinie, le prochain objectif est
dimplmenter la nouvelle action executeExtend() qui a pour rle de
prolonger la dure de vie dune offre, condition que celle-ci arrive
bientt expiration.
Mthode executeExtend() ajouter au fichier apps/frontend/modules/job/actions/
actions.class.php
Le code de cette nouvelle action est trs simple comprendre.
1 La mthode checkCSRFProtection() contrle la validit du jeton
transmis suite au clic sur le lien Extend.
2 Puis loffre demploi est retrouve partir de sa route et de son propre
jeton. La mthode extend() de lobjet JobeetJob prolonge la dure
de vie de loffre pour une nouvelle priode de 30 jours condition
que celle-ci arrive expiration prochainement. Dans le cas contraire,
lutilisateur est automatiquement redirig vers une page derreur 404.
3 Enfin, si tout sest bien droul, lutilisateur est automatiquement
redirig vers son annonce en profitant dun message lui informant de
la nouvelle date dexpiration de son offre.
<?php if ($job->expiresSoon()): ?>
- <?php echo link_to('Extend', 'job_extend', $job,
array('method' => 'put')) ?> for another <?php echo
sfConfig::get('app_active_days') ?> days
<?php endif; ?>
public function executeExtend(sfWebRequest $request)
{
$request->checkCSRFProtection();
$job = $this->getRoute()->getObject();
$this->forward404Unless($job->extend());
$this->getUser()->setFlash('notice', sprintf('Your job
validity has been extend until %s.', date('m/d/Y',
strtotime($job->getExpiresAt()))));
$this->redirect($this->generateUrl('job_show_user', $job));
}
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
212
Implmenter la mthode extend() dans JobeetJob
prsent, il ne reste plus qu implmenter la mthode extend() la
classe JobeetJob avant de sassurer que cette nouvelle fonctionnalit est
entirement oprationnelle grce quelques scnarios de tests fonction-
nels. Le code suivant prsente le dtail de la mthode extend().
Mthode extend() ajouter au fichier lib/model/doctrine/JobeetJob.class.php
L encore, le code de la mthode est suffisamment clair, intuitif et expli-
cite pour ne pas tre comment davantage. Cette mthode retourne
immdiatement false si loffre nest pas sur le point dexpirer. Dans le
cas contraire, la nouvelle date dexpiration est recalcule et lobjet est
srialis en base de donnes avant de retourner true.
Tester la prolongation de la dure de vie dune offre
La dernire tape consiste vrifier avec quelques scnarios de test que
la nouvelle fonctionnalit a bien t implmente et quelle ne provoque
pas deffet de bord ou de rgression pour les autres fonctionnalits dj
implmentes.
Une offre ne peut tre prolonge si elle nexpire pas bientt
Le premier test effectuer doit vrifier que lutilisateur ne peut pro-
longer la dure de vie de son annonce si cette dernire nest pas sur le
point dexpirer. Le test contrle que lutilisateur est redirig automati-
quement vers une page derreur 404 comme le montre le bout de code
ci-dessous.
class JobeetJob extends BaseJobeetJob
{
public function extend()
{
if (!$this->expiresSoon())
{
return false;
}
$this->setExpiresAt(date('Y-m-d', time() + 86400 *
sfConfig::get('app_active_days')));
$this->save();
return true;
}
// ...
}
1
1


T
e
s
t
e
r

l
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
213
On note ici lutilisation de la mthode call() la place de get() pour ex-
cuter la prolongation de la dure de vie de loffre. call() sert effectivement
appeler des URLs dont la mthode HTTP est diffrente de GET ou POST.
Une offre peut tre prolonge uniquement si elle expire bientt
Le second test consiste vrifier cette fois-ci quune offre prte expirer
peut tre prolonge. Cest le scnario que teste le code suivant.
Quelques nouvelles explications simposent ici. De la mme manire quavec
le scnario prcdent, la mthode call() appelle lURL avec la mthode
PUT et un jeton. On constate que lutilisateur est bien redirig mais la redi-
rection nest pas suivie afin deffectuer quelques tests supplmentaires.
Tout dabord, loffre demploi est rafrachie ($job->refresh()) dans le
but de prendre en compte les modifications. Ensuite, le timestamp sau-
vegard en base de donnes est compar avec celui de la date courante
plus trente jours laide de lobjet lime_test du navigateur.
$browser->info(' 3.6 - A job validity cannot be extended before
the job expires soon')->
createJob(array('position' => 'FOO4'), true)->
call(sprintf('/job/%s/extend',
$browser->getJobByPosition('FOO4')->getToken()),
'put', array('_with_csrf' => true))->
with('response')->begin()->
isStatusCode(404)->
end()
;
$browser->info(' 3.7 - A job validity can be extended when the
job expires soon')->
createJob(array('position' => 'FOO5'), true)
;
$job = $browser->getJobByPosition('FOO5');
$job->setExpiresAt(date('Y-m-d'));
$job->save();
$browser->
call(sprintf('/job/%s/extend', $job->getToken()), 'put',
array('_with_csrf' => true))->
with('response')->isRedirected()
;
$job->refresh();
$browser->test()->is(
date('y/m/d', strtotime($job->getExpiresAt())),
date('y/m/d', time() + 86400 *
sfConfig::get('app_active_days'))
);
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
214
Scuriser les formulaires
Le framework interne de formulaire de Symfony intgre nativement des
points de scurit diffrents endroits. La cration de jeton unique pour
chaque formulaire, lchappement automatique des donnes contre les
failles de type Cross Site Scripting (XSS) ou bien encore linjection
de transactions SQL lors de la sauvegarde dun objet en base de donnes
sont dautant de points sensibles protger. Heureusement, Symfony
soulage le dveloppeur de cette tche.
Srialisation dun formulaire Doctrine
Les formulaires Doctrine sont trs simples utiliser dans la mesure o ils
automatisent une grande partie du travail du dveloppeur. Par exemple,
srialiser un formulaire Doctrine en base de donnes consiste en un
simple appel la mthode save() du formulaire : $form->save().
Mais concrtement, que se passe-il dans cette mthode ? La mthode
save() droule en fait les tapes suivantes une une :
1 dmarrage dune transaction SQL car les formulaires imbriqus Doc-
trine sont tous sauvegards dun seul coup ;
2 traitement des valeurs soumises en appelant les mthodes
updateCOLUMNColumn() si elles existent ;
3 appel de la mthode fromArray() de lobjet Doctrine pour mettre
jour les valeurs des colonnes ;
4 sauvegarde de lobjet en base de donnes ;
5 validation de la transaction.
Lutilisation de la mthode fromArray() peut potentiellement engendrer
une faille de scurit non ngligeable pour le systme dinformation si
elle est mal utilise. Heureusement, le framework interne de formulaire
de Symfony ralise les traitements adquats pour sen prmunir. La sec-
tion suivante explique en dtail comment cette scurit est implmente.
Scurit native du framework de formulaire
La mthode fromArray() prend un tableau de valeurs en paramtre et
met jour les valeurs des colonnes correspondantes. En quoi est-ce que
cela reprsente un ventuel problme de scurit ? Que se passe-t-il si
quelquun essaie de soumettre sans lautorisation ncessaire une valeur
une colonne ? Par exemple, est-il possible de forcer la valeur de la
colonne token directement dans le formulaire ?
1
1


T
e
s
t
e
r

l
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
215
Le meilleur moyen de sen assurer est dcrire un nouveau test qui simule
la soumission dun formulaire de cration doffre demploi dans lequel se
trouve un champ supplmentaire token.
Code ajouter au fichier test/functional/frontend/jobActionsTest.php
Lorsque le formulaire est soumis, une erreur globale de type
extra_fields est leve. Cest en effet parce que par dfaut, les formu-
laires nacceptent pas la prsence de champs supplmentaires parmi les
valeurs transmises. Cest aussi pour cette raison que tous les champs de
formulaire doivent possder un validateur associ.
Toutefois cette mesure de scurit peut tre outrepasse en fixant
loption allow_extra_fields true.
Le test devrait prsent passer bien que la valeur du champ token a t
filtre. Ainsi, il nest toujours pas possible de franchir cette mesure de
scurit. Pour rendre vritablement possible ce cas de figure, il suffit de
fixer la valeur de loption filter_extra_fields false.
Les tests crits dans cette section ne sont qu but dmonstratif. Ils peu-
vent tre retirs du projet Jobeet dans la mesure o il nest pas ncessaire
de valider des fonctionnalits de Symfony dj testes.
$browser->
get('/job/new')->
click('Preview your job', array('job' => array(
'token' => 'fake_token',
)))->
with('form')->begin()->
hasErrors(7)->
hasGlobalError('extra_fields')->
end()
;
class MyForm extends sfForm
{
public function configure()
{
// ...
$this->validatorSchema->setOption('allow_extra_fields',
true);
}
}
$this->validatorSchema->setOption('filter_extra_fields',
false);
ASTUCE Modifier un formulaire avec la
Firefox Web Developer Toolbar
Des outils comme la clbre Firefox Web Developer
Toolbar permettent de modifier les valeurs des
champs dun formulaire mais aussi den ajouter de
nouveau simplement depuis le navigateur.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
216
Se protger contre les attaques CSRF et XSS
Au cours du premier chapitre, lapplication frontend a t cre partir
de la ligne de commande suivante.
Loption --escaping-strategy active la protection contre les failles de
scurit XSS, ce qui signifie que toutes les variables utilises dans les
templates sont chappes par dfaut. Lors de la cration dune nouvelle
offre demploi, si la description de cette dernire contient des balises
HTML, alors la description de lannonce dans la page de consultation
de dtail sera rendue par Symfony comme tant du texte plein et non
interprte comme du code HTML par le navigateur.
Loption --csrf-secret active la protection contre les failles de scurit
CSRF (prononcer Sea Surf ). Lorsque cette option est active, tous
les formulaires intgrent un champ cach _csrf_token.
La stratgie dchappement et la cl secrte contre les CSRF peuvent
tre modifies nimporte quel moment en ditant manuellement le
fichier de configuration apps/frontend/config/settings.yml. De la
mme manire que le fichier databases.yml, les paramtres de configu-
ration sont configurables par environnement.
Avant de terminer ce chapitre, il est intressant de passer quelques
minutes sur la cration de tches automatiques propres au projet. Dans
le cadre de Jobeet, il sagit dcrire une tche qui se charge de supprimer
de la base de donnes toutes les offres demploi expires depuis un cer-
tain nombre de jours.
Les tches automatiques de maintenance
Symfony est un framework web, mais il n'en est pas moins livr avec un
outil fonctionnant en ligne de commande. Ce dernier a dj t utilis
plusieurs reprises pour crer larchitecture par dfaut du projet et de lappli-
$ php symfony generate:app --escaping-strategy=on --csrf-
secret="Unique$ecret" frontend
all:
.settings:
# Form security secret (CSRF protection)
csrf_secret: Unique$ecret
# Output escaping settings
escaping_strategy: on
escaping_method: ESC_SPECIALCHARS
1
1


T
e
s
t
e
r

l
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
217
cation, ou bien encore pour gnrer quelques fichiers du modle. Ajouter
une nouvelle tche est simplissime dans la mesure o les outils utiliss par la
ligne de commande Symfony sont dj intgrs dans le framework.
Crer la nouvelle tche de maintenance jobeet:cleanup
Lorsque lutilisateur cre une nouvelle offre demploi, il doit imprative-
ment lactiver pour la mettre en ligne. dfaut et avec le temps, la base de
donnes continuera de grossir avec des annonces hors ligne. Il devient
donc utile de crer une tche automatique qui se charge de supprimer
toutes les offres non publies de la base de donnes. Pour viter davoir la
lancer rgulirement la main, cette tche doit tre excute priodique-
ment sous forme dune tche automatique planifie (cron job).
Nouvelle tche automatique de maintenance crer dans le fichier lib/task/
JobeetCleanupTask.class.php
class JobeetCleanupTask extends sfBaseTask
{
protected function configure()
{
$this->addOptions(array(
new sfCommandOption('application', null,
sfCommandOption::PARAMETER_REQUIRED,
'The application', 'frontend'),
new sfCommandOption('env', null,
sfCommandOption::PARAMETER_REQUIRED,
'The environement', 'prod'),
new sfCommandOption('days', null,
sfCommandOption::PARAMETER_REQUIRED, '', 90),
));
$this->namespace = 'jobeet';
$this->name = 'cleanup';
$this->briefDescription = 'Cleanup Jobeet database';
$this->detailedDescription = <<<EOF
The [jobeet:cleanup|INFO] task cleans up the Jobeet database:
[./symfony jobeet:cleanup --env=prod --days=90|INFO]
EOF;
}
protected function execute($arguments = array(),
$options = array())
{
$databaseManager = new sfDatabaseManager($this
->configuration);
$nb = Doctrine::getTable('JobeetJob')
->cleanup($options['days']);
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
218
La configuration de la tche est ralise dans la mthode configure().
Chaque nouvelle tche doit avoir un nom unique (namespace:name), et
peut prendre des arguments et des options en guise de paramtres. Pour
en savoir plus sur les tches internes de Symfony, il suffit de regarder
dans le rpertoire lib/task du framework Symfony pour analyser le code
source de quelques unes dentre elles.
Comme la nouvelle classe vient tout juste dtre cre, il faut bien videm-
ment ractualiser le cache de Symfony pour quelle soit prise en compte.
La tche jobeet:cleanup dfinit deux options : --env et --days avec
pour chacune une valeur par dfaut. Tout le manuel dutilisation de la
tche est consultable en excutant la commande suivante.
Enfin, pour lexcuter, il suffit de lappeler en ligne de commande tout sim-
plement grce lexcutable Symfony comme le montre le code suivant.
Implmenter la mthode cleanup() de la classe
JobeetJobTable
La tche nest pas totalement fonctionnelle puisquil lui manque limpl-
mentation de la mthode cleanup() dans la classe JobeetJobTable. Cest
effectivement celle-ci qui soccupe vritablement de construire et
dappeler la bonne requte SQL qui nettoie la base de donnes, comme
le montre le code ci-dessous.
Mthode cleanup() ajouter au fichier lib/model/doctrine/JobeetJobTable.class.php
$this->logSection('doctrine',
sprintf('Removed %d stale jobs', $nb));
}
}
$ php symfony cc
$ php symfony help jobeet:cleanup
$ php symfony jobeet:cleanup --days=10 --env=dev
public function cleanup($days)
{
$q = $this->createQuery('a')
->delete()
->andWhere('a.is_activated = ?', 0)
->andWhere('a.created_at < ?',
date('Y-m-d', time() - 86400 * $days));
return $q->execute();
}
1
1


T
e
s
t
e
r

l
e
s

f
o
r
m
u
l
a
i
r
e
s
Groupe Eyrolles, 2008
219
Les tches de Symfony se comportent parfaitement avec leur environne-
ment puisquelles retournent une valeur en cas de succs. De ce fait, la
valeur de retour par dfaut peut tre force en retournant explicitement
un entier la fin de la tche.
En rsum
Tester le code est au cur de la philosophie Symfony et de ses outils.
Dans ce chapitre, nous avons vu une fois de plus comment nous servir
efficacement des outils de Symfony pour rendre le processus de dvelop-
pement plus facile, plus rapide et surtout plus sr.
Le framework Symfony fournit bien plus que ces quelques widgets et
validateurs. Il offre en ralit une manire souple de tester les formu-
laires pour sassurer quils sont scuriss par dfaut.
Ce nouveau tour dhorizon des fonctionnalits de Symfony sachve ici
pour ce chapitre. Dans le chapitre suivant, il sera question de la cration
de linterface dadministration dans lapplication backend. Crer un back
office dadministration est un passage oblig pour la plupart des projets
web, et bien sr Jobeet ne droge pas cette rgle. La question qui se
pose alors est : Comment crer une telle interface dadministration
complte et fonctionnelle en seulement quelques minutes ? . Cest en
fait extrmement facile avec laide du clbre gnrateur dadministra-
tion de Symfony
Groupe Eyrolles, 2008
chapitre 12
Groupe Eyrolles, 2008
Le gnrateur dinterface
dadministration
Concevoir une interface dadministration pour une application
web grand public est une tche courante et fastidieuse
raliser. En effet, la plupart des crans dun backoffice
suivent le mme schma : des listes ordonnes et pagines,
des formulaires de cration et ddition du contenu,
des outils de recherche
Le framework Symfony dispose dun outil capable
dautomatiser entirement la gnration de telles interfaces
mais aussi den simplifier la configuration sans avoir
(ou presque) crire de code PHP.
MOTS-CLS :
BGnrateur dadministration
BFichier de configuration
generator.yml
BActions CRUD
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
222
Lapplication dveloppe tout au long de cet ouvrage est destine avant
tout tre alimente par de simples utilisateurs. Tous les usagers sont
libres de crer et de publier de nouvelles offres sur le site Internet. Nan-
moins, cette grande libert qui leur est offerte doit aussi tre encadre
pour viter toute drive ou publication de contenus non conformes aux
rgles du site. Cest l lun des multiples avantages de linterface dadmi-
nistration dune application Symfony.
Avec les nouvelles fonctionnalits implmentes au chapitre prcdent,
lapplication frontend grand public est dsormais entirement utilisable
par les utilisateurs la recherche demploi ou bien par ceux qui propo-
sent de nouvelles offres. Il est donc temps de sintresser au dveloppe-
ment dune interface dadministration, dveloppement qui sera
rapidement ralis grce au gnrateur de backoffice ( admin
generator ) de Symfony.
Cration de lapplication backend
Gnrer le squelette de lapplication
La toute premire tape de ce nouveau chapitre consacr la gnration
du backoffice consiste crer une nouvelle application backend .
Cette opration a dj t prsente au premier chapitre de cet ouvrage
avec lutilisation de la tche generate:app.
Bien que lapplication se destine uniquement aux administrateurs de
Jobeet, toutes les mesures de scurit natives ont t actives.
Lapplication backend est maintenant disponible ladresse http://
jobeet.localhost/backend.php/ pour lenvironnement de production, et
http://jobeet.localhost/backend_dev.php/ pour celui de dveloppement.
Recharger les jeux de donnes initiales
Le chargement des donnes initiales en base de donnes partir de la
tche doctrine:data-load ne fonctionne plus du tout prsent. Cest en
fait parce que la mthode JobeetJob::save() a besoin daccder au
fichier de configuration app.yml de lapplication frontend. Comme le
projet est maintenant compos de deux applications, Symfony utilise le
premier quil trouve ; en loccurrence celui de lapplication backend.
$ php symfony generate:app --escaping-strategy=on
X --csrf-secret=UniqueSecret1 backend
ASTUCE Utiliser des caractres spciaux
pour le jeton CSRF ?
Si la valeur de loption --csrf-secret con-
tient des caractres spciaux comme un signe $
(dollar) par exemple, ces derniers doivent tre
explicitement chapps laide dun antislash
dans la console de lignes de commande.
$ php symfony generate:app
--csrf-secret=Unique\$ecret backend
1
2


L
e

g

r
a
t
e
u
r

d

i
n
t
e
r
f
a
c
e

d

a
d
m
i
n
i
s
t
r
a
t
i
o
n
Groupe Eyrolles, 2008
223
Lidal est donc de partager le contenu du fichier de configuration de
lapplication frontend avec celui de lapplication backoffice. Depuis
larrive de Symfony 1.2, cette opration est dsormais rendue possible.
Le huitime chapitre a montr que les paramtres du projet pouvaient
tre configurs diffrents niveaux. En dplaant le fichier apps/
frontend/config/app.yml dans le rpertoire global config/, les param-
tres de configuration seront alors partags par lensemble des applica-
tions du projet. Ce changement rsout le problme, et se rvle
important pour la suite du chapitre dans la mesure o les classes de
modle et les variables de configuration seront sollicites directement
dans le gnrateur de backoffice.
Gnrer les modules dadministration
La nouvelle tape rside dans la cration des modules de gestion des
catgories et des offres. Bien sr, il est hors de question de dmarrer avec
deux modules vides, puis de dvelopper les diffrentes actions CRUD
la main pour ces deux derniers. Le gnrateur de backoffice de Symfony
automatise toutes ces tches afin de fournir des modules entirement
fonctionnels et personnalisables.
Gnrer les modules category et job
Pour lapplication frontend, cest la tche doctrine:generate-module qui
a t utilise pour gnrer un module basique CRUD reposant sur une
classe de modle. En ce qui concerne lapplication backoffice, cest la
commande doctrine:generate-admin qui se charge de btir une inter-
face complte dadministration pour une classe de modle donne.
Ces deux commandes crent les modules job et category pour les classes
de modle respectives JobeetJob et JobeetCategory. Loption facultative
-module surcharge le nom du module gnr par dfaut par la tche, qui
aurait t jobeet_job pour la classe JobeetJob. De plus, la tche cre
aussi une collection de routes Doctrine ddie pour chaque module.
Sans surprise, la classe de la route utilise par le gnrateur dadministra-
tion est sfDoctrineRouteCollection, puisquune interface dadministra-
tion a pour but de grer tout le cycle de vie des objets du modle. La
dfinition de la route dclare galement quelques options vues prc-
demment dans cet ouvrage :
prefix_path : dtermine le prfixe du chemin pour la route gnre.
Par exemple, la page ddition sera accessible lURL /job/1/edit ;
ASTUCE Charger des donnes initiales
partir dune configuration spcifique
La tche doctrine:data-load accepte ga-
lement un paramtre facultatif -
appplication qui permet de charger les don-
nes initiales de test en utilisant la configuration
propre lapplication mentionne.
$ php symfony doctrine:data-load --
application=frontend
$ php symfony doctrine:generate-admin backend JobeetJob --module=job
$ php symfony doctrine:generate-admin backend JobeetCategory --module=category
BONNE PRATIQUE Lire le manuel des tches
automatiques de Symfony
Le framework Symfony, et plus particulirement
les tches automatiques, regorgent d'options et
de paramtres, permettant de coller au plus prs
des besoins du dveloppeur. Une bonne pra-
tique adopter est donc de lire la documenta-
tion de la tche concerne, et ce, avant toute
utilisation.
$ php symfony help
doctrine:generate-admin
Le manuel prsente tous les arguments et
options possibles pour chaque tche ainsi que
quelques exemples pratiques simples.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
224
column : dfinit la colonne de la table utiliser dans lURL pour les
liens qui rfrencent un objet ;
with_wildcard_routes : puisque ladministration aura des oprations
supplmentaires en plus des actions CRUD classiques, cette option
permet de dclarer plus dactions dobjet et de collection sans avoir
diter la route.
Personnaliser linterface utilisateur
et lergonomie des modules du backoffice
Dcouvrir les fonctions des modules dadministration
Lexcution des deux lignes de commande prcdentes a suffi gnrer
deux modules dadministration complets et parfaitement oprationnels.
Ils sont respectivement disponibles aux URLs suivantes.
http://jobeet.localhost/backend_dev.php/job
http://jobeet.localhost/backend_dev.php/category
Les modules dadministration disposent de tout un tas de fonctionna-
lits supplmentaires en comparaison des modules auto gnrs tradi-
tionnels tudis lors des chapitres prcdents. Sans avoir crit la moindre
ligne de code PHP, chaque module intgre ces quelques fonctionnalits
ultimes :
la liste des objets est pagine ;
la liste est ordonnable grce aux en-ttes du tableau ;
la liste peut tre filtre par le biais du formulaire de recherche sur la
droite de lcran ;
les objets peuvent tre crs, dits ou supprims ;
les objets slectionns peuvent tre supprims par lot ;
la validation des formulaires est active ;
les messages flash donnent immdiatement des feedbacks
lutilisateur ;
et bien plus encore !
Le gnrateur de backoffice fournit toutes les fonctionnalits ncessaires
pour crer soi-mme une interface de pilotage et ce, trs simplement.
1
2


L
e

g

r
a
t
e
u
r

d

i
n
t
e
r
f
a
c
e

d

a
d
m
i
n
i
s
t
r
a
t
i
o
n
Groupe Eyrolles, 2008
225
Amliorer le layout du backoffice
Dans le but damliorer lexprience utilisateur, il est ncessaire de per-
sonnaliser davantage linterface graphique du backend. Les deux
modules sont pour linstant accessibles individuellement par leur URL,
ce qui nest pas spcialement pratique Le code suivant contient le con-
tenu du fichier layout.php de lapplication backend. Ce dernier impl-
mente entre autres un menu de liens afin de faciliter la navigation entre
les diffrents modules.
Contenu du layout dans le fichier apps/backend/templates/layout.php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
lang="en">
<head>
<title>Jobeet Admin Interface</title>
<link rel="shortcut icon" href="/favicon.ico" />
<?php use_stylesheet('admin.css') ?>
<?php include_javascripts() ?>
<?php include_stylesheets() ?>
</head>
<body>
<div id="container">
<div id="header">
<h1>
<a href="<?php echo url_for('@homepage') ?>">
<img src="/images/jobeet.gif" alt="Jobeet Job Board" />
</a>
</h1>
</div>
<div id="menu">
<ul>
<li>
<?php echo link_to('Jobs', '@jobeet_job') ?>
</li>
<li>
<?php echo link_to('Categories', '@jobeet_category') ?>
</li>
</ul>
</div>
<div id="content">
<?php echo $sf_content ?>
</div>
<div id="footer">
<img src="/images/jobeet-mini.png" />
powered by <a href="http://www.symfony-project.org/">
<img src="/images/symfony.gif" alt="symfony framework" />
</a>
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
226
Ce layout utilise une feuille de style admin.css qui doit tre obligatoire-
ment prsente dans le rpertoire web/css install au chapitre 4 avec les
autres fichiers CSS.
Dans la foule, lURL de la page daccueil du backend peut tre rem-
place par la liste des offres demploi du module job. Pour ce faire, il
suffit de modifier la route homepage du fichier de configuration
routing.yml de lapplication.
Extrait du fichier apps/backend/config/routing.yml
Ltape suivante consiste pntrer davantage dans les entrailles du code
gnr pour chaque module dadministration. Il y a en effet beaucoup
apprendre sur le fonctionnement du framework Symfony, commencer
par le cache.
</div>
</div>
</body>
</html>
Figure 121
cran daccueil du
module de gestion des
catgories
homepage:
url: /
param: { module: job, action: index }
1
2


L
e

g

r
a
t
e
u
r

d

i
n
t
e
r
f
a
c
e

d

a
d
m
i
n
i
s
t
r
a
t
i
o
n
Groupe Eyrolles, 2008
227
Comprendre le cache de Symfony
Il est temps de dcouvrir comment fonctionnent les modules gnrs, ou
du moins dtudier leur code source pour comprendre ce qua gnr la
tche automatique pour chacun deux. Comme job et category sont
deux modules, ils se trouvent naturellement dans le rpertoire apps/
backend/modules. En les explorant tous les deux, il est important de
remarquer que les rpertoires templates/ sont tous les deux vides tandis
que les fichiers dactions le sont quasiment aussi. Le code suivant issu du
fichier actions.class.php du module job en tmoigne.
Contenu du fichier apps/backend/modules/job/actions/actions.class.php
Comment tout cela peut-il fonctionner avec si peu de code ? En y regar-
dant de plus prs, la classe jobActions ntend pas sfActions comme
cest le cas gnralement, mais drive la classe autoJobActions. Cette
classe existe bel et bien dans le projet mais se trouve dans un endroit un
peu inattendu. Elle appartient en ralit au rpertoire cache/backend/
dev/modules/autoJob/ qui contient le vritable module dadministra-
tion.
Extrait du fichier cache/backend/dev/modules/autoJob/actions/actions.class.php
Le choix de gnrer tout le module dans le cache de Symfony na pas t
dcid au hasard. En effet, les sections suivantes du chapitre prsentent
toute la force du gnrateur dadministration, savoir la configuration
du module grce un simple fichier YAML. Nimporte quel change-
ment dans ce dernier entranera une rgnration de lintgralit du
module et donc des templates et des classes. Cest pour cette raison que
require_once dirname(__FILE__).'/../lib/jobGeneratorConfiguration.class.php';
require_once dirname(__FILE__).'/../lib/jobGeneratorHelper.class.php';
class jobActions extends autoJobActions
{
}
class autoJobActions extends sfActions
{
public function preExecute()
{
$this->configuration = new jobGeneratorConfiguration();
if (!$this->getUser()->hasCredential(
$this->configuration->getCredentials($this->getActionName())
))
{
// ...
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
228
les modules ne se trouvent pas dans le rpertoire de lapplication mais
dans celui du cache.
Introduction au fichier de configuration generator.yml
La manire dont fonctionne le gnrateur de backoffice doit forcment
rappeler quelques comportements connus. Cest en fait trs similaire ce
qui a dj t prsent au sujet des classes de formulaires et de modles.
laide du schma de description de la base de donnes, Symfony
gnre le modle et les classes de formulaire. En ce qui concerne le gn-
rateur de backoffice, lintgralit du module est configurable en ditant
le fichier config/generator.yml du module. Le code suivant prsente le
fichier par dfaut gnr avec le module job.
Fichier de configuration apps/backend/modules/job/config/generator.yml
Chaque fois que le fichier config/generator.yml est modifi, Symfony
rgnre le cache. Le renouvellement de ce dernier se produit bien sr
automatiquement en environnement de dveloppement. En environne-
ment de production, il doit tre vid manuellement. Les prochaines
pages de ce chapitre montrent quel point il est facile, rapide et intuitif
de configurer et de personnaliser des modules construits grce au gn-
rateur de backoffice.
generator:
class: sfDoctrineGenerator
param:
model_class: JobeetJob
theme: admin
non_verbose_templates: true
with_show: false
singular: ~
plural: ~
route_prefix: jobeet_job
with_doctrine_route: 1
config:
actions: ~
fields: ~
list: ~
filter: ~
form: ~
edit: ~
new: ~
1
2


L
e

g

r
a
t
e
u
r

d

i
n
t
e
r
f
a
c
e

d

a
d
m
i
n
i
s
t
r
a
t
i
o
n
Groupe Eyrolles, 2008
229
Configurer les modules autognrs
par Symfony
Cette nouvelle section aborde la partie la plus importante du chapitre. Il
sagit dapprendre comment configurer un module auto gnr partir
du fichier de configuration generator.yml. En effet, tous les lments
qui composent les pages dune interface dadministration sont ditables
et surchargeables grce ce fichier.
Organisation du fichier de configuration generator.yml
Un module dadministration peut tre configur en ditant toutes les
sections se trouvant sous la cl config du fichier de configuration
config/generator.yml. La configuration est organise en sept sections
distinctes :
actions dfinit la configuration par dfaut des actions qui se trouvent
dans la liste dobjets et dans les formulaires ;
fields dfinit la configuration des diffrents champs dun objet ;
list dfinit la configuration de la liste des objets ;
filter dfinit la configuration des filtres de recherche de la barre
latrale de droite ;
form dfinit la configuration des formulaires dajout et de modifica-
tion des objets ;
edit correspond la configuration spcifique pour la page ddition
dun objet ;
new correspond la configuration spcifique pour la page de cration
dun objet.
Toutes ces sections de configuration des modules seront prsentes juste
aprs plus en dtail avec des exemples concrets appliqus au backoffice
de lapplication. Ce processus de personnalisation dmarre par la confi-
guration des titres de chaque page du module.
Configurer les titres des pages des modules auto gnrs
Changer le titre des pages du module category
Pour linstant, les titres des pages affichs au-dessus de la liste de rsul-
tats ou au-dessus des formulaires de cration et ddition sont ceux qui
ont t gnrs par dfaut par Symfony. Ils sont bien videmment tous
modifiables trs simplement grce au fichier de configuration
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
230
generator.yml. Il suffit en effet dajouter ce dernier une sous-section
title aux cls list, new et edit comme le montre le code ci-dessous.
Contenu du fichier de configuration du module category apps/backend/modules/
category/config/generator.yml
Le titre de la section edit supporte des valeurs dynamiques. Les chanes
de caractres dlimites par %% sont remplaces par la valeur correspon-
dante au nom de la colonne indique de la table. Ainsi, dans cet
exemple, le motif %%name%% sera remplac par le nom de la catgorie en
cours de modification.
Configurer les titres des pages du module job
Dans la foule, il est possible den faire autant pour le module job en
ditant son fichier de configuration generator.yml.
Contenu du fichier de configuration du module job apps/backend/modules/job/
config/generator.yml
config:
actions: ~
fields: ~
list:
title: Category Management
filter: ~
form: ~
edit:
title: Editing Category "%%name%%"
new:
title: New Category
Figure 122
Titre dynamique de lcran
ddition dune catgorie
config:
actions: ~
fields: ~
list:
title: Job Management
filter: ~
form: ~
1
2


L
e

g

r
a
t
e
u
r

d

i
n
t
e
r
f
a
c
e

d

a
d
m
i
n
i
s
t
r
a
t
i
o
n
Groupe Eyrolles, 2008
231
Comme le montre le nouveau titre de la section edit, les paramtres
dynamiques sont cumulables. Ici, le titre de la page ddition dune offre
est compos du nom de la socit (%%company%%) et du type de poste pro-
pos (%%position%%).
Modifier le nom des champs dune offre demploi
Redfinir globalement les proprits des champs du module
Les diffrentes vues (list, new et edit) de chaque module sont compo-
ses de champs . Un champ reprsente aussi bien le nom dune
colonne dans une classe de modle, quune colonne virtuelle. Ce concept
est expliqu plus loin dans ces pages. La section fields permet de
dfinir globalement lensemble des proprits des champs du module.
Configuration des champs du module job dans le fichier apps/backend/modules/
job/config/generator.yml
Lorsquils sont dclars directement dans la section fields, les champs
sont redfinis globalement pour toutes les autres vues. Ainsi, le label du
champ is_public sera le mme quelle que soit la vue affiche : list,
edit ou new.
Surcharger localement les proprits des champs du module
Toutefois, il est frquent de vouloir des intituls diffrents pour chaque
vue, notamment entre la page de liste et les formulaires. Toute la confi-
guration du gnrateur dadministration repose sur un principe dhri-
tage en cascade, ce qui permet de pouvoir redfinir certaines valeurs
edit:
title: Editing Job "%%company%% is looking for a %%position%%"
new:
title: Job Creation
config:
fields:
is_activated: { label: Activated?, help: Whether the user
has activated the job, or not }
is_public: { label: Public? }
Figure 123
Configuration des
champs is_activated
et is_public
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
232
plusieurs niveaux. Par exemple, si lon souhaite changer un label uni-
quement pour la liste dobjet, il suffit simplement de crer une nouvelle
section fields juste en dessous de la cl list.
Redfinition du label dun champ pour la liste dans le fichier apps/backend/
modules/job/config/generator.yml
La partie suivante explique le principe de configuration en cascade du
gnrateur dadministration.
Comprendre le principe de configuration en cascade
Nimporte quelle configuration dfinie sous la section principale fields
peut tre surcharge pour les besoins dune vue spcifique. Les rgles de
reconfiguration sont les suivantes :
les sections new et edit hritent de form, qui hrite lui-mme de
fields ;
la section list hrite de fields ;
la section filter hrite de fields.
Pour les sections form (form, edit et new), les options label et help sur-
chargent celles dfinies dans les classes de formulaire.
Configurer la liste des objets
Les parties qui suivent expliquent lensemble des possibilits de configu-
ration de la vue liste des modules auto gnrs. Parmi toutes ces direc-
tives de configuration figurent entre autres la dclaration des
informations de lobjet afficher, la dfinition du nombre dobjets par
page, lordre par dfaut de la liste, les actions par lot, etc.
Dfinir la liste des colonnes afficher
Colonnes afficher dans la liste des catgories
Par dfaut, les colonnes affiches de la vue liste sont toutes celles du
modle, dans lordre du schma de dfinition de la base de donnes.
Loption display surcharge la configuration par dfaut en dfinissant,
dans lordre dapparition, la liste des colonnes afficher dans la liste.
config:
list:
fields:
is_public: { label: "Public? (label for the list)" }
1
2


L
e

g

r
a
t
e
u
r

d

i
n
t
e
r
f
a
c
e

d

a
d
m
i
n
i
s
t
r
a
t
i
o
n
Groupe Eyrolles, 2008
233
Redfinition des colonnes de la vue liste du module category dans le fichier de
configuration apps/backend/modules/category/config/generator.yml
Le signe = qui prcde le nom de la colonne est une convention dans le
framework Symfony qui permet de convertir le texte en un lien cliquable
qui mne lutilisateur au formulaire ddition de lobjet.
Liste des colonnes afficher dans la liste des offres
La configuration des colonnes du tableau de la vue liste du module job
est exactement la mme comme le dmontre le code ci-aprs.
Dfinition des colonnes de la vue liste du module job dans le fichier apps/backend/
modules/job/config/generator.yml
Dsormais, la nouvelle liste se limite au nom de la socit, le type de
poste, la localisation de loffre, lurl, le statut et lemail de contact.
Configurer le layout du tableau de la vue liste
La vue liste peut tre affiche avec diffrents gabarits. Par dfaut, cest le
layout tabulaire (tabular), qui signifie que chaque valeur dune colonne
config:
list:
title: Category Management
display: [=name, slug]
Figure 124
Rendu de la page
daccueil du module
dadministration des
catgories
config:
list:
title: Job Management
display: [company, position, location, url, is_activated, email]
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
234
est prsente dans sa propre colonne du tableau. Il serait nanmoins plus
avantageux davoir recours au layout linaire (stacked), qui est le second
gabarit disponible par dfaut.
Configuration du layout stacked pour le module job dans le fichier apps/backend/
modules/job/config/generator.yml
Avec le layout linaire, chaque objet est reprsent sous la forme dune
chane de caractres unique, dfinie grce loption params. Loption
display reste ncessaire dans la mesure o elle dtermine les colonnes
grce auxquelles lutilisateur peut ordonner la liste des rsultats.
Dclarer des colonnes virtuelles
Avec cette configuration, le motif %%category_id%% sera remplac par la
cl primaire de la catgorie laquelle loffre est associe. Cependant, il
est beaucoup plus pertinent et significatif dafficher le nom de la cat-
gorie. Quelle que soit la notation %% utilise, la variable na pas besoin de
correspondre une colonne relle du schma de dfinition de la base de
donnes. Le gnrateur de backoffice doit en effet tre capable de
trouver un accesseur associ dans la classe de modle.
Afin dafficher le nom de la catgorie, il est possible de dclarer une
mthode getCategoryName() dans la classe de modle JobeetJob, puis de
remplacer %%category_id%% par %%category_name%%. Or, la classe
JobeetJob dispose dj dune mthode getJobeetCategory() qui
retourne lobjet catgorie associ. De ce fait, en utilisant le motif
%%jobeet_category%%, le nom de la catgorie saffiche dans la liste
doffres demploi car la classe JobeetCategory implmente la mthode
magique __toString() qui convertit lobjet en une chane de caractres.
Code remplacer dans le fichier apps/backend/modules/job/config/generator.yml
config:
list:
title: Job Management
layout: stacked
display: [company, position, location, url,
is_activated, email]
params: |
%%is_activated%% <small>%%category_id%%</small>
- %%company%%
(<em>%%email%%</em>) is looking for a %%=position%%
(%%location%%)
%%is_activated%% <small>%%jobeet_category%%</small> -
%%company%%
(<em>%%email%%</em>) is looking for a %%=position%%
(%%location%%)
1
2


L
e

g

r
a
t
e
u
r

d

i
n
t
e
r
f
a
c
e

d

a
d
m
i
n
i
s
t
r
a
t
i
o
n
Groupe Eyrolles, 2008
235
Dfinir le tri par dfaut de la liste dobjets
Un administrateur prfrera certainement voir les dernires offres
demploi postes sur la premire page. Lordre des enregistrements dans
la liste est configurable grce loption sort de la section list comme le
montre le code ci-dessous.
Dfinition de lordre des objets dans le tableau de la vue liste dans le fichier apps/
backend/modules/job/config/generator.yml
La premire valeur du tableau sort correspond au nom de la colonne sur
laquelle le tri effectu, tandis que la seconde dfinit le sens. La valeur
desc dtermine un ordre descendant, cest--dire du plus grand au plus
petit (ou de Z A pour les chanes, ou bien du plus rcent au plus vieux
avec les dates). Pour obtenir un ordre ascendant (par dfaut), il suffit de
dindiquer la valeur asc.
Rduire le nombre de rsultats par page
Par dfaut, la liste est pagine et chaque page contient
20 enregistrements. Cette valeur est bien sr ditable grce loption
max_per_page de la section list.
Figure 125
Ajout de la catgorie
pour chaque objet
config:
list:
sort: [expires_at, desc]
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
236
Dfinition du nombre denregistrements par page dans le fichier apps/backend/
modules/job/config/generator.yml
Configurer les actions de lot dobjets
Le gnrateur de backoffice de Symfony intgre nativement la possibi-
lit dexcuter des actions sur un lot dobjets slectionns dans le tableau
de la vue liste. Le gestionnaire nen a pas vritablement besoin, cest
pourquoi la premire partie explique comment les dsactiver. En
revanche, la seconde partie prsente comment configurer de nouvelles
actions de lot pour le module job.
Dsactiver les actions par lot dans le module category
La vue liste permet dexcuter une action sur plusieurs objets en mme
temps. Ces actions par lot ne sont pas ncessaires pour le module
category, cest pourquoi le code montre la manire de les retirer laide
dune simple configuration du fichier generator.yml.
Suppression des actions de lot dans le fichier apps/backend/modules/category/
config/generator.yml
Loption batch_actions dfinit la liste des actions applicables sur un lot
dobjets Doctrine. Indiquer explicitement un tableau vide en guise de
valeur permet de supprimer cette fonctionnalit.
config:
list:
max_per_page: 10
Figure 126
Pagination de la liste
dobjets
config:
list:
batch_actions: {}
1
2


L
e

g

r
a
t
e
u
r

d

i
n
t
e
r
f
a
c
e

d

a
d
m
i
n
i
s
t
r
a
t
i
o
n
Groupe Eyrolles, 2008
237
Ajouter de nouvelles actions par lot dans le module job
Par dfaut, chaque module dispose dune action de suppression par lot
gnre automatiquement par le framework. En ce qui concerne le
module job, il serait utile de pouvoir tendre la validit de quelques objets
slectionns pour 30 jours supplmentaires, grce une nouvelle action.
Ajout de la nouvelle action extends dans le fichier apps/backend/modules/job/
config/generator.yml
Toutes les actions commenant par un tiret soulign (underscore) sont
des actions natives fournies par le framework. En rafrachissant le navi-
gateur, la liste droulante accueille prsent laction extend mais Sym-
fony lance une exception indiquant quune nouvelle mthode
executeBatchExtend() doit tre cre.
Mthode executeBatchExtend() ajouter au fichier apps/backend/modules/job/
actions/actions.class.php
Figure 127
Liste des catgories aprs
suppression des actions
par lot dobjets
config:
list:
batch_actions:
_delete: ~
extend: ~
class jobActions extends autoJobActions
{
public function executeBatchExtend(sfWebRequest $request)
{
$ids = $request->getParameter('ids');
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
238
Bien que la comprhension de ce code ne pose pas de difficult particu-
lire, quelques explications supplmentaires simposent. La liste des cls
primaires des objets slectionns est stocke dans le paramtre ids de
lobjet de requte. Ce tableau didentifiants uniques a t transmis en
POST lors de la soumission du formulaire. La variable $ids est ensuite
transmise la requte Doctrine qui se charge de rcuprer et dhydrater
tous les objets correspondant cette liste didentifiants.
Lappel la mthode execute() sur lobjet Doctrine_Query retourne un
objet Doctrine_Collection contenant toutes les offres JobeetJob corres-
pondantes rcupres dans la base de donnes. Pour chaque offre
demploi, lappel la mthode extend() permet de prolonger la dure de
vie de lobjet pour une dure de 30 jours supplmentaires.
Enfin, un nouveau message de feedback est crit dans la session de luti-
lisateur afin de lui informer que les offres slectionnes ont bien t pro-
longes aprs sa redirection.
Le paramtre true de la mthode extend() permet de contourner la
vrification de la date dexpiration. Le code suivant implmente cette
nouvelle fonctionnalit.
dition de la mthode extend() de la classe JobeetJob dans le fichier lib/model/
doctrine/JobeetJob.class.php
$q = Doctrine_Query::create()
->from('JobeetJob j')
->whereIn('j.id', $ids);
foreach ($q->execute() as $job)
{
$job->extend(true);
}
$this->getUser()->setFlash('notice', 'The selected jobs
have been extended successfully.');
$this->redirect('@jobeet_job');
}
}
class JobeetJob extends BaseJobeetJob
{
public function extend($force = false)
{
if (!$force && !$this->expiresSoon())
{
return false;
}
$this->setExpiresAt(date('Y-m-d', time() + 86400 *
sfConfig::get('app_active_days')));
1
2


L
e

g

r
a
t
e
u
r

d

i
n
t
e
r
f
a
c
e

d

a
d
m
i
n
i
s
t
r
a
t
i
o
n
Groupe Eyrolles, 2008
239
Configurer les actions unitaires pour chaque objet
Le tableau de la vue liste contient une colonne additionnelle destine aux
actions applicables unitairement sur chaque objet. Par dfaut, Symfony
introduit les actions ddition et de suppression dun enregistrement,
mais il est videmment possible den ajouter de nouvelles en ditant le
fichier de configuration du module.
Supprimer les actions dobjets des catgories
En considrant que le lien sur le titre de la catgorie suffise pour accder
au formulaire ddition et que la suppression dune catgorie soit inter-
dite, les actions dobjet nont plus vritablement de raison de persister.
La configuration suivante retire compltement la dernire colonne du
tableau de catgories.
Suppression des actions dobjets dans le fichier apps/backend/modules/category/
config/generator.yml
De la mme manire que pour les actions par lot, spcifier un tableau
vide comme valeur loption object_actions permet de retirer les
actions des objets du tableau.
$this->save();
return true;
}
// ...
}
Figure 128
Ajout de laction extend
la liste droulante des
actions de lot
config:
list:
object_actions: {}
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
240
Ajouter dautres actions pour chaque offre demploi
Pour le module job, il convient de conserver les actions ddition et de
suppression pour chaque item du tableau. Le code ci-dessous montre
comment ajouter une nouvelle action extend pour chaque objet. Cette
dernire permet dtendre la dure de vie dune offre de manire unitaire
au simple clic sur le lien cr par Symfony.
Ajout de la nouvelle action dobjet extend au fichier apps/backend/modules/job/
config/generator.yml
Pour fonctionner compltement, cette action doit tre accompagne de
la dclaration dune mthode executeListExtend() dans la classe
dactions. Celle-ci doit implmenter la logique ncessaire la prolonga-
tion de la dure de vie dune offre comme le prsente le code suivant.
Ajout de la mthode executeListExtend() au fichier apps/backend/modules/job/
actions/actions.class.php
Configurer les actions globales de la vue liste
Pour linstant, il est possible de crer des liens vers des actions pour la liste
entire ou bien pour un seul enregistrement du tableau. Loption actions
dfinit la liste des actions qui ne sont pas directement en relation avec les
objets, cest le cas par exemple de la cration dun nouvel objet.
config:
list:
object_actions:
extend: ~
_edit: ~
_delete: ~
class jobActions extends autoJobActions
{
public function executeListExtend(sfWebRequest $request)
{
$job = $this->getRoute()->getObject();
$job->extend(true);
$this->getUser()->setFlash('notice', 'The selected jobs
have been extended successfully.');
$this->redirect('@jobeet_job');
}
// ...
}
1
2


L
e

g

r
a
t
e
u
r

d

i
n
t
e
r
f
a
c
e

d

a
d
m
i
n
i
s
t
r
a
t
i
o
n
Groupe Eyrolles, 2008
241
Dans Jobeet, ce sont les utilisateurs qui postent de nouvelles offres. Les
administrateurs nen ont pas la ncessit, cest pourquoi le lien de cra-
tion dun nouvel objet peut tre supprim. En revanche, les administra-
teurs doivent avoir la possibilit de purger la base de donnes des offres
expires de plus de 60 jours.
Ajout dune nouvelle action globale deleteNeverActivated dans le fichier apps/
backend/modules/job/config/generator.yml
Jusqu maintenant, toutes les actions globales de la vue liste taient
dclares avec le symbole tilde (~), ce qui signifie que Symfony confi-
gure laction automatiquement. Chaque action peut tre personnalise
en dfinissant un tableau de paramtres. Loption label surcharge linti-
tul automatiquement gnr par le framework. Par dfaut, laction ex-
cute lorsque lon clique sur le lien est le nom de laction prfix par
list. Le code ci-dessous montre limplmentation de laction globale
deleteNeverActivated pour le module job.
Implmentation de la mthode listDeleteNeverActivated dans le fichier apps/
backend/modules/job/actions/actions.class.php
Figure 129
Ajout de laction extend
pour chaque objet
config:
list:
actions:
deleteNeverActivated: { label: Delete never activated jobs }
class jobActions extends autoJobActions
{
public function executeListDeleteNeverActivated(sfWebRequest
$request)
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
242
Cette action nest gure complexe comprendre. La premire ligne du
corps de la mthode fait appel la mthode cleanup() qui se charge de
supprimer de la base de donnes toutes les offres expires depuis plus de
60 jours. Cette mthode a t implmente lors du prcdent chapitre.
Elle prend en paramtre le nombre de jours succdant la date dexpira-
tion de loffre et retourne le nombre denregistrements effacs de la base
de donnes. Enfin, en fonction du nombre dobjets effacs, un message
flash est fix dans la session de lutilisateur avant de le rediriger vers la
page daccueil du module dadministration.
La rutilisation de la mthode cleanup() est ici un exemple particulire-
ment dmonstratif des avantages du motif de conception MVC.
Le nom de la mthode crire risquera d'tre trop peu explicite et fasti-
dieux crire. Dans ce cas, il est possible de remplacer le nom gnr par
dfaut.
{
$nb = Doctrine::getTable('JobeetJob')->cleanup(60);
if ($nb)
{
$this->getUser()->setFlash('notice', sprintf('%d never
activated jobs have been deleted successfully.', $nb));
}
else
{
$this->getUser()->setFlash('notice', 'No job to delete.');
}
$this->redirect('@jobeet_job');
}
// ...
}
Figure 1210
Ajout de fonctionnalits
globales la vue liste
1
2


L
e

g

r
a
t
e
u
r

d

i
n
t
e
r
f
a
c
e

d

a
d
m
i
n
i
s
t
r
a
t
i
o
n
Groupe Eyrolles, 2008
243
Le nom de laction utiliser peut aussi tre redfini afin de le simplifier.
Il suffit alors dajouter un paramtre action dans la dclaration de
laction globale comme le prsente le code ci-dessous.
Optimiser les requtes SQL de rcupration des
enregistrements
En affichant longlet SQL de la barre de dboguage de Symfony, on cons-
tate ici que la liste a besoin dexcuter 14 requtes SQL pour afficher la
liste des offres demploi. Or, parmi ces 14 requtes, il y en a 10 qui rem-
plissent le mme besoin : rcuprer le nom de la catgorie associe
lenregistrement. Cet exemple sous-entend clairement le manque dune
jointure entre les relations jobeet_job et jobeet_category qui permet-
trait de slectionner la fois les offres et leur catgorie respective laide
dune seule et mme requte SQL.
Par dfaut, le gnrateur de backoffice utilise la mthode Doctrine la plus
simple pour rcuprer une liste denregistrements. De ce fait, lorsque des
objets sont en relation, lORM est oblig deffectuer les requtes adquates
pour les retrouver et hydrater lobjet associ la demande.
Pour viter ce comportement et cette perte de performance, loption
table_method permet de surcharger la mthode Doctrine utilise par
dfaut par le framework pour gnrer la liste de rsultats. Ds lors, il est
possible de dclarer une nouvelle mthode dans le modle qui impl-
mente la jointure entre les deux tables.
Les deux listings de code suivants expliquent comment configurer
lemploi dune nouvelle mthode du modle pour la rcupration des
deleteNeverActivated: { label: Delete never activated jobs,
action: foo }
Figure 1211
Dtail des requtes SQL
gnres pour afficher la
liste dobjets
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
244
enregistrements et de quelle manire elle doit tre implmente pour le
module job.
Surcharge de la mthode de rcupration des enregistrements dans le fichier apps/
backend/modules/job/config/generator.yml
Il ne reste alors plus qu implmenter cette nouvelle mthode
retrieveBackendJobList dans la classe JobeetJobTable qui se trouve
dans le fichier lib/model/doctrine/JobeetJobTable.class.php.
Implmentation de la mthode retrieveBackendJobList dans le fichier lib/model/
doctrine/JobeetJobTable.class.php
Cette mthode retrieveBackendJobList() reoit un objet
Doctrine_Query en paramtre auquel elle ajoute la condition de jointure
entre les tables jobeet_categoy et jobeet_job dans le but de crer auto-
matiquement chaque objet JobeetJob et JobeetCategory. Maintenant,
grce cette requte optimise, le nombre de requtes SQL excutes
pour gnrer la vue liste tombe 4 comme latteste longlet SQL de la
barre de dboguage de Symfony.
config:
list:
table_method: retrieveBackendJobList
class JobeetJobTable extends Doctrine_Table
{
public function retrieveBackendJobList(Doctrine_Query $q)
{
$rootAlias = $q->getRootAlias();
$q->leftJoin($rootAlias . '.JobeetCategory c');
return $q;
}
// ...
Figure 1212
Dtail des requtes SQL
gnres pour afficher la
liste dobjets aprs
optimisation
1
2


L
e

g

r
a
t
e
u
r

d

i
n
t
e
r
f
a
c
e

d

a
d
m
i
n
i
s
t
r
a
t
i
o
n
Groupe Eyrolles, 2008
245
La configuration de la vue list des modules category et job sachve ici.
Les deux modules disposent prsent chacun dune vue list entirement
fonctionnelle, pagine et adapte aux besoins des administrateurs de
lapplication. Il est dsormais temps de sintresser la personnalisation
des formulaires qui composent les vues new et edit.
Configurer les formulaires des vues de
saisie de donnes
La configuration des vues de formulaires se dcompose en trois sections
distinctes dans le fichier generator.yml : form, edit et new. Toutes pos-
sdent les mmes possibilits de configuration dans la mesure o la sec-
tion form peut tre surcharge dans les deux autres sections.
Les parties qui suivent expliquent comment configurer les formulaires
qui permettent de crer et dditer les objets Doctrine en vue de leur
srialisation dans la base de donnes. La configuration de ces vues inter-
vient diffrents niveaux tels que lajout ou la suppression de champs, la
modification de leurs proprits respectives, le choix dune classe de for-
mulaire personnalise la place de celle par dfaut
Configurer la liste des champs afficher dans les
formulaires des offres
De la mme manire que pour la vue list, il est possible de changer le
nombre et lordre des champs affichs dans les formulaires grce
loption display. Comme le formulaire affich est dfini par une classe,
il est recommand de ne pas tenter de supprimer de champ dans la
mesure o cela risque de conduire des erreurs de validation inatten-
dues. Le code ci-dessous explique comment utiliser loption display
pour configurer la liste des champs afficher dans le formulaire. Cette
option a la particularit de faciliter lordonnancement des champs par
groupes dinformation de mme nature.
Configuration des formulaires dans le fichier apps/backend/modules/job/config/
generator.yml
config:
form:
display:
Content: [category_id, type, company, logo, url, position,
location, description, how_to_apply, is_public, email]
Admin: [_generated_token, is_activated, expires_at]
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
246
La configuration ci-dessus dfinit deux groupes dinformations (Content
et Admin), dont chacun contient un sous-ensemble des champs du for-
mulaire. La capture dcran ci-dessous montre le rendu obtenu partir
de cette configuration du formulaire.
Les colonnes du groupe dinformations Admin ne saffichent pas encore
dans le navigateur car elles ont t retires de la dfinition du formulaire
de gestion dune offre. Elles apparatront plus tard dans ce chapitre
lorsquune classe de formulaire doffre demploi personnalise sera
dfinie pour lapplication backoffice.
Le gnrateur dadministration dispose dun support natif pour les rela-
tions plusieurs plusieurs (many to many). Sur le formulaire de manipu-
lation dune catgorie, il existe un champ pour le nom et pour le slug,
ainsi quune liste droulante pour les partenaires associs. diter cette
relation dans cette page na pas vritablement de sens, cest pourquoi elle
peut tre retire du formulaire comme le montre la configuration de la
classe de formulaire ci-dessous.
Configuration de la liste des champs du formulaire de catgorie dans le fichier lib/
form/doctrine/JobeetCategoryForm.class.php
Figure 1213
Rendu du formulaire
ddition dune offre
demploi
class JobeetCategoryForm extends BaseJobeetCategoryForm
{
public function configure()
{
unset($this['created_at'], $this['updated_at'],
$this['jobeet_affiliates_list']);
}
}
1
2


L
e

g

r
a
t
e
u
r

d

i
n
t
e
r
f
a
c
e

d

a
d
m
i
n
i
s
t
r
a
t
i
o
n
Groupe Eyrolles, 2008
247
La section suivante aborde la notion de champs virtuels, cest--dire des
champs supplmentaires qui nappartiennent pas la dfinition de base
des widgets de la classe de formulaire.
Ajouter des champs virtuels au formulaire
Dans loption display du formulaire doffre demploi figure le champ
_generated_token dont le nom commence par un underscore. Cela
signifie que le rendu de ce champ est pris en charge par un template par-
tiel nomm _generated_token.php. Le contenu de ce fichier crer est
prsent dans le bloc de code ci-dessous.
Contenu du template partiel dans le fichier apps/backend/modules/job/templates/
_generated_token.php
Un partial a accs au formulaire courant ($form), et donc lobjet associ
via la mthode getObject() applique sur cet objet de formulaire. Le
rendu du champ virtuel peut aussi tre dlgu un composant en pr-
fixant son nom par un tilde.
Redfinir la classe de configuration du formulaire
Comme le formulaire sera manipul par les administrateurs, quelques
informations nouvelles ont t ajoutes en complment par rapport au
formulaire de lapplication frontend. Pour linstant, certaines dentre
elles napparaissent pas sur le formulaire puisquelles ont t supprimes
dans la classe JobeetJobForm.
Implmenter une nouvelle classe de formulaire par dfaut
Afin dobtenir diffrents formulaires entre les applications frontend et
backend, il est ncessaire de crer deux classes spares, dont une inti-
tule BackendJobeetJobForm qui tend la classe JobeetJobForm. Comme
les champs cachs ne sont pas les mmes dans les deux formulaires, la
classe JobeetJobForm doit tre remanie lgrement afin de dplacer
linstruction unset() dans une mthode qui sera redfinie dans
BackendJobeetJobForm.
<div class="sf_admin_form_row">
<label>Token</label>
<?php echo $form->getObject()->getToken() ?>
</div>
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
248
Contenu de la classe JobeetJobForm dans le fichier lib/form/doctrine/
JobeetJobForm.class.php
Contenu de la classe BackendJobeetJobForm dans le fichier lib/form/doctrine/
BackendJobeetJobForm.class.php
prsent, la classe de formulaire par dfaut utilise par le gnrateur
dadministration peut tre surcharge grce au paramtre class du
fichier de configuration generator.yml. Avant de rafrachir le naviga-
teur, le cache de Symfony doit tre vid afin de prendre en compte la
nouvelle classe cre dans le fichier dauto chargement de classes.
class JobeetJobForm extends BaseJobeetJobForm
{
public function configure()
{
$this->removeFields();
$this->validatorSchema['email'] = new sfValidatorEmail();
// ...
}
protected function removeFields()
{
unset(
$this['created_at'], $this['updated_at'],
$this['expires_at'], $this['is_activated'],
$this['token']
);
}
}
class BackendJobeetJobForm extends JobeetJobForm
{
public function configure()
{
parent::configure();
}
protected function removeFields()
{
unset(
$this['created_at'], $this['updated_at'],
$this['token']
);
}
}
1
2


L
e

g

r
a
t
e
u
r

d

i
n
t
e
r
f
a
c
e

d

a
d
m
i
n
i
s
t
r
a
t
i
o
n
Groupe Eyrolles, 2008
249
Modification du nom de la classe par dfaut pour les formulaires de lapplication
backend dans le fichier apps/backend/modules/job/config/generator.yml
Le formulaire de modification possde nanmoins un lger inconvnient.
La photo courante tlcharge de lobjet nest affiche nulle part sur le for-
mulaire, et il est impossible de forcer la suppression de lactuelle.
Implmenter un meilleur mcanisme de gestion des photos des
offres
Le widget sfWidgetFormInputFileEditable apporte des capacits sup-
plmentaires ddition de fichier par rapport au widget classique de tl-
chargement de fichier. Le code suivant remplace le widget actuel du
champ logo par un widget de type sfWidgetFormInputFileEditable,
afin de permettre aux administrateurs de faciliter la gestion des images
tlcharges pour chaque offre.
Modification du widget du champ logo dans le fichier lib/form/doctrine/
BackendJobeetJobForm.class.php
Le widget sfWidgetFormInputFileEditable prend plusieurs options en
paramtres pour personnaliser ses fonctionnalits et son rendu dans le
navigateur :
file_src dtermine le chemin web vers le fichier courant ;
is_image indique si oui ou non le fichier doit tre affich comme une
image ;
config:
form:
class: BackendJobeetJobForm
class BackendJobeetJobForm extends JobeetJobForm
{
public function configure()
{
parent::configure();
$this->widgetSchema['logo'] = new sfWidgetFormInputFileEditable(array(
'label' => 'Company logo',
'file_src' => '/uploads/jobs/'.$this->getObject()->getLogo(),
'is_image' => true,
'edit_mode' => !$this->isNew(),
'template' => '<div>%file%<br />%input%<br />%delete% %delete_label%</div>',
));
$this->validatorSchema['logo_delete'] = new sfValidatorPass();
}
// ...
}
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
250
edit_mode spcifie si le formulaire est en mode ddition ou sil ne
lest pas ;
with_delete permet dajouter ou non une case cocher pour forcer la
suppression du fichier ;
template dfinit le gabarit HTML pour le rendu du widget.
Lapparence du gnrateur de backoffice peut facilement tre personna-
lise dans la mesure o les templates gnrs dfinissent un nombre
important de classes CSS et dattributs id. Par exemple, le champ logo
peut tre mis en forme en utilisant la classe CSS
sf_admin_form_field_logo. Chaque champ du formulaire dispose de sa
propre classe relative au type de widget, telles que sf_admin_text ou
sf_admin_boolean.
Loption edit_mode utilise la mthode isNew() de lobjet
sfDoctrineRecord. Elle retourne true si lobjet modle du formulaire est
nouveau (cration) et false dans le cas contraire (dition). Cette
mthode est une aide indniable lorsque le formulaire requiert des wid-
gets ou des validateurs qui dpendent du statut de lobjet embarqu.
Figure 1214 Rendu du widget de modification de tlchargement de fichier
1
2


L
e

g

r
a
t
e
u
r

d

i
n
t
e
r
f
a
c
e

d

a
d
m
i
n
i
s
t
r
a
t
i
o
n
Groupe Eyrolles, 2008
251
Configurer les filtres de recherche de la vue
liste
Configurer les filtres de recherche est sensiblement similaire configurer
les vues de formulaire. En ralit, les filtres sont tout simplement des
formulaires. Ainsi, comme avec les formulaires, les classes de filtre ont
t automatiquement gnres par la tche doctrine:build-all, mais
peuvent galement tre reconstruites laide de la tche
doctrine:build-filters.
Les classes des formulaires de filtre sont situes dans le rpertoire lib/
filter, et chaque classe de modle dispose de sa propre classe de filtre
(JobeetJobFormFilter pour JobeetJob).
Supprimer les filtres du module de category
Comme le module de gestion des catgories ne ncessite gure de filtres,
ces derniers peuvent tre dsactivs dans le fichier de configuration
generator.yml comme le montre le code ci-dessous.
Suppression de la liste de filtres du module category dans le fichier apps/backend/
modules/category/config/generator.yml
Configurer la liste des filtres du module job
Par dfaut, la liste de filtres permet de rduire la slection des objets en
agissant sur toutes les colonnes de la table. Or, tous les filtres ne sont pas
pertinents, cest pourquoi certains dentre eux peuvent tre retirs pour
les offres demploi.
Modification de la liste de filtres du module job dans le fichier apps/backend/
modules/job/config/generator.yml
Tous les filtres sont optionnels. De ce fait, il ny a aucun besoin de sur-
charger la classe de formulaire de filtres pour configurer les champs
afficher.
config:
filter:
class: false
filter:
display: [category_id, company, position, description,
is_activated, is_public, email, expires_at]
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
252
Personnaliser les actions dun module
autognr
Lorsque la configuration ne suffit plus, il est toujours possible dajouter
de nouvelles mthodes la classe des actions, comme cela a dj t
expliqu avec la prolongation de la dure de vie dune offre demploi. Par
ailleurs, toutes les actions autognres par le gnrateur de backoffice
peuvent tre redfinies grce lhritage de classe. Le tableau ci-dessous
dresse la liste intgrale de ces mthodes autognres dont on peut sur-
charger la configuration depuis la classe dactions du module.
Figure 1215
Rendu de la liste des
filtres aprs configuration
Tableau 121 Liste des actions auto gnres par le gnrateur dadministration
Nom de la mthode Description
executeIndex() Excute la vue list
executeFilter() Met jour les filtres
executeNew() Excute laction de la vue new
1
2


L
e

g

r
a
t
e
u
r

d

i
n
t
e
r
f
a
c
e

d

a
d
m
i
n
i
s
t
r
a
t
i
o
n
Groupe Eyrolles, 2008
253
Il faut savoir que chaque mthode gnre ne ralise quun traitement
bien spcifique, ce qui permet de modifier certains comportements sans
avoir copier et coller trop de code, ce qui irait lencontre de la philo-
sophie DRY du framework.
Personnaliser les templates dun module
autognr
Tous les templates construits par le gnrateur dadministration ont la
particularit dtre facilement personnalisables dans la mesure o ils con-
tiennent tous de nombreuses classes CSS et attributs id dans le code
HTML. Cependant, il arrive parfois que cela ne suffise pas pour aboutir
au mme rsultat que la maquette graphique choisie par le client.
Au mme titre que pour les classes, tous les templates originaux des modules
autognrs sont entirement redfinissables. Il est important de rappeler
quun template nest en fait quun simple fichier PHP contenant unique-
ment du code HTML et PHP. Crer un nouveau template du mme nom
que loriginal dans le rpertoire apps/backend/modules/job/templates/
(o job est ici le nom du module concern) suffit pour quil soit utilis.
executeCreate() Cre une nouvelle offre dans la base de donnes
executeEdit() Excute laction de la vue edit
executeUpdate() Met jour une offre dans la base de donnes
executeDelete() Supprime une offre demploi
executeBatch() Excute une action sur un lot dobjets
executeBatchDelete() Excute laction de suppression par lot _delete
processForm() Traite le formulaire dune offre demploi
getFilters() Retourne la liste des filtres courants
setFilters() Dfinit les filtres courants
getPager() Retourne lobjet de pagination de la vue liste
getPage() Retourne la page courante de pagination
setPage() Dfinit la page courante de pagination
buildCriteria() Construit le critre de tri de la liste
addSortCriteria() Ajoute le critre de tri la liste
getSort() Retourne la colonne de tri courante
setSort() Dfinit la colonne de tri courant
Tableau 121 Liste des actions auto gnres par le gnrateur dadministration (suite)
Nom de la mthode Description
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
254
Le tableau suivant dcrit lensemble des templates ncessaires au bon
fonctionnement des modules btis partir du gnrateur de backoffice.
Tous remplissent une fonction bien prcise dans la gnration dune
page dadministration, ce qui permet, comme pour les classes autogn-
res, de pouvoir en surcharger seulement quelques uns pour personna-
liser le rendu dun module.
Tableau 122 Liste des templates autognrs par le gnrateur dadministration
Nom du template Description
_assets.php Rend les feuilles de style et JavaScript utiliser dans le template
_filters.php Rend la barre latrale des filtres
_filters_field.php Rend un champ unique de la barre de filtres
_flashes.php Rend les messages flash de feedback
_form.php Affiche le formulaire
_form_actions.php Affiche les actions du formulaire
_form_field.php Affiche un champ unique du formulaire
_form_fieldset.php Affiche un groupe de champs de mme nature dans le formulaire
_form_footer.php Affiche le pied du formulaire
_form_header.php Affiche len-tte du formulaire
_list.php Affiche la liste dobjets
_list_actions.php Affiche les actions de la liste
_list_batch_actions.php Affiche les actions de lot dobjets de la liste
_list_field_boolean.php Affiche un champ de type boolen dans la liste
_list_footer.php Affiche le pied de la liste
_list_header.php Affiche len-tte de la liste
_list_td_actions.php Affiche les actions unitaires dun objet reprsent par une ligne du tableau
_list_td_batch_actions.php Affiche la case cocher dun objet
_list_td_stacked.php Affiche le layout stacked dune ligne
_list_td_tabular.php Affiche un champ unique dune ligne
_list_th_stacked.php Affiche les proprits d'un enregistrement dans une seule colonne sur toute la ligne
_list_th_tabular.php Affiche les proprits d'un enregistrement dans des colonnes spares du tableau
_pagination.php Affiche la pagination de la liste dobjets
editSuccess.php Affiche la vue ddition dun objet
indexSuccess.php Affiche la vue liste du module
newSuccess.php Affiche la vue de cration dun nouvel objet
1
2


L
e

g

r
a
t
e
u
r

d

i
n
t
e
r
f
a
c
e

d

a
d
m
i
n
i
s
t
r
a
t
i
o
n
Groupe Eyrolles, 2008
255
La configuration finale du module
Avec seulement deux fichiers de configuration et quelques ajustements
dans le code PHP gnrs, lapplication Jobeet se dote dune interface
dadministration complte en un temps record. Les deux codes suivants
synthtisent toute la configuration finale des modules prsente pas
pas tout au long de ce chapitre.
Configuration finale du module job
Configuration finale du module job dans le fichier apps/backend/modules/job/
config/generator.yml
generator:
class: sfDoctrineGenerator
param:
model_class: JobeetJob
theme: admin
non_verbose_templates: true
with_show: false
singular: ~
plural: ~
route_prefix: jobeet_job
with_doctrine_route: 1
config:
actions: ~
fields:
is_activated: { label: Activated?, help: Whether the
user has activated the job, or not }
is_public: { label: Public? }
list:
title: Job Management
layout: stacked
display: [company, position, location, url,
is_activated, email]
params: |
%%is_activated%% <small>%%JobeetCategory%%</small>
- %%company%%
(<em>%%email%%</em>) is looking for a %%=position%%
(%%location%%)
max_per_page: 10
sort: [expires_at, desc]
batch_actions:
_delete: ~
extend: ~
object_actions:
extend: ~
_edit: ~
_delete: ~
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
256
Configuration finale du module category
Configuration finale du module category dans le fichier apps/backend/modules/
category/config/generator.yml
actions:
deleteNeverActivated: { label: Delete never activated
jobs }
table_method: retrieveBackendJobList
filter:
display: [category_id, company, position, description,
is_activated, is_public, email, expires_at]
form:
class: BackendJobeetJobForm
display:
Content: [category_id, type, company, logo, url,
position, location, description,
how_to_apply, is_public, email]
Admin: [_generated_token, is_activated, expires_at]
edit:
title: Editing Job "%%company%% is looking for a
%%position%%"
new:
title: Job Creation
generator:
class: sfDoctrineGenerator
param:
model_class: JobeetCategory
theme: admin
non_verbose_templates: true
with_show: false
singular: ~
plural: ~
route_prefix: jobeet_category
with_doctrine_route: 1
config:
actions: ~
fields: ~
list:
title: Category Management
display: [=name, slug]
batch_actions: {}
object_actions: {}
filter:
class: false
form:
actions:
_delete: ~
_list: ~
_save: ~
1
2


L
e

g

r
a
t
e
u
r

d

i
n
t
e
r
f
a
c
e

d

a
d
m
i
n
i
s
t
r
a
t
i
o
n
Groupe Eyrolles, 2008
257
En rsum
En seulement moins dune heure, le projet Jobeet dispose dune interface
complte dadministration des catgories et des offres demploi dposes
par les utilisateurs. Mais le plus tonnant, cest que lcriture de toute cette
interface de gestion naura mme pas ncessit plus de cinquante lignes de
code PHP. Ce nest pas si mal pour autant de fonctionnalits intgres !
Le chapitre suivant aborde un point essentiel du projet Jobeet : la scuri-
sation du backoffice dadministration laide dun identifiant et dun
mot de passe. Ce sera galement loccasion de parler de la classe Sym-
fony qui gre lutilisateur courant
edit:
title: Editing Category "%%name%%"
new:
title: New Category
ASTUCE Configuration YAML vs configuration PHP
prsent, vous savez que lorsque quelque chose est configurable dans un fichier YAML, cest
galement possible avec du code PHP pur. En ce qui concerne le gnrateur de backoffice,
toute la configuration PHP se trouve dans le fichier apps/backend/modules/job/
lib/jobGeneratorConfiguration.class.php. Ce dernier fournit les mmes
options que le fichier YAML mais avec une interface en PHP. Afin dapprendre et de connatre
les noms des mthodes de configuration, il suffit de jeter un oeil aux classes de base auto-
gnres (par exemple BaseJobGeneratorConfiguration) dans le fichier de confi-
guration PHP
cache/backend/dev/modules/autoJob/lib/
X BaseJobGeneratorConfiguration.class.php.
Groupe Eyrolles, 2008
chapitre 13
Groupe Eyrolles, 2008
Authentification et droits
avec lobjet sfUser
Grer lauthentification dun utilisateur, lui accorder des droits
daccs certaines ressources, ou bien garder en mmoire
des informations dans la session en cours est monnaie courante
dans une application web actuelle.
Le framework Symfony intgre nativement tous ces
mcanismes afin de faciliter la gestion de lutilisateur qui
navigue sur lapplication.
MOTS-CLS :
BAuthentification
BDroits daccs
BObjet sfUser
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
260
Le chapitre prcdent a t loccasion de dcouvrir tout un lot de nou-
velles fonctionnalits propres au framework Symfony. Avec seulement
quelques lignes de code PHP, le gnrateur dadministration de Sym-
fony assure au dveloppeur la cration dinterfaces de gestion en quel-
ques minutes.
Les prochaines pages permettent de dcouvrir comment Symfony gre la
persistance des donnes entre les requtes HTTP. En effet, le protocole
HTTP est dit sans tat , ce qui signifie que chaque requte effectue est
compltement indpendante de celle qui la prcde ou de celle qui lui suc-
cde. Les sites web daujourdhui ncessitent un moyen de faire persister
les donnes entre les requtes afin damliorer lexprience de lutilisateur.
La session dun utilisateur peut tre identifie laide dun cookie .
Dans Symfony, le dveloppeur na nul besoin de manipuler directement
la session car celle-ci est en fait abstraite grce lobjet sfUser qui repr-
sente lutilisateur final de lapplication.
Dcouvrir les fonctionnalits de base de
lobjet sfUser
Le chapitre prcdent fait quelque peu usage de lobjet sfUser qui garde
en mmoire les messages de feedback afficher lutilisateur aprs que
celui-ci a ralis une action. La prsente partie explique ce quils sont
rellement, comment ils fonctionnent et comment les utiliser dans les
dveloppements Symfony.
CULTURE TECHNIQUE Quest-ce quun cookie ?
Malgr tout ce que lon a pu lui reprocher dans le pass au sujet de la scurit, un cookie nen
demeure pas moins un simple fichier texte dpos temporairement sur le poste de lutilisateur.
Lobjectif premier du cookie dans une application web est la reconnaissance de lutilisateur entre
deux requtes HTTP ainsi que le stockage de brves informations nexcdant pas 4 kilo-octets.
Un cookie possde au minimum un nom, une valeur et une date dexpiration dans le temps.
Dautres paramtres optionnels supplmentaires peuvent lui tre attribus comme son
domaine de validit ou bien le fait quil soit utilis sur une connexion scurise via le proto-
cole HTTPS. Un cookie est cr par le navigateur du client la demande du serveur lorsque ce
dernier lui envoie les en-ttes adquats. chaque nouvelle requte HTTP, si le navigateur du
client dispose dun cookie valable pour le nom de domaine en cours, il transmet le nom et la
valeur de son cookie au serveur qui pourra ainsi oprer des traitements particuliers.
1
3


A
u
t
h
e
n
t
i
f
i
c
a
t
i
o
n

e
t

d
r
o
i
t
s

a
v
e
c

l

o
b
j
e
t

s
f
U
s
e
r
Groupe Eyrolles, 2008
261
Comprendre les messages flash de feedback
quoi servent ces messages dans Symfony ?
Dans Symfony, un flash est un message phmre stock dans la ses-
sion de lutilisateur et qui est automatiquement supprim la toute pro-
chaine requte. Ces messages sont particulirement utiles lorsque lon a
besoin dafficher un message lutilisateur aprs une redirection. Le gn-
rateur dadministration a recours ces messages de feedback ds lors
quune offre est sauvegarde, supprime ou bien prolonge dans le temps.
crire des messages flash depuis une action
Les messages flash de feedback sont gnralement dfinis dans les
mthodes des classes daction aprs que lutilisateur a effectu une opra-
tion sur lapplication. La mise en mmoire dun message est triviale
puisquil sagit simplement dutiliser la mthode setFlash() de lobjet
sfUser courant comme le montre le code ci-dessous.
Exemple de cration dun message flash dans le fichier apps/frontend/modules/job/
actions/actions.class.php
Figure 131
Exemple de message flash
dans ladministration de
Jobeet
public function executeExtend(sfWebRequest $request)
{
$request->checkCSRFProtection();
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
262
La mthode setFlash() accepte deux arguments. Le premier est liden-
tifiant du message flash tandis que le second est le corps exact du mes-
sage. Il est possible de dfinir nimporte quel identifiant de message
flash, mais notice et error sont les plus communs car ils sont principale-
ment utiliss par le gnrateur dadministration. Ils servent respective-
ment afficher des messages dinformation et des messages derreur.
Lire des messages flash dans un template
Cest au dveloppeur quincombe la tche dinclure ou non les messages
flash dans les templates. Pour ce faire, Symfony intgre les deux
mthodes hasFlash() et getFlash() de lobjet sfUser. Ces dernires per-
mettent respectivement de vrifier si lutilisateur possde ou non un
message flash pour lidentifiant pass en paramtre, et de rcuprer
celui-ci en vue de son affichage dans le template. Dans Jobeet par
exemple, les flashs sont tous affichs par le fichier layout.php.
Affichage des messages flash dans le fichier apps/frontend/templates/layout.php
Dans un template, lobjet sfUser est accessible via la variable spciale
$sf_user.
Stocker des informations dans la session courante de
lutilisateur
Pour linstant, les cas dutilisation de Jobeet ne prvoient aucune con-
trainte particulire impliquant le stockage dinformations dans la session
de lutilisateur. Pourquoi ne pas ajouter un nouveau besoin fonctionnel
$job = $this->getRoute()->getObject();
$this->forward404Unless($job->extend());
$this->getUser()->setFlash('notice', sprintf('Your job
validity has been extend until %s.', date('m/d/Y',
strtotime($job->getExpiresAt()))));
$this->redirect($this->generateUrl('job_show_user', $job));
}
<?php if ($sf_user->hasFlash('notice')): ?>
<div class="flash_notice"><?php echo $sf_user
->getFlash('notice') ?></div>
<?php endif; ?>
<?php if ($sf_user->hasFlash('error')): ?>
<div class="flash_error"><?php echo $sf_user
->getFlash('error') ?></div>
<?php endif; ?>
REMARQUE Accder dautres objets
internes de Symfony dans les templates
Dautres objets internes de Symfony sont toujours
accessibles dans les templates, sans avoir les
leur passer explicitement depuis une action. Il
sagit par exemple des objets sfRequest,
sfUser ou bien encore sfResponse qui se
trouvent respectivement dans les variables
$sf_request, $sf_user et
$sf_response.
1
3


A
u
t
h
e
n
t
i
f
i
c
a
t
i
o
n

e
t

d
r
o
i
t
s

a
v
e
c

l

o
b
j
e
t

s
f
U
s
e
r
Groupe Eyrolles, 2008
263
permettant de faire usage de la session ? Il sagit en effet de dvelopper
un mcanisme simple dhistorique dans le but de faciliter la navigation
de lutilisateur. chaque fois que ce dernier consulte une offre, celle-ci
est conserve dans lhistorique, et les trois dernires offres lues sont raf-
fiches dans la barre de menu afin de pouvoir y revenir plus tard.
Lire et crire dans la session de lutilisateur courant
Pour rpondre cette problmatique, il convient de sauvegarder dans la
session de lutilisateur un historique des offres demploi lues et dy ajouter
loffre en cours de consultation son arrive. Pour ce faire, le framework
Symfony introduit les mthodes getAttribute() et setAttribute() de
lobjet sfUser qui permettent respectivement de lire et dcrire des infor-
mations dans la session persistante. Le bout de code ci-dessous illustre le
principe de fonctionnement de la session de lutilisateur.
Dans cet exemple, la mthode setAttribute() de lobjet sfUser sauve-
garde la valeur bar dans la variable de session foo. Puis cette valeur est
rcupre au moyen de la mthode getAttribute() laquelle est pass en
premier argument le nom de la variable de session. Cette mthode accepte
galement un second argument facultatif qui correspond la valeur par
dfaut renvoyer si la variable de session est vide ou nexiste pas.
Implmenter lhistorique de navigation de lutilisateur
Limplmentation de lhistorique de navigation de lutilisateur ne pose
pas de difficults particulires. Il sagit en effet de sauvegarder en session
persistante un tableau des cls primaires des offres demploi dj consul-
tes. Lalgorithme tient en seulement trois lignes de code PHP comme le
montre le code PHP ci-dessous.
Implmentation de lhistorique de navigation dans la mthode executeShow() du
fichier apps/frontend/modules/job/actions/actions.class.php
<?php
public function executeAction(sfWebRequest $request)
{
$this->getUser()->setAttribute('foo', 'bar');// Set bar in the foo session variable

$foo = $this->getUser()->getAttribute('foo','baz');// Returns bar
}
class jobActions extends sfActions
{
public function executeShow(sfWebRequest $request)
{
$this->job = $this->getRoute()->getObject();
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
264
Bien quil soit possible de stocker des objets PHP directement dans une
session, cette pratique nen demeure pas moins fortement dconseille
dans la mesure o toutes les variables de session sont srialises entre
chaque requte. Lorsquune session est recre sur une nouvelle page, les
objets quelle contient sont dsrialiss, cest--dire quils sont recons-
truits et quils peuvent tre altrs ou bien obsoltes sils ont t modifis
ou bien supprims entre-temps.
Refactoriser le code de lhistorique de navigation dans
le modle
Laction executeShow() nest pas lendroit le plus idal pour accueillir la
logique de lhistorique de navigation. En effet, le code en devient enti-
rement dpendant, ce qui lempche dtre rutilis ailleurs. Dautre part,
il nest pas factoris et complexifie ainsi inutilement laction
executeShow().
Implmenter lhistorique de navigation dans la classe myUser
Daprs ce dernier postulat, on en dduit rapidement le besoin de
dplacer le code vers la couche du modle afin de respecter la sparation
entre les diffrentes logiques. Le meilleur endroit pour recevoir limpl-
mentation de lhistorique de navigation est naturellement la classe
myUser qui surcharge les spcificits de la classe de base sfUser avec des
comportements propres lapplication courante.
Implmentation de la mthode addJobToHistory() dans la classe myUser du fichier
apps/frontend/lib/myUser.class.php
// fetch jobs already stored in the job history
$jobs = $this->getUser()->getAttribute('job_history',
array());
// add the current job at the beginning of the array
array_unshift($jobs, $this->job->getId());
// store the new job history back into the session
$this->getUser()->setAttribute('job_history', $jobs);
}
// ...
}
class myUser extends sfBasicSecurityUser
{
public function addJobToHistory(JobeetJob $job)
{
$ids = $this->getAttribute('job_history', array());
1
3


A
u
t
h
e
n
t
i
f
i
c
a
t
i
o
n

e
t

d
r
o
i
t
s

a
v
e
c

l

o
b
j
e
t

s
f
U
s
e
r
Groupe Eyrolles, 2008
265
Le remaniement du code prend dsormais en compte toutes les con-
traintes de lhistorique de navigation. Linstruction in_array($job-
>getId(), $ids) sassure que loffre demploi nexiste pas deux fois dans
lhistorique de navigation tandis que linstruction array_slice($ids, 0,
3) se contente de rcuprer uniquement les cls primaires des trois der-
nires annonces consultes par lutilisateur en vue de rafficher un lien
vers chacune delle dans la page courante.
Simplifier laction executeShow() de la couche contrleur
Ltape suivante consiste mettre jour laction executeShow() prc-
dente afin de la simplifier en lui implmentant la mthode
addJobToHistory().
Remaniement de la mthode executeShow() dans le fichier apps/frontend/modules/
job/actions/actions.class.php
Grce au remaniement du code, laction ne comporte plus que deux
lignes de code PHP alors quil en fallait quatre prcdemment. La der-
nire tape consiste enfin rcuprer les offres demploi de lhistorique
puis afficher leur titre respectif sur la page en cours.
Afficher lhistorique des offres demploi consultes
prsent, il ne reste plus qu ajouter le programme PHP qui gnre
dans la vue le code HTML de lhistorique des trois dernires annonces.
Limplmentation de ce script est ajouter avant la variable $sf_content
du fichier layout.php comme lillustre le code ci-dessous.
if (!in_array($job->getId(), $ids))
{
array_unshift($ids, $job->getId());
$this->setAttribute('job_history', array_slice($ids, 0,
3));
}
}
}
class jobActions extends sfActions
{
public function executeShow(sfWebRequest $request)
{
$this->job = $this->getRoute()->getObject();
$this->getUser()->addJobToHistory($this->job);
}
// ...
}
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
266
Gnration de lhistorique des offres dans le fichier apps/frontend/templates/
layout.php
Ce template fait appel la mthode getJobHistory() de lobjet sfUser.
Cette mthode a pour rle de rcuprer les trois derniers objets JobeetJob
de la base de donnes partir des identifiants des offres sauvegards dans
la session courante de lutilisateur. Le fonctionnement de la mthode
getJobHistory() est trivial puisquil sagit de faire appel lhistorique des
offres dans la session, puis de retourner lobjet Doctrine_Collection rsul-
tant de la requte SQL excute par Doctrine.
Implmentation de la mthode getJobHistory() dans la classe myUser du fichier
apps/frontend/lib/myUser.class.php
<div id="job_history">
Recent viewed jobs:
<ul>
<?php foreach ($sf_user->getJobHistory() as $job): ?>
<li>
<?php echo link_to($job->getPosition().' - '
.$job->getCompany(), 'job_show_user', $job) ?>
</li>
<?php endforeach; ?>
</ul>
</div>
<div class="content">
<?php echo $sf_content ?>
</div>
class myUser extends sfBasicSecurityUser
{
public function getJobHistory()
{
$ids = $this->getAttribute('job_history', array());
if (!empty($ids))
{
return Doctrine::getTable('JobeetJob')
->createQuery('a')
->whereIn('a.id', $ids)
->execute();
}
else
{
return array();
}
}
// ...
}
1
3


A
u
t
h
e
n
t
i
f
i
c
a
t
i
o
n

e
t

d
r
o
i
t
s

a
v
e
c

l

o
b
j
e
t

s
f
U
s
e
r
Groupe Eyrolles, 2008
267
La capture dcran ci-dessous prsente le rsultat final obtenu aprs que
lhistorique de navigation a t compltement implment.
Implmenter un moyen de rinitialiser lhistorique des offres
consultes
Tous les attributs de lutilisateur sont grs par une instance de la classe
sfParameterHolder. Les mthodes getAttribute() et setAttribute()
sont deux mthodes proxy (raccourcies) pour getParameterHolder-
>get() et getParameterHolder()->set().
Lobjet sfParameterHolder contient galement une mthode remove()
qui permet de supprimer un attribut de la session de lutilisateur. Cette
mthode nest associe aucune mthode raccourcie dans la classe
sfUser, cest pourquoi le code ci-dessous fait directement appel lobjet
sfParameterHolder pour supprimer lattribut job_history, et ainsi vider
lhistorique des offres consultes.
Implmentation de la mthode resetJobHistory dans la classe myUser du fichier
apps/frontend/lib/myUser.class.php
La classe sfParameterHolder est galement utilise par lobjet sfRequest
pour sauvegarder ses diffrents paramtres.
Figure 132
Exemple de lhistorique
de navigation reposant
sur les informations
sauvegardes en session
class myUser extends sfBasicSecurityUser
{
public function resetJobHistory()
{
$this->getAttributeHolder()->remove('job_history');
}
// ...
}
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
268
Les sections suivantes de ce chapitre abordent de nouvelles notions cls
du framework Symfony. Il sagit en effet de dcouvrir les mcanismes
internes de scurisation des applications et de contrle de droits daccs
de lutilisateur. Un systme dauthentification avec nom dutilisateur et
mot de passe pour lapplication backend de Jobeet est dvelopp en guise
dexemple pratique.
Comprendre les mcanismes de
scurisation des applications
Cette partie sintresse aux principes dauthentification et de contrle de
droits daccs sur une application. Lauthentification consiste sassurer
que lutilisateur courant est bien authentifi sur lapplication ; cest par
exemple le cas lorsquil remplit un formulaire avec son couple didentifiant
et mot de passe valides. Le contrle daccs, quant lui, vrifie que lutili-
sateur dispose des autorisations ncessaires pour accder tout ou partie
dune application (par exemple, lorsquil sagit de lui empcher la suppres-
sion dun objet sil ne dispose pas dun statut de super administrateur).
Activer lauthentification de lutilisateur sur une application
Dcouvrir le fichier de configuration security.yml
Comme avec la plupart des fonctionnalits de Symfony, la scurit dune
application est gre au travers du fichier YAML security.yml. Pour
linstant, ce fichier se trouve par dfaut dans le rpertoire apps/backend/
config/ du projet et dsactive toute scurit de lapplication comme le
montre son contenu :
Contenu du fichier apps/backend/config/security.yml
En fixant la constante de configuration is_secure la valeur on, toute
lapplication backend forcera lutilisateur tre authentifi pour aller
plus loin. La capture dcran ci-dessous illustre la page affiche par
dfaut lutilisateur si ce dernier nest pas authentifi.
default:
is_secure: off
1
3


A
u
t
h
e
n
t
i
f
i
c
a
t
i
o
n

e
t

d
r
o
i
t
s

a
v
e
c

l

o
b
j
e
t

s
f
U
s
e
r
Groupe Eyrolles, 2008
269
Dans un fichier de configuration YAML, une valeur boolenne peut tre
exprime laide des chanes de caractres on, off, true ou bien false.
Comment se fait-il que lutilisateur se voit redirig vers cette page qui
napparat nulle part dans le projet ? La rponse se trouve tout naturellement
dans les logs que Symfony gnre en environnement de dveloppement.
Analyse des logs gnrs par Symfony
Lanalyse des logs dans la barre de dbogage de Symfony indique que la
mthode executeLogin() de la classe defaultActions est appele pour
chaque page laquelle lutilisateur tente daccder.
Le module default nexiste pas rellement dans un projet Symfony
puisquil sagit dun module livr entirement avec le framework. Bien
videmment, lappel la mthode executeLogin() du module default
est entirement redfinissable afin de pouvoir rediriger automatique-
ment lutilisateur vers une page personnalise.
Personnaliser la page de login par dfaut
Lorsquun utilisateur essaie daccder une action scurise, Symfony
dlgue automatiquement la requte laction login du module default.
Ces deux informations ne sont pas choisies au hasard par le framework
puisquelles figurent dans le fichier de configuration settings.yml de
lapplication. Ainsi, il est possible de redfinir soi-mme laction personna-
lise invoquer lorsquun utilisateur souhaite accder une page scurise.
Figure 133
Page de login par dfaut de Symfony
pour un utilisateur non identifi
Figure 134
Extrait des logs lorsque lutilisateur tente
daccder une page scurise
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
270
Redfinition de laction login dans le fichier apps/backend/config/settings.yml
Pour des raisons techniques videntes, il est impossible de scuriser la
page de login afin dviter une rcursivit infinie. Au chapitre 4, il a t
dmontr quune mme configuration stalonne plusieurs endroits
dans le projet. Il en va de mme pour le fichier security.yml. En effet,
pour scuriser ou retirer la scurit dune action ou de tout un module de
lapplication, il suffit dajouter un fichier security.yml dans le rpertoire
config/ du module concern.
Authentifier et tester le statut de lutilisateur
Par dfaut, la classe myUser drive directement de la classe
sfBasicSecurityUser qui tend elle-mme la classe sfUser.
sfBasicSecurityUser intgre des mthodes additionnelles pour grer
lauthentification et les droits daccs de lutilisateur courant. Lauthenti-
fication de lutilisateur se manipule avec deux mthodes seulement :
isAuthenticated() et setAuthenticated(). La premire se contente de
retourner si oui ou non lutilisateur est dj authentifi, alors que la
seconde permet de lauthentifier (ou de le dconnecter). Le bout de code
illustre leur fonctionnement dans le cadre dune action.
prsent, il est temps de sintresser au mcanisme natif de contrle de
droits daccs. La section suivante explique comment restreindre tout ou
partie des fonctionnalits dune application lutilisateur en lui affectant
un certain nombre de droits.
Restreindre les actions dune application lutilisateur
Dans la plupart des projets complexes, les dveloppeurs sont confronts
la notion de politique de droits daccs aux informations. Cest dautant
all:
.actions:
login_module: default
login_action: login
index:
is_secure: off
all:
is_secure: on
if (!$this->getUser()->isAuthenticated())
{
$this->getUser()->setAuthenticated(true);
}
1
3


A
u
t
h
e
n
t
i
f
i
c
a
t
i
o
n

e
t

d
r
o
i
t
s

a
v
e
c

l

o
b
j
e
t

s
f
U
s
e
r
Groupe Eyrolles, 2008
271
plus vrai dans le cas dune interface de gestion ou bien dans un Intranet
pour lequel il peut exister plusieurs profils dutilisateurs : administrateur,
publicateur, modrateur, trsorier Tous ne possdent pas les mmes
autorisations et ne peuvent dans ce cas avoir accs certaines parties de
lapplication lorsquils sont connects. Heureusement, Symfony intgre
parfaitement un systme de contrle de droits daccs simple et rapide
mettre en uvre.
Activer le contrle des droits daccs sur lapplication
Lorsquun utilisateur est authentifi sur lapplication, il ne doit pas for-
cment avoir accs toutes les fonctionnalits de cette dernire. Cer-
taines zones peuvent donc lui tre restreintes en tablissant une politique
de droits daccs. Lusager doit alors possder les droits ncessaires et
suffisants pour atteindre les pages quil dsire. Dans Symfony, les droits
de lutilisateur sont nomms credentials et se dclarent plusieurs
niveaux. Le code ci-dessous du fichier security.yml de lapplication
dsactive lauthentification mais contraint nanmoins lutilisateur pos-
sder les droits dadministrateur pour aller plus loin.
Le systme de contrle de droits daccs de Symfony est particulire-
ment simple et puissant. Un droit peut reprsenter nimporte quelle
chose pour dcrire le modle de scurit de lapplication comme les
groupes ou les permissions.
tablir des rgles de droits daccs complexes
La section credentials du fichier security.yml supporte les oprations
boolennes pour dcrire les contraintes de droits daccs complexes. Par
exemple, si un utilisateur est contraint davoir les droits A et B, il suffit
dencadrer ces deux derniers avec des crochets.
En revanche, si un utilisateur doit avoir les droits A ou B, il faut alors
encadrer ces derniers par une double paire de crochets comme ci-dessous.
default:
is_secure: off
credentials: admin
index:
credentials: [A, B]
index:
credentials: [[A, B]]
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
272
Au final, il est possible de mixer volont les rgles boolennes jusqu
trouver celle qui correspond la politique de droits daccs que lon sou-
haite mettre en application.
Grer les droits daccs via lobjet sfBasicSecurityUser
Toute la politique de droits daccs peut galement tre gre directe-
ment grce lobjet sfBasicSecurityUser qui fournit un ensemble de
mthodes capables dajouter ou de retirer des droits lutilisateur, mais
qui permet galement de tester si ce dernier en possde certains, comme
le montrent les exemples ci-dessous.
Le tableau ci-dessous rsume et dtaille plus exactement chacune de ces
mthodes.
// Add one or more credentials
$user->addCredential('foo');
$user->addCredentials('foo', 'bar');
// Check if the user has a credential
echo $user->hasCredential('foo'); => true
// Check if the user has both credentials
echo $user->hasCredential(array('foo', 'bar')); => true
// Check if the user has one of the credentials
echo $user->hasCredential(array('foo', 'bar'), false); =>
true
// Remove a credential
$user->removeCredential('foo');
echo $user->hasCredential('foo'); => false
// Remove all credentials (useful in the logout process)
$user->clearCredentials();
echo $user->hasCredential('bar'); => false
Tableau 131 Liste des mthodes de lobjet sfBasicSecurityUser
Nom de la mthode Description
addCredential('foo') Affecte un droit lutilisateur
addCredentials('foo', 'bar') Affecte un ou plusieurs droits lutilisateur
hasCredential('foo') Indique si oui ou non lutilisateur possde le droit foo
hasCredential(array('foo', 'bar')) Indique si oui ou non lutilisateur possde les droits foo et bar
hasCredential(array('foo', 'bar'), false) Indique si oui ou non lutilisateur possde lun des deux droits
removeCredential('foo') Retire le droit foo lutilisateur
clearCredentials() Retire tous les droits de lutilisateur
1
3


A
u
t
h
e
n
t
i
f
i
c
a
t
i
o
n

e
t

d
r
o
i
t
s

a
v
e
c

l

o
b
j
e
t

s
f
U
s
e
r
Groupe Eyrolles, 2008
273
Pour lapplication Jobeet, il nest pas ncessaire de grer les droits daccs
dans la mesure o celle-ci naccueille quun seul type de profils : le rle
administrateur.
Mise en place de la scurit de
lapplication backend de Jobeet
Tous les concepts prsents dans la section prcdente sont plutt thori-
ques et nont pas encore t vritablement mis en application. Il est temps
de retourner lapplication backend et de lui ajouter la page didentifica-
tion qui lui manque pour le moment. Lobjectif nest pas dcrire ce type de
fonctionnalit ex nihilo (from scratch disent les puristes anglicistes), et heu-
reusement le framework Symfony dispose de tout le ncessaire pour
mettre cela en uvre en quelques minutes. Il sagit en effet de recourir
linstallation du plugin sfDoctrineGuardPlugin qui intgre entre autres les
mcanismes didentification et de reconnaissance de lutilisateur.
Installation du plug-in sfDoctrineGuardPlugin
Lune des incroyables forces du framework Symfony rside dans son riche
cosystme de plugins. Lun des prochains chapitres de cet ouvrage
explique en quoi il est trs facile de crer des plugins et en quoi ces derniers
sont des outils puissants et pratiques. En effet, un plugin est capable de
contenir aussi bien des modules que de la configuration, des classes PHP,
des fichiers XML ou encore des ressources web Pour le dveloppement
de lapplication Jobeet, cest le plugin sfDoctrineGuardPlugin qui sera ins-
tall pour garantir le besoin de scurisation de linterface dadministration.
Linstallation dun plugin dans le projet est simple puisquil suffit dex-
cuter une commande depuis linterface en ligne de commande Symfony
comme le montre le code suivant.
La commande plugin:install tlcharge et installe un plugin partir
de son nom. Tous les plugins du projet sont stocks sous le rpertoire
plugins/ et chacun deux possde son propre rpertoire nomm avec le
nom du plugin. Bien quelle soit pratique et souple utiliser, la tche
plugin:install requiert linstallation de PEAR sur le serveur pour fonc-
tionner correctement !
$ php symfony plugin:install sfDoctrineGuardPlugin
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
274
Lorsque lon installe un plugin partir de la tche plugin:install, Sym-
fony tlcharge la toute dernire version stable de ce dernier. Pour installer
une version spcifique dun plugin, il suffit de lui passer loption facultative
-release accompagne du numro de la version dsire. La page ddie
du plugin sur le site officiel du framework Symfony liste toutes les versions
disponibles du plugin pour chaque version du framework.
Dans la mesure o un plugin est copi en intgralit dans son propre
rpertoire, il est galement possible de tlcharger son archive depuis le
site officiel de Symfony, puis de la dcompresser dans le rpertoire
plugins/ du projet. Enfin, une dernire mthode alternative dinstalla-
tion consiste crer un lien externe vers le dpt Subversion du plugin
laide dun client Subversion et de la proprit svn:externals sur le
rpertoire plugins/.
Enfin, il ne faut pas oublier dactiver le plugin aprs lavoir install si lon
nutilise pas la mthode enableAllPluginsExcept() de la classe config/
ProjectConfiguration.class.php.
Mise en place des scurits de lapplication backend
Gnrer les classes de modle et les tables SQL
Chaque plugin possde son propre fichier README qui explique comment
linstaller et le configurer pour le projet. Les lignes qui suivent dcrivent
pas pas la configuration du plugin sfDoctrineGuardPlugin en com-
menant par la gnration du modle et des nouvelles tables SQL. En
effet, ce plugin fournit plusieurs classes de modle pour grer les utilisa-
teurs, les groupes et les permissions sauvegards en base de donnes.
La tche doctrine:build-all-reload supprime toutes les tables exis-
tantes de la base de donnes avant de les recrer une par une. Afin
dviter cela, il est possible de gnrer le modle, les formulaires et les fil-
tres, et enfin crer les nouvelles tables en excutant le script SQL du
plugin gnr dans le rpertoire data/sql/.
Implmenter de nouvelles mthodes lobjet User via la classe
sfGuardSecurityUser
Lexcution de la tche doctrine:build-all-reload a gnr de nou-
velles classes de modle pour le plugin sfDoctrineGuardPlugin, cest
pourquoi le cache du projet doit tre vid pour les prendre en compte.
$ php symfony doctrine:build-all-reload
$ php symfony cc
1
3


A
u
t
h
e
n
t
i
f
i
c
a
t
i
o
n

e
t

d
r
o
i
t
s

a
v
e
c

l

o
b
j
e
t

s
f
U
s
e
r
Groupe Eyrolles, 2008
275
Dans la mesure o sfDoctrineGuardPlugin ajoute plusieurs nouvelles
mthodes la classe de lutilisateur, il est ncessaire de changer la classe
parente de la classe myUser par sfGuardSecurityUser comme le montre le
code ci-dessous.
Redfinition de la classe de base de myUser dans le fichier apps/backend/lib/
myUser.class.php
Activer le module sfGuardAuth et changer laction de login par
dfaut
Le plugin sfDoctrineGuardPlugin fournit galement une action signin
lintrieur du module sfGuardAuth afin de grer lauthentification des
utilisateurs. Il faut donc indiquer Symfony que cest vers cette action
que les utilisateurs non authentifis doivent tre amens lorsquils
essaient daccder une page scurise. Pour ce faire, il suffit dditer le
fichier de configuration settings.yml de lapplication backend.
Dfinition du module et de laction par dfaut pour la page de login dans le fichier
apps/backend/config/settings.yml
Dans la mesure o tous les plugins sont partags pour toutes les applica-
tions du projet, il est ncessaire de nactiver que les modules utiliser dans
lapplication en les ajoutant explicitement au paramtre de configuration
enabled_modules comme cest le cas ici pour le module sfGuardAuth. La
figure ci-dessous illustre la page de login qui est affiche lutilisateur
lorsque ce dernier nest pas authentifi sur lapplication.
class myUser extends sfGuardSecurityUser
{
// ...
}
all:
.settings:
enabled_modules: [default, sfGuardAuth]
# ...
.actions:
login_module: sfGuardAuth
login_action: signin
# ...
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
276
Crer un utilisateur administrateur
La dernire tape consiste enregistrer dans la base de donnes un
compte utilisateur autoris sauthentifier sur linterface de gestion de
Jobeet. Il serait bien sr possible de raliser cette opration manuelle-
ment directement dans la base de donnes ou bien partir dun fichier
de donnes initiales mais le plugin fournit des tches Symfony pour faci-
liter ce genre de procdures.
La tche guard:create-user permet de crer un nouveau compte utilisa-
teur dans la base de donnes en lui spcifiant le nom dutilisateur en pre-
mier argument et le mot de passe associ en second. De son ct, la
tche guard:promote promeut le compte utilisateur pass en argument
comme super administrateur.
sfDoctrineGuardPlugin inclut dautres tches pour grer les utilisateurs,
les groupes et les permissions depuis la ligne de commande. Par
exemple, lutilisation de la tche list affiche la liste des commandes dis-
ponibles sous lespace de nom guard.
Cacher le menu de navigation lorsque lutilisateur nest pas
authentifi
Il reste encore un petit dtail rgler. En effet, les liens du menu
dadministration de linterface backend continuent dtre affichs mme
quand lutilisateur nest pas authentifi. Ce dernier ne devrait donc pas
tre en mesure de voir ce menu. Pour le masquer, il suffit seulement de
tester dans le template si lutilisateur est authentifi ou non grce la
mthode isAuthenticated() vue prcdemment.
Figure 135
Page didentification
lapplication backend
$ php symfony guard:create-user fabien SecretPass
$ php symfony guard:promote fabien
$ php symfony list guard
1
3


A
u
t
h
e
n
t
i
f
i
c
a
t
i
o
n

e
t

d
r
o
i
t
s

a
v
e
c

l

o
b
j
e
t

s
f
U
s
e
r
Groupe Eyrolles, 2008
277
Masquer le menu de navigation lutilisateur non identifi dans le fichier apps/
backend/templates/layout.php
Un lien de dconnexion a galement t ajout pour permettre lutilisa-
teur connect de fermer proprement sa session sur linterface dadminis-
tration. Ce lien utilise la route sf_guard_signout dclare dans le plugin
sfDoctrineGuardPlugin. La tche app:routes permet de lister
lensemble des routes dfinies pour lapplication courante.
Ajouter un nouveau module de gestion des utilisateurs
La fin de ce chapitre est toute proche mais il est encore temps dajouter
un module complet de gestion des utilisateurs pour parfaire lapplication.
Cette opration ne demande que quelques secondes puisque
sfDoctrineGuardPlugin dtient ce prcieux module. De la mme
manire que pour le module sfGuardAuth, le plugin sfGuardUser doit
tre rfrenc auprs de la liste des modules activs dans le fichier de
configuration settings.yml.
Ajout du module sfGuardUser dans le fichier apps/backend/config/settings.yml
Le module sfGuardUser a t gnr partir du gnrateur dadminis-
tration tudi au chapitre prcdent. De ce fait, il est entirement para-
mtrable et personnalisable grce au fichier de configuration
generator.yml se trouvant dans le rpertoire config/ du module.
Il ne reste finalement plus qu installer un lien dans le menu de naviga-
tion afin de permettre ladministrateur daccder ce nouveau module
pour grer tous les comptes utilisateurs de lapplication.
<?php if ($sf_user->isAuthenticated()): ?>
<div id="menu">
<ul>
<li><?php echo link_to('Jobs', '@jobeet_job') ?></li>
<li><?php echo link_to('Categories', '@jobeet_category') ?></li>
<li><?php echo link_to('Logout', '@sf_guard_signout') ?></li>
</ul>
</div>
<?php endif; ?>
all:
.settings:
enabled_modules: [default, sfGuardAuth, sfGuardUser]
<?php if ($sf_user->isAuthenticated()): ?>
<div id="menu">
<ul>
<li><?php echo link_to('Jobs', '@jobeet_job') ?></li>
<li><?php echo link_to('Categories', '@jobeet_category') ?>
</li>
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
278
La capture dcran ci-dessus illustre le rendu final du menu de naviga-
tion et du gestionnaire dutilisateurs qui ont t ajouts lapplication en
quelques minutes seulement.
Implmenter de nouveaux tests
fonctionnels pour lapplication frontend
Ce chapitre nest pas encore termin puisquil reste parler rapidement
des tests fonctionnels propres lutilisateur. Comme le navigateur de
Symfony est capable de simuler les cookies, il est trs facile de tester les
comportements de lusager laide du testeur natif sfTesterUser. Il est
temps de mettre jour les tests fonctionnels de lapplication frontend
pour prendre en compte les fonctionnalits additionnelles du menu
implmentes dans ce chapitre. Pour ce faire, il suffit dajouter le code
suivant la fin du fichier de tests fonctionnels jobActionsTests.php.
Tests fonctionnels de lhistorique de navigation ajouter la fin du fichier
test/functional/frontend/jobActionsTest.php
<li><?php echo link_to('Users', '@sf_guard_user') ?></li>
<li><?php echo link_to('Logout', '@sf_guard_signout') ?></li>
</ul>
</div>
<?php endif; ?>
Figure 136
Rendu final de la page
daccueil du module
sfGuardUser
$browser->
info('4 - User job history')->
loadData()->
restart()->
info(' 4.1 - When the user access a job, it is added to its
history')->
get('/')->
1
3


A
u
t
h
e
n
t
i
f
i
c
a
t
i
o
n

e
t

d
r
o
i
t
s

a
v
e
c

l

o
b
j
e
t

s
f
U
s
e
r
Groupe Eyrolles, 2008
279
Afin de faciliter les tests, il est ncessaire de forcer le rechargement des
donnes initiales de test et de rinitialiser le navigateur afin de dmarrer
avec une nouvelle session vierge. Les tests ci-dessus font appel la
mthode isAttribute() du testeur sfTesterUser qui permet de vrifier
la prsence et la valeur dune donne de session de lutilisateur.
Le testeur sfTesterUser fournit galement les mthodes
isAuthenticated() et hasCredential() qui contrlent lauthentification
et les autorisations de lutilisateur courant.
En rsum
Les classes internes de Symfony ddies lutilisateur constituent une
bonne manire de sabstraire de la gestion du mcanisme des sessions de
PHP. Couples lexcellent systme de gestion des plugins ainsi quau
plugin sfDoctrineGuardPlugin, elles sont capables de scuriser une
interface dadministration en quelques minutes ! Au final, lapplication
Jobeet dispose dune interface de gestion propre et complte qui permet
aux administrateurs de grer des utilisateurs grce aux modules livrs par
le plugin.
Comme Jobeet est une application Web 2.0 digne de ce nom, elle ne
peut chapper aux traditionnels flux RSS et Atom qui seront dvelopps
avec autant de facilit au cours du prochain chapitre
click('Web Developer', array(), array('position' => 1))->
get('/')->
with('user')->begin()->
isAttribute('job_history', array($browser->
getMostRecentProgrammingJob()->getId()))->
end()->
info(' 4.2 - A job is not added twice in the history')->
click('Web Developer', array(), array('position' => 1))->
get('/')->
with('user')->begin()->
isAttribute('job_history', array($browser->
getMostRecentProgrammingJob()->getId()))->
end()
;
Groupe Eyrolles, 2008
chapitre 14
Groupe Eyrolles, 2008
Les flux de syndication ATOM
Toutes les applications web modernes mettent disposition
leurs contenus sous forme de flux afin de permettre
aux internautes de suivre les dernires informations publies
dans leur navigateur ou leur agrgateur favoris. Lapplication
Jobeet ne droge pas cette rgle, et, grce au framework
Symfony, sera pourvue dun systme de flux de syndication
ATOM.
MOTS-CLS :
BFormats XML, HTML et ATOM
BRoutage
BTemplates
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
282
La fracheur et le renouvellement de linformation sont des points majeurs
qui contribuent la russite dune application web grand public. En effet,
un site Internet dont le contenu nest pas mis jour rgulirement risque
de perdre une part non ngligeable de son audience, cette dernire ayant le
besoin permanent dtre nourrie dinformations nouvelles.
Or, comment est-il possible dinformer un internaute non connect au
site que le contenu de ce dernier a t mis jour ? Par exemple, en ce qui
concerne lapplication dveloppe tout au long de cet ouvrage, il sagit de
trouver un moyen de notifier linternaute la prsence de nouvelles
offres demploi publies, sans que celui-ci nait se rendre de lui-mme
sur le site. La rponse cette problmatique se trouve dans les flux de
syndication (feeds en anglais) RSS et ATOM. En effet, les formats RSS
et ATOM sont deux standards reposant sur la norme XML, et peuvent
tre lus par tous les navigateurs web modernes ou par des agrgateurs de
contenus tels que Netvibes, Google Reader, delicious.com Leur stan-
dardisation ainsi que leur extrme simplicit servent galement
changer, voire publier, de linformation entre les diffrentes applica-
tions web ou terminaux (tlphones mobiles par exemple).
Lobjectif de ce quatorzime chapitre est dimplmenter petit petit des
flux de syndication ATOM des offres demploi afin que lutilisateur
puisse tre tenu inform des nouveauts publies.
Dcouvrir le support natif des formats
de sortie
Dfinir le format de sortie dune page
Le framework Symfony dispose dun support natif des formats de sortie et
des types de fichiers mimes (mime-types en anglais), ce qui signifie que le
mme Modle et Contrleur peuvent avoir diffrents templates en fonc-
tion du format demand. Le format par dfaut est bien videmment le
HTML mais Symfony supporte un certain nombre de formats de sortie
supplmentaires comme txt, js, css, json, xml, rdf ou bien encore atom.
La mthode setRequestFormat() de lobjet sfRequest permet de dfinir
le format de sortie dune page.
$request->setRequestFormat('xml');
1
4


L
e
s

f
l
u
x

d
e

s
y
n
d
i
c
a
t
i
o
n

A
T
O
M
Groupe Eyrolles, 2008
283
Grer les formats de sortie au niveau du routage
Bien quil soit possible de dfinir manuellement le format de sortie dune
action dans Symfony, ce dernier se trouve embarqu la plupart du temps
dans lURL. De ce fait, Symfony est capable de dterminer lui-mme le
format de sortie retourner daprs la valeur de la variable sf_format de
la route correspondante. Par exemple, pour la liste des offres demploi,
lurl est la suivante :
http://jobeet.localhost/frontend_dev.php/job
Cette mme URL est quivalente la suivante :
http://jobeet.localhost/frontend_dev.php/job.html
Ces deux URLs sont effectivement identiques car les routes gnres par
la classe sfDoctrineRouteCollection possdent la variable sf_format en
guise dextension, et parce que le HTML est le format privilgi. Pour
sen convaincre, il suffit dexcuter la commande app:routes afin
dobtenir un rsultat similaire la capture ci-dessous.
Prsentation gnrale du format ATOM
Un fichier de syndication ATOM est en ralit un document au format
XML qui sappuie sur une structure bien dfinie, localise dans sa dcla-
ration de type de document : la DTD (Document Type Declaration en
anglais). Lensemble des spcificits du format ATOM dpasse large-
ment le cadre de cet ouvrage ; il est nanmoins ncessaire de connatre
les fondamentaux pour tre capable de raliser un flux minimal valide.
La principale chose retenir propos du format ATOM concerne sa
structure. En effet, un flux ATOM est compos de deux parties
distinctes : les informations gnrales du flux et les entres.
Figure 141
Liste des routes paramtres pour lapplication frontend
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
284
Les informations globales du flux
Les informations gnrales sont situes tout de suite sous llment
racine <feed> du flux. Les balises prsentes sous cet lment apportent
des donnes globales comme le titre du flux, sa description, son lien, sa
ou ses catgories, sa date de dernire mise jour, son logo Certaines
dentre elles sont obligatoires et doivent donc figurer imprativement
dans le flux afin que celui-ci soit considr comme valide.
Les entres du flux
Les entres, quant elles, sont les items qui dcrivent le contenu. Leur
nombre nest pas limit et elles sont rfrences laide de llment
<entry> qui contient une srie de nuds fils pour les dcrire et donner
du sens linformation syndique. Les fils du nud <entry> sont ainsi
responsables dinformations telles que le titre, le contenu, le lien vers la
page originale sur le site Internet, le ou les auteurs, la date de publica-
tion et bien plus encore. L encore, certaines donnes sont obliga-
toires afin de rendre le flux valide.
Le flux ATOM minimal valide
Le code ci-dessous donne la structure minimale requise pour rendre un
flux ATOM valide. Il intgre entre autres le titre, la date de mise jour
ainsi que lidentifiant unique en guise dinformations gnrales. En ce
qui concerne les entres, cet exemple est compos dune seule et unique
entre qui contient elle aussi un jeu dinformations obligatoires. Parmi
elles se trouvent le titre du contenu, son extrait, sa date de mise jour
ainsi que son auteur.
Exemple de flux ATOM minimaliste valide
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Jobeet</title>
<updated>2009-03-17T20:38:43Z</updated>
<id>afba34a2a11ab13eeba5d0a7aa22bbb6120e177b</id>
<entry>
<title>Sensio Labs is looking for a Web Developer</title>
<author>
<name>Sensio Labs</name>
</author>
<id>d0be2dc421be4fcd0172e5afceea3970e2f3d940</id>
<updated>2009-03-17T20:38:43Z</updated>
1
4


L
e
s

f
l
u
x

d
e

s
y
n
d
i
c
a
t
i
o
n

A
T
O
M
Groupe Eyrolles, 2008
285
Lobjectif des prochaines pages est de sappuyer sur ces connaissances de
base dans le but de gnrer des flux dinformations plus complexes et
valides. Il sagit en effet de construire successivement deux flux ATOM
pour lapplication Jobeet. Le premier consiste crer la liste des dernires
offres demploi publies sur le site, toutes catgories confondues, alors que
le second est un flux dynamique propre chaque catgorie de lapplication.
Gnrer des flux de syndication ATOM
Afin de sinitier et de comprendre plus concrtement comment fonctionne
le mcanisme des formats de sortie dans Symfony, les pages suivantes
droulent pas pas la cration de flux de syndication au format ATOM.
Pour commencer, il est primordial de dcouvrir et de comprendre de
quelle manire est dclar un nouveau format de sortie dans Symfony.
Flux ATOM des dernires offres demploi
Dclarer un nouveau format de sortie
Dans Symfony, supporter diffrents formats est aussi simple que de crer
diffrents templates dont le nom du fichier intgre la particule du format
souhait. Par exemple, pour raliser un flux de syndication ATOM des
dernires offres demploi, un nouveau template nomm
indexSuccess.atom.php doit tre disponible et contenir par exemple le
contenu statique suivant.
Exemple de code ATOM pour les dernires offres dans le fichier apps/frontend/
modules/job/templates/indexSuccess.atom.php
<summary>
You've already developed websites with Symfony and you want
to work with Open-Source technologies.
You have a minimum of 3 years
experience in web development with PHP or Java and you wish
to
participate to development of Web 2.0 sites using the best
frameworks available.
</summary>
</entry>
</feed>
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
286
Le nom du fichier contient la particule atom avant lextension .php. Cette
dernire indique Symfony le format de sortie renvoyer au client. La
section suivante donne un rapide rappel sur les conventions de nommage
des fichiers de template dans un projet.
Rappel des conventions de nommage des templates
Dans la mesure o le format HTML est le plus couramment employ
dans la ralisation dapplications web, lexpression du format de sortie
.html nest pas obligatoire et peut donc tre omise du nom du gabarit
PHP. En effet, les deux templates indexSuccess.php et
indexSuccess.html.php sont quivalents pour le framework, cest pour-
quoi celui-ci utilise le premier quil trouve.
Pourquoi les noms des templates par dfaut sont-ils suffixs avec
Success ? Une action est capable de retourner une valeur qui indique
quel template doit tre rendu. Si laction ne retourne rien, cela corres-
pond au code ci-dessous qui renvoie la valeur Success :
Pour changer le suffixe dun template, il suffit tout simplement de
retourner une valeur diffrente comme par exemple :
<title>Jobeet</title>
<subtitle>Latest Jobs</subtitle>
<link href="" rel="self"/>
<link href=""/>
<updated></updated>
<author>
<name>Jobeet</name>
</author>
<id>Unique Id</id>
<entry>
<title>Job title</title>
<link href="" />
<id>Unique id</id>
<updated></updated>
<summary>Job description</summary>
<author>
<name>Company</name>
</author>
</entry>
</feed>
return sfView::SUCCESS; // == 'Success'
return sfView::ERROR; // == 'Error'
return 'Foo';
1
4


L
e
s

f
l
u
x

d
e

s
y
n
d
i
c
a
t
i
o
n

A
T
O
M
Groupe Eyrolles, 2008
287
De mme, il a t montr au cours des chapitres prcdents que le nom
du template rendre pouvait lui aussi tre modifi grce lemploi de la
mthode setTemplate().
Il est temps de revenir la gnration des flux de syndication et de
modifier le layout de lapplication grand public afin quelle dispose des
liens vers ces derniers.
Ajouter le lien vers le flux des offres dans le layout
Par dfaut, Symfony modifie automatiquement len-tte HTTP
Content-Type de la rponse en fonction du format. De plus, tous les for-
mats qui ne sont pas du HTML ne sont pas dcors par le layout. Dans
le cas des flux ATOM par exemple, Symfony retourne au client le type
de contenu application/atom+xml; charset=utf-8 dans len-tte
Content-Type de la rponse.
Lapplication frontend de Jobeet a besoin dun hyperlien supplmentaire
pour faciliter laccs au flux dinformations par lutilisateur. Le code ci-
dessous donne le code HTML et PHP ajouter dans le pied de page du
layout de lapplication.
Ajout dun lien vers le flux de syndication ATOM des offres demploi dans le fichier
apps/frontend/templates/layout.php
LURL interne ici cre est la mme que celle qui existe dj pour la liste
des offres, ceci prs quelle redfinit la valeur de la variable sf_format
vue prcdemment. Par ailleurs, les navigateurs web sont capables de
dcouvrir et de charger automatiquement les flux de syndication dune
application web, condition que cette dernire intgre une balise <link>
dans la section <head> de la page. Cette balise spciale informe le client
quune ressource externe la page courante est disponible lURL indi-
que, et que le type de contenu de cette dernire est du mme type que
celui spcifi dans lattribut type de la balise.
Ajout du marqueur du flux ATOM dans la section HEAD du fichier apps/frontend/
templates/layout.php
$this->setTemplate('foo');
<li class="feed">
<a href="<?php echo url_for('@job?sf_format=atom') ?>">Full
feed</a>
</li>
<link rel="alternate" type="application/atom+xml" title="Latest
Jobs"
href="<?php echo url_for('@job?sf_format=atom', true) ?>" />
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
288
Lattribut href de la balise <link> reoit une URL absolue qui est
gnre laide du second argument du helper url_for().
Gnrer les informations globales du flux
Le premier objectif de la construction du flux consiste gnrer les
informations globales de ce dernier. Pour ce faire, len-tte actuel du flux
doit tre remplac par le code ci-dessous.
Ajout des informations gnrales du flux ATOM dans le fichier apps/frontend/
modules/job/templates/indexSuccess.atom.php
Ce template fait usage de la fonction strtotime() afin dobtenir la valeur
du champ created_at sous la forme dun timestamp Unix. Pour obtenir
la date de cration de la dernire offre poste, il suffit de crer la
mthode getLatestPost() suivante.
Implmentation de la mthode getLatestPost() dans la classe JobeetJobTable du
fichier lib/model/doctrine/JobeetJobTable.class.php
Il ne reste prsent qu gnrer toutes les entres du flux correspon-
dantes aux dernires offres publies sur le site Internet.
<title>Jobeet</title>
<subtitle>Latest Jobs</subtitle>
<link href="<?php echo url_for('@job?sf_format=atom', true) ?>"
rel="self"/>
<link href="<?php echo url_for('@homepage', true) ?>"/>
<updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ',
strtotime(Doctrine::getTable('JobeetJob')->getLatestPost()->
getCreatedAt())) ?></updated>
<author>
<name>Jobeet</name>
</author>
<id><?php echo sha1(url_for('@job?sf_format=atom', true)) ?>
</id>
class JobeetJobTable extends Doctrine_Table
{
public function getLatestPost()
{
$q = Doctrine_Query::create()
->from('JobeetJob j');
$this->addActiveJobsQuery($q);
return $q->fetchOne();
}
// ...
}
1
4


L
e
s

f
l
u
x

d
e

s
y
n
d
i
c
a
t
i
o
n

A
T
O
M
Groupe Eyrolles, 2008
289
Gnrer les entres du flux ATOM
Chaque entre est compose dun titre, dun contenu au format HTML,
dun lien, dun identifiant unique, dune date de publication et dun
auteur. Toutes ces informations sont bien videmment issues de la base
de donnes grce laction index du module job.
Implmentation des entres du flux ATOM dans le fichier apps/frontend/modules/
templates/indexSuccess.atom.php
La mthode getHost() de lobjet sfWebRequest ($sf_request) retourne
le serveur courant, qui permet ensuite de construire aisment un lien
absolu vers le logo de la socit en recherche de nouveaux collaborateurs.
La fonction gmstrftime() se charge quant elle de formater une date
GMT daprs le paramtrage de la locale du serveur.
<?php use_helper('Text') ?>
<?php foreach ($categories as $category): ?>
<?php foreach ($category->getActiveJobs(sfConfig::get('app_max_jobs_on_homepage')) as $job): ?>
<entry>
<title>
<?php echo $job->getPosition() ?> (<?php echo $job->getLocation() ?>)
</title>
<link href="<?php echo url_for('job_show_user', $job, true) ?>" />
<id><?php echo sha1($job->getId()) ?></id>
<updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', strtotime($job->getCreatedAt())) ?>
</updated>
<summary type="xhtml">
<div xmlns="http://www.w3.org/1999/xhtml">
<?php if ($job->getLogo()): ?>
<div>
<a href="<?php echo $job->getUrl() ?>">
<img src="http://<?php echo $sf_request->getHost().'/uploads/jobs/'.$job->getLogo() ?>"
alt="<?php echo $job->getCompany() ?> logo" />
</a>
</div>
<?php endif; ?>
<div>
<?php echo simple_format_text($job->getDescription()) ?>
</div>
<h4>How to apply?</h4>
<p><?php echo $job->getHowToApply() ?></p>
</div>
</summary>
<author>
<name><?php echo $job->getCompany() ?></name>
</author>
</entry>
<?php endforeach; ?>
<?php endforeach; ?>
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
290
Lors du dveloppement de flux de syndication, le dbogage de ce dernier
peut tre complexe avec le navigateur comme seul outil, dans la mesure o
ce dernier naffiche pas la source XML par dfaut qui est gnre. Lidal
est donc de sappuyer sur des outils plus pratiques en ligne de commande
tels que curl ou wget qui permettent de rcuprer une ressource identifie
par son URL. Le contenu textuel du flux ainsi rcupr devient alors un
outil supplmentaire pour apprcier les erreurs et les corriger.
Flux ATOM des dernires offres dune catgorie
Lun des objectifs de lapplication Jobeet est daider les internautes
trouver des offres demploi cibles leur profil. Partant de ce besoin, il
savre judicieux de produire un flux doffres demploi ddi chaque cat-
gorie. Disposer dun flux dynamique pour chaque catgorie a lavantage de
diffuser encore plus de contenus sur Internet mais galement de satisfaire
les besoins de chaque utilisateur en termes de pertinence de linformation.
Les prochaines sections abordent pas pas la gnration du flux dyna-
mique de chaque catgorie. Le processus de fabrication de ces flux
schelonne sur 5 tapes successives :
1 mise jour de la route de la catgorie ;
2 ajout des liens du flux de la catgorie dans les templates ;
3 refactorisation du code de gnration des entres du flux des der-
nires offres ;
4 simplification du template indexSuccess.atom.php du module job ;
5 gnration du template du flux dune catgorie.
Figure 142
Affichage du flux ATOM des dernires
offres dans le navigateur Safari
1
4


L
e
s

f
l
u
x

d
e

s
y
n
d
i
c
a
t
i
o
n

A
T
O
M
Groupe Eyrolles, 2008
291
Pour commencer, il sagit de mettre jour la route ddie de la catgorie
afin de rendre le flux publiquement accessible travers un navigateur web.
Mise jour de la route ddie de la catgorie
Pour commencer, la route daccs au dtail dune catgorie doit tre mise
jour afin quelle prenne en considration la variable sf_format comme
le montre le code ci-dessous.
Intgration du support du format ATOM la route category du fichier de
configuration apps/frontend/config/routing.yml
LURL de la route se termine dsormais par une extension bien prcise.
Il sagit soit de lextension .html (par dfaut) soit de lextension .atom.
En fonction de la valeur de celle-ci, Symfony choisira automatiquement
la vue correspondante quil doit rendre au client.
Mise jour des liens des flux de la catgorie
Dsormais, les deux liens qui pointent vers le flux ATOM doivent tre
mis jour. Ces liens figurent respectivement dans les fichiers
indexSuccess.php et showSuccess.php des modules job et category.
Extrait de code remplacer dans le fichier apps/frontend/modules/job/templates/
indexSuccess.php
Extrait de code remplacer dans le fichier apps/frontend/modules/category/
templates/showSuccess.php
category:
url: /category/:slug.:sf_format
class: sfDoctrineRoute
param: { module: category, action: show, sf_format: html }
options: { model: JobeetCategory, type: object }
requirements:
sf_format: (?:html|atom)
<div class="feed">
<a href="<?php echo url_for('category', array('sf_subject' =>
$category, 'sf_format' => 'atom')) ?>">Feed</a>
</div>
<div class="feed">
<a href="<?php echo url_for('category', array('sf_subject' =>
$category, 'sf_format' => 'atom')) ?>">Feed</a>
</div>
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
292
Factoriser le code de gnration des entres du flux
La dernire tape du parcours consiste crer le tempate
showSuccess.atom.php. Le flux de syndication a bien videmment besoin
de la liste des offres demploi ; cest pourquoi il semble opportun de refac-
toriser le code qui gnre les entres du flux. Il convient donc dajouter un
nouveau template partiel _list.atom.php au projet, qui se charge de
gnrer toutes les entres du flux. De la mme manire quavec le format
HTML, les templates partiels sont spcifiques au format utilis.
Contenu du fichier apps/frontend/job/templates/_list.atom.php
<?php use_helper('Text') ?>
<?php foreach ($jobs as $job): ?>
<entry>
<title><?php echo $job->getPosition() ?> (<?php echo $job->
getLocation() ?>)</title>
<link href="<?php echo url_for('job_show_user', $job, true)
?>" />
<id><?php echo sha1($job->getId()) ?></id>
<updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ',
strtotime($job->getCreatedAt())) ?>
</updated>
<summary type="xhtml">
<div xmlns="http://www.w3.org/1999/xhtml">
<?php if ($job->getLogo()): ?>
<div>
<a href="<?php echo $job->getUrl() ?>">
<img src="http://<?php echo $sf_request->
getHost().'/uploads/jobs/'.$job->getLogo() ?>"
alt="<?php echo $job->getCompany() ?> logo" />
</a>
</div>
<?php endif; ?>
<div>
<?php echo simple_format_text($job->getDescription()) ?>
</div>
<h4>How to apply?</h4>
<p><?php echo $job->getHowToApply() ?></p>
</div>
</summary>
<author>
<name><?php echo $job->getCompany() ?></name>
</author>
</entry>
<?php endforeach; ?>
1
4


L
e
s

f
l
u
x

d
e

s
y
n
d
i
c
a
t
i
o
n

A
T
O
M
Groupe Eyrolles, 2008
293
Simplifier le template indexSuccess.atom.php
Maintenant que le code est proprement isol dans un template partiel, le
template indexSuccess.atom.php du module job peut son tour tre
simplifi en profitant de cette modification.
Contenu du fichier apps/frontend/modules/job/templates/indexSuccess.atom.php
Gnrer le template du flux des offres dune catgorie
Le template showSuccess.atom.php du module category est sensible-
ment le mme que celui des dernires offres, ceci prs que les informa-
tions globales du flux concernent cette fois-ci la catgorie. Il sagit donc
dadapter les informations den-tte du flux avec celles correspondant
la catgorie demande. Le code ci-dessous prsente le contenu du fichier
showSuccess.atom.php.
Le fichier apps/frontend/modules/category/templates/showSuccess.atom.php
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Jobeet</title>
<subtitle>Latest Jobs</subtitle>
<link href="<?php echo url_for('@job?sf_format=atom', true)
?>" rel="self"/>
<link href="<?php echo url_for('@homepage', true) ?>"/>
<updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ',
strtotime(Doctrine::getTable('JobeetJob')->
getLatestPost()->getCreatedAt())) ?></updated>
<author>
<name>Jobeet</name>
</author>
<id><?php echo sha1(url_for('@job?sf_format=atom', true))
?></id>
<?php foreach ($categories as $category): ?>
<?php include_partial('job/list', array('jobs' => $category->
getActiveJobs(sfConfig::get('app_max_jobs_on_homepage')))) ?>
<?php endforeach; ?>
</feed>
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Jobeet (<?php echo $category ?>)</title>
<subtitle>Latest Jobs</subtitle>
<link href="<?php echo url_for('category', array('sf_subject'
=> $category, 'sf_format' => 'atom'), true) ?>" rel="self" />
<link href="<?php echo url_for('category', array('sf_subject'
=> $category), true) ?>" />
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
294
Ce template fait appel la mthode getLatestPost() qui renvoie la
toute dernire offre poste dans cette catgorie. Cette nouvelle mthode
nexiste pas encore et doit donc tre implmente dans la classe
JobeetCategory comme le montre le morceau de code ci-dessous.
Implmentation de la mthode getLatestPost() de la classe JobeetCategory dans le
fichier lib/model/doctrine/JobeetCategory.class.php
La mthode getActiveJobs() retourne une collection dobjets JobeetJob
bien quil ny ait quun seul enregistrement rcupr. Cest pour cette
raison quil faut utiliser la syntaxe ArrayAccess sur lobjet
Doctrine_Collection afin de renvoyer lobjet unitaire. La classe
Doctrine_Collection implmente galement une mthode getFirst()
qui permet dobtenir le premier objet de la collection, ce qui revient
exactement au mme que la syntaxe ArrayAccess employe ici.
<updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ',
strtotime($category->getLatestPost()->getCreatedAt())) ?>
</updated>
<author>
<name>Jobeet</name>
</author>
<id><?php echo sha1(url_for('category', array('sf_subject' =>
$category), true)) ?></id>
<?php include_partial('job/list', array('jobs' =>
$pager->getResults())) ?>
</feed>
class JobeetCategory extends BaseJobeetCategory
{
public function getLatestPost()
{
$jobs = $this->getActiveJobs(1);
return $jobs[0];
}
// ...
}
1
4


L
e
s

f
l
u
x

d
e

s
y
n
d
i
c
a
t
i
o
n

A
T
O
M
Groupe Eyrolles, 2008
295
En rsum
Comme pour la plupart des fonctionnalits de Symfony dj dcrites,
lajout de flux de syndication aux applications web sest effectu sans
effort, et ceci grce au support natif des formats de sortie. Ce chapitre a
donc permis de faciliter la vie de lutilisateur en recherche demploi, en
lui fournissant un moyen simple et efficace de se tenir directement
inform, depuis son navigateur ou son agrgateur favori, des dernires
offres demploi publies.
Le chapitre suivant va plus loin dans la manire dexposer les offres aux
internautes, en leur fournissant un service web (Web Service)
Figure 143 Affichage du flux ATOM des offres dune catgorie dans le navigateur Safari
Groupe Eyrolles, 2008
chapitre 15
Groupe Eyrolles, 2008
Construire des services web
Aujourdhui, de plus en plus de sites modernes proposent
des services web aux dveloppeurs afin que ces derniers soient
capables dintgrer leur contenu aisment dans leurs propres
applications web.
La puissance du routage de Symfony ainsi que le support natif
des formats de sortie apportent une solution efficace et rapide
pour la conception de services web.
MOTS-CLS :
BFormats XML, JSON et YAML
BEnvoi de-mails
BTests fonctionnels
BREST
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
298
Avec larrive des flux de syndication ATOM dans Jobeet, les utilisateurs
en recherche demploi ont dsormais la possibilit dtre tenus informs
de la publication de nouvelles offres en temps rel. Cest un excellent
dbut pour amliorer leur confort dutilisation mais cest surtout un
moyen efficace de diffuser de linformation travers Internet pour lui
garantir une meilleure visibilit.
De lautre ct, lorsquune nouvelle offre demploi est poste, il convient
idalement de lui faire profiter de la meilleure exposition possible. En effet,
plus lannonce est syndique sur un rseau de petits sites, et plus elle aura de
chance dattirer les meilleurs profils. Cest tout le pouvoir de la longue
trane (long tail), ce qui signifie ici que mme les petits sites Internet ou
weblogs reprsentent potentiellement une part non ngligeable dans la
russite de loffre. Grce aux services web qui seront dvelopps tout au
long de ce chapitre, les partenaires et les affilis seront capables dafficher
sur leurs sites web les toutes dernires offres demploi publies.
Concevoir le service web des offres
demploi
Lobjectif de ce quinzime chapitre est de raliser pas pas un service
web destination des dveloppeurs. Ce service sera accessible au moyen
dune interface de programmation applicative (API) simple reposant sur
lappel dURLs et sur la rcupration dinformations dans diffrents for-
mats de sortie tels que le XML, le JSON ou bien encore le YAML. La
conception de ce service web se droule en plusieurs tapes successives
qui ncessitent entre autres de dclarer la route de lAPI, dimplmenter
laction excuter ou bien encore de construire les templates relatifs
chaque format de sortie demand. Pour commencer en douceur, il con-
vient de prparer quelques jeux de donnes initiales.
Prparer des jeux de donnes initiales des affilis
Le troisime chapitre a t loccasion dtablir et de dcouvrir le schma
de description de la base de donnes. Ce dernier dfinit deux entits de
modle qui nont pas encore t exploites jusqu prsent :
JobeetAffiliate et JobeetCategoryAffiliate. La table
jobeet_affiliate stocke les informations du site Internet partenaire
tandis que la relation jobeet_category_affiliate se contente de garder
en mmoire la liste des catgories auxquelles est affili ce dernier.
Comme avec les catgories et les offres demploi, il est bon de dmarrer
le dveloppement dune nouvelle fonctionnalit en prparant quelques
1
5


C
o
n
s
t
r
u
i
r
e

d
e
s

s
e
r
v
i
c
e
s

w
e
b
Groupe Eyrolles, 2008
299
jeux de donnes initiales. Le code ci-dessous dclare deux sites Internet
partenaires et leur associe chacun une liste de catgories.
Jeu de donnes initiales du fichier data/fixtures/affiliates.yml
Comme le montre le contenu de ce fichier YAML, la cration denregis-
trements pour une relation many-to-many est aussi simple que
dfinir un tableau dont les valeurs sont les noms des enregistrements des
entits en relation. Le contenu du tableau de catgories correspond au
nom des objets dfinis dans les fichiers de donnes. Les objets peuvent
ainsi tre lis entre eux divers endroits et dans diffrents fichiers con-
dition quils aient tous bien t dclars en premier.
Pour des raisons de simplification des tests, les jetons de chaque affili
sont cods en dur dans le fichier YAML. Lorsque le site sera en produc-
tion, ces derniers devront bien videmment tre gnrs automatique-
ment au moment o lutilisateur postulera pour un compte.
Implmentation de la mthode preValidate() dans la classe JobeetAffiliate du fichier
lib/model/doctrine/JobeetAffiliate.class.php
JobeetAffiliate:
sensio_labs:
url: http://www.sensio-labs.com/
email: fabien.potencier@example.com
is_active: true
token: sensio_labs
JobeetCategories: [programming]
symfony:
url: http://www.symfony-project.org/
email: fabien.potencier@example.org
is_active: false
token: symfony
JobeetCategories: [design, programming]
class JobeetAffiliate extends BaseJobeetAffiliate
{
public function preValidate($event)
{
$object = $event->getInvoker();
if (!$object->getToken())
{
$object->setToken(sha1($object->getEmail().rand(11111, 99999)));
}
}
// ...
}
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
300
La mthode preValidate() dun objet Doctrine est toujours excute
avant que lobjet ne soit srialis en base de donnes. Elle a pour rle de
contrler que lobjet courant est valide et quil peut tre sauvegard en
base de donnes. La mthode getInvoker() de lobjet Doctrine_Event,
lui-mme pass en paramtre de la mthode preValidate(), retourne
lobjet JobeetAffiliate. La condition qui lui succde vrifie si le jeton
est dj dfini. Sil ne lest pas encore, il est alors gnr automatique-
ment partir de ladresse e-mail et dune valeur alatoire. Grce ce
mcanisme, le champ token de la table jobeet_affiliate ne peut rester
nul, et donc lobjet reste valide.
Au final, il ne reste plus qu charger les jeux de donnes initiales dans la
base de donnes au moyen de la commande Symfony doctrine:data-load.
Construire le service web des offres demploi
Dclaration de la route ddie du service web
Comme toujours, la premire bonne pratique mettre en uvre
lorsquune nouvelle fonctionnalit est sur le point dtre implmente,
est de dclarer une route ddie pour la rendre accessible. Dans le cas
prsent, il sagit de dfinir une URL propre chaque affili qui fait usage
de la variable spciale sf_format vue au cours du prcdent chapitre. Le
jeton qui vient tout juste dtre cr pour le modle JobeetAffiliate sert
effectivement rendre la route dpendante de laffili.
La route ci-dessous implmente donc ce jeton ainsi que la variable sp-
ciale sf_format afin de dterminer dans quel format les informations
doivent tre dlivres par lAPI.
Dclaration de la route api_jobs dans le fichier apps/frontend/config/routing.yml
Cette route se termine par la variable sf_format qui, daprs les restric-
tions qui lui sont appliques, peut prendre lune des valeurs parmi xml,
json ou bien yaml.
$ php symfony doctrine:data-load
api_jobs:
url: /api/:token/jobs.:sf_format
class: sfDoctrineRoute
param: { module: api, action: list }
options: { model: JobeetJob, type: list, method: getForToken }
requirements:
sf_format: (?:xml|json|yaml)
1
5


C
o
n
s
t
r
u
i
r
e

d
e
s

s
e
r
v
i
c
e
s

w
e
b
Groupe Eyrolles, 2008
301
Implmenter la mthode getForToken() de lobjet JobeetJobTable
De plus, cette route a besoin dune mthode getForToken() qui est
appele lorsque laction rcupre la collection dobjets en relation. Dans
la mesure o il faut sassurer que laffili est bien activ, le comportement
par dfaut de la route doit tre surcharg.
Implmentation de la mthode getForToken() dans le fichier lib/model/doctrine/
JobeetJobTable.class.php
La mthode getForToken() se charge de rcuprer un objet
JobeetAffiliate partir de son jeton unique. Si le jeton nexiste pas dans
la base de donnes, alors une exception de type sfError404Exception est
leve afin dtre automatiquement convertie en rponse 404 par Symfony.
Lancer ce type dexception est la manire la plus simple de gnrer des
pages derreur 404 depuis une classe de modle.
Il faut aussi remarquer que getForToken() fait appel la mthode vir-
tuelle findOneByToken() de la classe JobeetAffiliateTable. Doctrine
permet de rcuprer un objet unique dune table en utilisant la mthode
findOneBy*() o * est le nom du champ dans la table qui sert de critre
de restriction (clause WHERE de la requte SQL). Ces mthodes virtuelles
sont automatiquement gnres par Doctrine laide de limplmenta-
tion de la mthode magique __call() de PHP. Cest grce cette der-
nire quil est rendu possible dappeler des mthodes non dfinies
explicitement dans la classe de lobjet.
Implmenter la mthode getActiveJobs() de lobjet JobeetAffiliate
Dautre part, la mthode getForToken() utilise une nouvelle mthode
getActiveJobs() afin de retourner la liste des offres actives courantes.
class JobeetJobTable extends Doctrine_Table
{
public function getForToken(array $parameters)
{
$affiliate = Doctrine::getTable('JobeetAffiliate')
->findOneByToken($parameters['token']);
if (!$affiliate || !$affiliate->getIsActive())
{
throw new sfError404Exception(sprintf('Affiliate with
token "%s" does not exist or is not activated.',
$parameters['token']));
}
return $affiliate->getActiveJobs();
}
// ...
}
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
302
Implmentation de la mthode getActiveJobs() dans la classe JobeetAffiliate du
fichier lib/model/doctrine/JobeetAffiliate.class.php
La dernire tape consiste mettre en place laction de lAPI et ses tem-
plates. Pour ce faire, il suffit de gnrer un nouveau module laide de la
commande generate:module.
Dans la mesure o laction index par dfaut nest pas utile au reste de
lapplication, elle peut tre supprime en toute scurit de la classe
dactions, ainsi que son template associ indexSuccess.php.
Dvelopper le contrleur du service web
Implmenter laction executeList() du module api
Tous les formats de sortie de la route api_jobs partagent la mme action
list du module api. En sachant cela, il convient seulement dimpl-
menter la mthode executeList() puis de construire les templates pro-
pres chaque format de sortie dfinis pour la variable sf_format.
Le corps de cette mthode ne pose aucune difficult particulire dans la
mesure o cette dernire se charge de rcuprer la liste des objets
JobeetJob partir de la route appele, puis de reprsenter chaque objet
sous la forme dun tableau avant de stocker ce dernier dans un autre
tableau pass au template correspondant.
class JobeetAffiliate extends BaseJobeetAffiliate
{
public function getActiveJobs()
{
$q = Doctrine_Query::create()
->select('j.*')
->from('JobeetJob j')
->leftJoin('j.JobeetCategory c')
->leftJoin('c.JobeetAffiliates a')
->where('a.id = ?', $this->getId());
$q = Doctrine::getTable('JobeetJob')
->addActiveJobsQuery($q);
return $q->execute();
}
// ...
}
$ php symfony generate:module frontend api
1
5


C
o
n
s
t
r
u
i
r
e

d
e
s

s
e
r
v
i
c
e
s

w
e
b
Groupe Eyrolles, 2008
303
Implmentation de la mthode executeListe() dans le fichier apps/frontend/
modules/api/actions/actions.class.php
Implmenter la mthode asArray() de JobeetJob
Au lieu de passer un tableau dobjets JobeetJob aux templates comme
cest le cas dhabitude, laction transmet un tableau de chanes de carac-
tres. Comme laction est partage par trois templates diffrents, la
logique mtier de traitement des valeurs a t mutualise ailleurs dans la
mthode JobeetJob::asArray().
Implmentation de la mthode asArray() dans la classe JobeetJob du fichier lib/
model/doctrine/JobeetJob.class.php
Le code mtier de laction est prsent compltement implment, et la
prochaine tape consiste alors dvelopper le template listSuccess.php
pour chaque format de sortie dsir.
public function executeList(sfWebRequest $request)
{
$this->jobs = array();
foreach ($this->getRoute()->getObjects() as $job)
{
$this->jobs[$this->generateUrl('job_show_user', $job,
true)] = $job->asArray($request->getHost());
}
}
class JobeetJob extends BaseJobeetJob
{
public function asArray($host)
{
return array(
'category' => $this->getJobeetCategory()->getName(),
'type' => $this->getType(),
'company' => $this->getCompany(),
'logo' => $this->getLogo() ? 'http://'.$host.'/
uploads/jobs/'.$this->getLogo() : null,
'url' => $this->getUrl(),
'position' => $this->getPosition(),
'location' => $this->getLocation(),
'description' => $this->getDescription(),
'how_to_apply' => $this->getHowToApply(),
'expires_at' => $this->getCreatedAt(),
);
}
// ...
}
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
304
Construction des templates XML, JSON et YAML
Cette section aborde la gnration des templates pour les trois formats
de sortie possibles. Les templates pour le format XML et JSON sont
relativement simples comprendre et mettre en uvre, cest pourquoi
il ny aura que trs peu dexplications leur gard. En revanche, le
format YAML ncessitera dapprofondir quelques notions subtiles
comme la gestion des erreurs en fonction des environnements.
Le format XML
Le format XML est aussi simple grer que le HTML puisquil sagit
de crer un nouveau template contenant la gnration du code XML
renvoyer au client. Dans le cadre de Jobeet, il sagit daboutir un fichier
XML similaire la maquette suivante :
Ce gabarit XML ne pose aucune difficult gnrer. En effet, les noms
des balises correspondent aux cls du tableau PHP renvoy par la
mthode asArray(). Seules quelques lignes de code PHP suffisent
construire un tel template comme le montre le code ci-dessous.
<?xml version="1.0" encoding="utf-8"?>
<jobs>
<job url="http://www.jobeet.org/en/job/extreme-sensio/paris-
france/2/web-designer">
<category>design</category>
<type>part-time</type>
<logo>http://www.jobeet.org/uploads/jobs/extreme-
sensio.gif</logo>
<location>Paris, France</location>
<description>
Lorem ipsum dolor sit amet, consectetur adipisicing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis
aute irure dolor in reprehenderit in.
Voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa
qui officia deserunt mollit anim id est laborum.
</description>
<how_to_apply>Send your resume to fabien.potencier [at]
sensio.com</how_to_apply>
<expires_at>2009-05-16</expires_at>
</job>
<!-- ... ->
</jobs>
1
5


C
o
n
s
t
r
u
i
r
e

d
e
s

s
e
r
v
i
c
e
s

w
e
b
Groupe Eyrolles, 2008
305
Contenu du template apps/frontend/modules/api/templates/listSuccess.xml.php
Seulement deux instructions foreach() suffisent au parcours du tableau
$jobs. La premire itre sur la liste des offres tandis que la seconde se
charge de traverser les proprits de chacune dentre elles afin den
gnrer les bons couples balise/contenu.
Le format JSON
JSON (JavaScript Object Notation), est un format de donnes standard
driv de la notation des objets du langage ECMAScript, et qui a pour
objectif premier de structurer de linformation laide dune syntaxe
simple et lisible par les dveloppeurs. Le format JSON permet de dcrire
diffrents types de structures de donnes comme les objets, les tableaux,
les entiers, les chanes de caractres, les boolens
LAPI de Jobeet supporte nativement le format JSON. Il sagit donc
prsent de dvelopper le template correspondant capable de gnrer une
rponse au format JSON identique au code ci-dessous.
<?xml version="1.0" encoding="utf-8"?>
<jobs>
<?php foreach ($jobs as $url => $job): ?>
<job url="<?php echo $url ?>">
<?php foreach ($job as $key => $value): ?>
<<?php echo $key ?>><?php echo $value ?></<?php echo $key
?>>
<?php endforeach; ?>
</job>
<?php endforeach; ?>
</jobs>
[
{
"url": "http://www.jobeet.org/en/job/extreme-sensio/
X paris-france/2/web-designer",
"category": "design",
"type": "part-time",
"logo": "http:\/\/www.jobeet.org\/uploads\/jobs\/
X extreme-sensio.gif",
"location": "Paris, France",
"description": "\tLorem ipsum dolor sit amet, consectetur
adipisicing elit, sed do \n\t\teiusmod tempor incididunt ut
labore et dolore magna aliqua. Ut \n\t\tenim ad minim veniam,
quis nostrud exercitation ullamco laboris \n\t\tnisi ut aliquip
ex ea commodo consequat. Duis aute irure dolor \n\t\tin
reprehenderit in.\n\t\tVoluptate velit esse cillum dolore eu
fugiat nulla pariatur. \n\t\tExcepteur sint occaecat cupidatat
non proident, sunt in culpa \n\t\tqui officia deserunt mollit
anim id est laborum.",
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
306
La gnration de ce type de rsultat JSON est ralise au moyen des
quelques lignes de code PHP qui suivent, et notamment grce la fonc-
tion native json_encode() du langage PHP.
Contenu du template apps/frontend/modules/api/templates/listSuccess.json.php
Le format YAML
Paramtrer les caractristiques de la rponse
Symfony configure automatiquement certains paramtres tels que les en-
ttes de type de contenu (Content-Type) ou bien encore la dsactivation du
layout pour les formats de sortie standards intgrs au framework. Le
format YAML ne fait pas partie de la liste des formats standards supports
nativement, cest pourquoi les en-ttes HTTP ainsi que la suppression du
layout doivent tre grs manuellement dans les actions.
"how_to_apply": "Send your resume to fabien.potencier [at]
sensio.com",
"0": "2009-05-16"
}
]
[
<?php $nb = count($jobs); $i = 0; foreach ($jobs as $url =>
$job): ++$i ?>
{
"url": "<?php echo $url ?>",
<?php $nb1 = count($job); $j = 0; foreach ($job as $key =>
$value): ++$j ?>
"<?php echo $key ?>": <?php echo json_encode($value).($nb1 ==
$j ? '' : ',') ?>
<?php endforeach; ?>
}<?php echo $nb == $i ? '' : ',' ?>
<?php endforeach; ?>
]
class apiActions extends sfActions
{
public function executeList(sfWebRequest $request)
{
$this->jobs = array();
foreach ($this->getRoute()->getObjects() as $job)
{
$this->jobs[$this->generateUrl('job_show_user', $job,
true)] = $job->asArray($request->getHost());
}
1
5


C
o
n
s
t
r
u
i
r
e

d
e
s

s
e
r
v
i
c
e
s

w
e
b
Groupe Eyrolles, 2008
307
Linstruction switch() ci-dessus teste la valeur du format de sortie
demand. Si celui-ci rpond la valeur yaml alors le layout est dsactiv
pour ne pas dcorer le template de laction, et len-tte HTTP Content-
Type de la rponse est fix la valeur text/yaml.
Construire le template de gnration de la sortie YAML
Le template au format YAML nest gure plus complexe mettre en
uvre que les deux prcdents dans la mesure o Symfony fournit tous
les outils ncessaires la conversion de tableaux PHP en chanes de
caractres YAML.
Contenu du template apps/frontend/modules/api/templates/listSuccess.yaml.php
Si lon tente dappeler le service web avec un jeton invalide, une page
derreur 404 au format XML ou JSON sera leve. Or, le format YAML
nest pas un format support nativement par le framework. Il en rsulte alors
que ce dernier ne sait pas quel template rendre au client. La section suivante
explique comment dfinir les pages derreur 404 pour le format YAML en
tenant compte des environnements de dveloppement et de production.
Gnration des pages derreur 404 en fonction de lenvironnement
chaque fois quun nouveau format est cr, un template derreur
associ doit galement tre prpar. Symfony se servira en effet de ce
template pour rendre les pages derreur 404 ainsi que toutes les autres
exceptions leves. Or, le rendu dune erreur ou dune exception nest pas
le mme selon que lapplication est excute en environnement de dve-
loppement ou de production.
switch ($request->getRequestFormat())
{
case 'yaml':
$this->setLayout(false);
$this->getResponse()->setContentType('text/yaml');
break;
}
}
}
<?php foreach ($jobs as $url => $job): ?>
-
url: <?php echo $url ?>
<?php foreach ($job as $key => $value): ?>
<?php echo $key ?>: <?php echo sfYaml::dump($value) ?>
<?php endforeach; ?>
<?php endforeach; ?>
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
308
De ce fait, il est ncessaire de grer ces deux cas de figure en fournissant
deux templates distincts : config/error/exception.yaml.php pour le
dbogage et config/error/error.yaml.php pour la production. Sur-
charger les pages par dfaut derreur 404 ou dexception de Symfony
revient simplement crer un fichier dans le rpertoire config/error/.
Contenu du template daffichage des exceptions du format YAML dans le fichier
config/error/exception.yaml.php
Contenu du template daffichage des erreurs 404 du format YAML dans le fichier
config/error/error.yaml.php
La cration de ces deux templates derreur ne suffit pas pour pouvoir les
tester. Il manque en effet la mise en place dun layout ddi au format
YAML.
Contenu du layout propre au format YAML dans le fichier apps/frontend/templates/
layout.yaml.php
<?php echo sfYaml::dump(array(
'error' => array(
'code' => $code,
'message' => $message,
'debug' => array(
'name' => $name,
'message' => $message,
'traces' => $traces,
),
)), 4) ?>
<?php echo sfYaml::dump(array(
'error' => array(
'code' => $code,
'message' => $message,
))) ?>
<?php echo $sf_content ?>
Figure 151
Rcupration dune page derreur 404
au format YAML en environnement
de dveloppement
1
5


C
o
n
s
t
r
u
i
r
e

d
e
s

s
e
r
v
i
c
e
s

w
e
b
Groupe Eyrolles, 2008
309
Le service web est maintenant entirement fonctionnel et prt lemploi.
Cependant, il ne sera mis en production quaprs lui avoir fait subir quel-
ques sries de tests fonctionnels pour en valider le bon comportement.
crire des tests fonctionnels pour valider le
service web
Comme toujours, les tests fonctionnels ncessitent des jeux de donnes. La
premire tape consiste donc copier les fichiers de donnes initiales du
modle JobeetAffiliate depuis le rpertoire data/fixtures vers test/
fixtures. Une fois cette manipulation accomplie, le contenu du fichier
autognr apiActionsTest.php peut tre remplac par le code suivant :
Scnarios de tests fonctionnels du module api dans le fichier test/functional/
frontend/apiActionsTest.php
include(dirname(__FILE__).'/../../bootstrap/functional.php');
$browser = new JobeetTestFunctional(new sfBrowser());
$browser->loadData();
$browser->
info('1 - Web service security')->
info(' 1.1 - A token is needed to access the service')->
get('/api/foo/jobs.xml')->
with('response')->isStatusCode(404)->
info(' 1.2 - An inactive account cannot access the web
service')->
get('/api/symfony/jobs.xml')->
with('response')->isStatusCode(404)->
info('2 - The jobs returned are limited to the categories
configured for the affiliate')->
get('/api/sensio_labs/jobs.xml')->
with('request')->isFormat('xml')->
with('response')->checkElement('job', 32)->
info('3 - The web service supports the JSON format')->
get('/api/sensio_labs/jobs.json')->
with('request')->isFormat('json')->
with('response')->contains('"category": "Programming"')->
info('4 - The web service supports the YAML format')->
get('/api/sensio_labs/jobs.yaml')->
with('response')->begin()->
isHeader('content-type', 'text/yaml; charset=utf-8')->
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
310
Lensemble de cette suite de tests fonctionnels est suffisamment explicite
pour ne pas tre dtaille davantage dans la mesure o la plupart des
concepts ici prsents ont dj t expliqus au chapitre 9. Il faut cepen-
dant remarquer lutilisation de deux nouvelles mthodes, isFormat() et
contains(), qui contrlent respectivement le format de sortie attendu et
le contenu de la rponse lorsque celle-ci ne peut tre analyse laide du
DOM (et de slecteurs CSS 3). Enfin, la mthode isHeader() du test
numro 4 sassure que la valeur de len-tte de la rponse correspond
bien la chane text/yml; charset=utf-8.
Formulaire de cration dun compte
daffiliation
Le service web est enfin prt tre consomm par les sites Internet par-
tenaires. Cependant, lutilisation du service oblige laffili senregistrer
auprs de lapplication Jobeet afin de se voir dlivrer un jeton unique.
Cette opration est bien videmment gratuite et ralisable via un court
formulaire dinscription. La section qui suit dcrit en cinq tapes succes-
sives comment mettre en uvre et tester cette nouvelle fonctionnalit.
Dclarer la route ddie du formulaire dinscription
Comme toujours, lactivation dune nouvelle route ddie pour la ressource
est la premire tape mettre en uvre. Il sagit ici de dclarer une nouvelle
collection de routes Doctrine capable de grer un ensemble dactions nces-
saires au bon fonctionnement du mcanisme dinscription des affilis.
Dclaration de la route affiliate dans le fichier apps/frontend/config/routing.yml
Le code ci-dessus dfinit une collection de routes Doctrine dans laquelle
figure une nouvelle option de configuration : actions. Dans la mesure o
le processus dinscription na pas besoin des sept actions par dfaut dfinies
dans la route, loption actions force la route n'tre active que pour les
contains('category: Programming')->
end()
;
affiliate:
class: sfDoctrineRouteCollection
options:
model: JobeetAffiliate
actions: [new, create]
object_actions: { wait: get }
1
5


C
o
n
s
t
r
u
i
r
e

d
e
s

s
e
r
v
i
c
e
s

w
e
b
Groupe Eyrolles, 2008
311
actions create et new. La route additionnelle wait sera utilise pour
donner des feedbacks sur son compte laffili en attente de validation.
Gnrer un module damorage
La seconde tape traditionnelle dans ce processus consiste gnrer un
module ddi pour cette nouvelle fonctionnalit. Bien videmment, il
convient de faire usage de la commande doctrine:generate-module pour
accomplir cette tche.
Construction des templates
La tche doctrine:generate-module gnre les sept actions classiques
par dfaut ainsi que tous leurs templates correspondants. Tous les tem-
plates du rpertoire templates/ peuvent tre supprims lexception des
fichiers _form.php et newSuccess.php. Pour ces fichiers restants, leur
contenu respectif doit tre remplac par ceux qui suivent.
Contenu du fichier apps/frontend/modules/affiliate/templates/newSuccess.php
Contenu du fichier apps/frontend/modules/affiliate/templates/_form.php
$ php symfony doctrine:generate-module frontend affiliate
JobeetAffiliate --non-verbose-templates
<?php use_stylesheet('job.css') ?>
<h1>Become an Affiliate</h1>
<?php include_partial('form', array('form' => $form)) ?>
<?php include_stylesheets_for_form($form) ?>
<?php include_javascripts_for_form($form) ?>
<?php echo form_tag_for($form, 'affiliate') ?>
<table id="job_form">
<tfoot>
<tr>
<td colspan="2">
<input type="submit" value="Submit" />
</td>
</tr>
</tfoot>
<tbody>
<?php echo $form ?>
</tbody>
</table>
</form>
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
312
Le template waitSuccess.php peut son tour tre cr comme le pr-
sente le code ci-dessous.
Contenu du fichier apps/frontend/modules/affiliate/templates/waitSuccess.php
Les templates sont dsormais prts. Il ne leur manque plus que limpl-
mentation de leur action associe pour rendre le tout fonctionnel.
Implmenter les actions du module affiliate
La plupart du code autognr dans le fichier actions.class.php nest
pas utile au reste de lapplication. De ce fait, tout le code de ce fichier
peut tre retir lexception des mthodes executeNew(),
executeCreate(), et processForm(). LURL de redirection de laction
processForm() doit quant elle tre modifie.
Modification de lURL de redirection dans le fichier apps/frontend/modules/
affiliate/actions/actions.class.php
De son ct, laction wait reste triviale puisquelle nimplmente aucune
logique particulire ni ne passe quoi que ce soit sa vue correspondante.
Implmentation de la mthode executeWait() dans le fichier apps/frontend/
modules/affiliate/actions/actions.class.php
<h1>Your affiliate account has been created</h1>
<div style="padding: 20px">
Thank you!
You will receive an email with your affiliate token
as soon as your account will be activated.
</div>
Last, change the link in the footer to point to the affiliate
module:
// apps/frontend/templates/layout.php
<li class="last">
<a href="<?php echo url_for('@affiliate_new') ?>">Become an
affiliate</a>
</li>
$this->redirect($this->generateUrl('affiliate_wait',
$jobeet_affiliate));
public function executeWait(sfWebRequest $request)
{
}
1
5


C
o
n
s
t
r
u
i
r
e

d
e
s

s
e
r
v
i
c
e
s

w
e
b
Groupe Eyrolles, 2008
313
Le partenaire ne peut choisir son propre jeton ni ne peut activer son
compte immdiatement. Pour remplir ce besoin fonctionnel, il est nces-
saire de personnaliser le formulaire JobeetAffiliateForm.
Configuration du formulaire dinscription dans le fichier lib/form/doctrine/
JobeetAffiliateForm.class.php
Le framework de formulaires supporte les relations many-to-many avec
nimporte quelle colonne dune table. Par dfaut, ce type de relation est
rendu possible laide dune liste droulante multiple grce au widget
sfWidgetFormChoice. Le chapitre 10 a dailleurs montr comment le
rendu HTML final dun widget sfWidgetFormChoice peut tre modifi
au moyen de loption de configuration expanded.
De plus, comme les adresses e-mails et les URLs ont tendance tre
plus longues que la taille par dfaut du champ input gnr, les attributs
HTML appliquer par dfaut peuvent tre paramtrs grce la
mthode setAttribute() du widget.
class JobeetAffiliateForm extends BaseJobeetAffiliateForm
{
public function configure()
{
unset($this['is_active'], $this['token'],
$this['created_at'], $this['updated_at']);
$this->widgetSchema['jobeet_categories_list']
->setOption('expanded', true);
$this->widgetSchema['jobeet_categories_list']
->setLabel('Categories');
$this->validatorSchema['jobeet_categories_list']
->setOption('required', true);
$this->widgetSchema['url']->setLabel('Your website URL');
$this->widgetSchema['url']->setAttribute('size', 50);
$this->widgetSchema['email']->setAttribute('size', 50);
$this->validatorSchema['email'] = new
sfValidatorEmail(array('required' => true));
}
}
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
314
Tester fonctionnellement le formulaire
La dernire tape consiste comme toujours crire quelques tests fonc-
tionnels pour sassurer que la page se comporte correctement. Les tests
autognrs du module affiliate sont remplacer par la suite de tests
fonctionnels ci-aprs.
Scnarios de tests fonctionnels du module affiliate dans le fichier test/functional/
frontend/affiliateActionsTest.php
Figure 152 Rendu final du formulaire de cration de compte lAPI de Jobeet
include(dirname(__FILE__).'/../../bootstrap/functional.php');
$browser = new JobeetTestFunctional(new sfBrowser());
$browser->loadData();
$browser->
info('1 - An affiliate can create an account')->
get('/affiliate/new')->
click('Submit', array('jobeet_affiliate' => array(
'url' => 'http://www.example.com/',
'email' => 'foo@example.com',
'jobeet_categories_list' =>
array(Doctrine::getTable('JobeetCategory')
->findOneBySlug('programming')->getId()),
)))->
isRedirected()->
followRedirect()->
1
5


C
o
n
s
t
r
u
i
r
e

d
e
s

s
e
r
v
i
c
e
s

w
e
b
Groupe Eyrolles, 2008
315
Dvelopper linterface dadministration des
affilis
Le service web est maintenant actif et les dveloppeurs peuvent sinscrire
lAPI. Or, linscription lAPI de Jobeet ncessite une activation manuelle
par ladministrateur du site. Il convient donc de dvelopper une interface
dadministration propice la gestion des affilis dans lapplication bac-
kend. Cette dernire permettra ladministrateur dactiver, de dsactiver
ou bien encore de supprimer un compte dveloppeur.
Gnrer le module dadministration affiliate
La premire tape de conception de cette interface dadministration
consiste gnrer le squelette fonctionnel du module de gestion des affi-
lis. Bien videmment, il sagit de ne pas rinventer la roue et de
sappuyer nouveau sur le gnrateur de backoffice de Symfony.
Afin de faciliter laccs ce module aux administrateurs, le menu principal
de navigation doit accueillir un nouveau lien. Ce dernier est dfini dans le
layout de lapplication comme le montre lexemple de code suivant.
Contenu du fichier apps/backend/templates/layout.php
with('response')->checkElement('#content h1', 'Your affiliate
account has been created')->
info('2 - An affiliate must at least select one category')->
get('/affiliate/new')->
click('Submit', array('jobeet_affiliate' => array(
'url' => 'http://www.example.com/',
'email' => 'foo@example.com',
)))->
with('form')->isError('jobeet_categories_list')
;
$ php symfony doctrine:generate-admin backend JobeetAffiliate -
-module=affiliate
<li>
<a href="<?php echo url_for('@jobeet_affiliate') ?>">
Affiliates - <strong><?php echo
Doctrine::getTable('JobeetAffiliate')->countToBeActivated()
?></strong>
</a>
</li>
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
316
Ce nouveau lien fait appel une nouvelle mthode
countToBeActivated() de lobjet JobeetAffiliateTable qui se charge de
retourner le nombre de comptes affilis en attente de validation.
Limplmentation de cette mthode est dcrite dans le code ci-aprs.
Implmentation de la mthode countToBeActivated() dans la classe
JobeetAffiliateTable du fichier lib/model/doctrine/JobeetAffiliateTable.class.php
Paramtrer le module affiliate
Les seules vritables actions ncessaires dans ce module dadministration
correspondent lactivation ou la dsactivation des comptes. De ce fait, la
vue list peut tre simplifie grce la configuration suivante. Elle surcharge
les paramtres par dfaut de la configuration actuelle, et lui ajoute deux
liens supplmentaires pour activer ou dsactiver un compte partenaire.
Configuration du module de gestion des partenaires dans le fichier apps/backend/
modules/affiliate/config/generator.yml
class JobeetAffiliateTable extends Doctrine_Table
{
public function countToBeActivated()
{
$q = $this->createQuery('a')->where('a.is_active = ?', 0);
return $q->count();
}
// ...
}
config:
fields:
is_active: { label: Active? }
list:
title: Affiliate Management
display: [is_active, url, email, token]
sort: [is_active]
object_actions:
activate: ~
deactivate: ~
batch_actions:
activate: ~
deactivate: ~
actions: {}
filter:
display: [url, email, is_active]
1
5


C
o
n
s
t
r
u
i
r
e

d
e
s

s
e
r
v
i
c
e
s

w
e
b
Groupe Eyrolles, 2008
317
Implmenter les nouvelles fonctionnalits dadministration
Afin de rendre les administrateurs plus productifs et plus efficaces dans
leurs tches de gestion de lapplication, il semble judicieux de changer les
filtres par dfaut pour nafficher que les comptes affilis en attente de
validation. Cette opration est trs simple mettre en uvre puisquil
sagit de redfinir la mthode par dfaut getFilterDefaults() de la
classe autognre affiliateGeneratorConfiguration.
Redfinition de la mthode getFilterDefaults du fichier apps/backend/modules/
affiliate/lib/affiliateGeneratorConfiguration.class.php
Il ne reste finalement plus qu implmenter le code relatif aux actions
dactivation et de dsactivation de comptes dveloppeur. La vue list
dclare ces actions aussi bien de manire unitaire (sur chaque objet) que
sur un ensemble denregistrements slectionns dans le tableau. Le code
ci-dessous donne lintgralit des nouvelles mthodes implmentes
dans la classe dactions du module affiliate.
Implmentation des actions dactivation et de dsactivation de comptes partenaires
dans le fichier apps/backend/modules/affiliate/actions/actions.class.php
class affiliateGeneratorConfiguration extends
BaseAffiliateGeneratorConfiguration
{
public function getFilterDefaults()
{
return array('is_active' => '0');
}
}
class affiliateActions extends autoAffiliateActions
{
public function executeListActivate()
{
$this->getRoute()->getObject()->activate();
$this->redirect('@jobeet_affiliate');
}
public function executeListDeactivate()
{
$this->getRoute()->getObject()->deactivate();
$this->redirect('@jobeet_affiliate');
}
public function executeBatchActivate(sfWebRequest $request)
{
$q = Doctrine_Query::create()
->from('JobeetAffiliate a')
->whereIn('a.id', $request->getParameter('ids'));
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
318
Certains passages de ce code sont prsents en exergue et montrent luti-
lisation des mthodes activate() et deactivate() de lobjet
JobeetAffiliate qui respectivement activent et dsactivent le compte
affili. Ces deux nouvelles mthodes nexistent pas encore dans le projet
et doivent tre implmentes afin de rendre linterface de gestion enti-
rement fonctionnelle.
Implmentation des methods activate() et deactivate() de la classe JobeetAffiliate
dans le fichier lib/model/doctrine/JobeetAffiliate.class.php
$affiliates = $q->execute();
foreach ($affiliates as $affiliate)
{
$affiliate->activate();
}
$this->redirect('@jobeet_affiliate');
}
public function executeBatchDeactivate(sfWebRequest $request)
{
$q = Doctrine_Query::create()
->from('JobeetAffiliate a')
->whereIn('a.id', $request->getParameter('ids'));
$affiliates = $q->execute();
foreach ($affiliates as $affiliate)
{
$affiliate->deactivate();
}
$this->redirect('@jobeet_affiliate');
}
}
class JobeetAffiliate extends BaseJobeetAffiliate
{
public function activate()
{
$this->setIsActive(true);
return $this->save();
}
1
5


C
o
n
s
t
r
u
i
r
e

d
e
s

s
e
r
v
i
c
e
s

w
e
b
Groupe Eyrolles, 2008
319
Cest tout pour ce nouveau module. En seulement quelques minutes,
lapplication Jobeet dispose dun module dadministration pour grer les
comptes affilis qui seront ainsi capables de consommer le service web
mis leur disposition. Bien que ce module soit compltement fonc-
tionnel, il nen demeure pas moins quil lui manque une fonctionnalit
primordiale lors de lactivation dun compte affili : la notification par e-
mail. En effet, le systme nenvoie pour linstant aucune notification
lutilisateur pour linformer que son compte a t valid.
La section suivante clture ce chapitre en expliquant pas pas comment
intgrer un envoi de-mail trs simple laide du composant Zend_Mail
du Zend Framework.
public function deactivate()
{
$this->setIsActive(false);
return $this->save();
}
// ...
}
Figure 153 Rendu final de linterface dadministration des comptes affilis
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
320
Envoyer des e-mails avec Zend_Mail
Lorsquun administrateur active le compte dun affili, un e-mail devrait
automatiquement lui tre adress. Celui-ci lui confirmerait son inscrip-
tion en lui attribuant son jeton unique afin quil puisse consommer le
service web. Le langage PHP dispose dun grand nombre dexcellentes
librairies denvoi de-mails comme SwitfMailer, Zend_Mail ou bien
encore ezcMail.
Le chapitre suivant aura recours des composants du Zend Framework
pour faciliter la mise en place dun outil de recherche. Cest en effet dans
le but de conserver une certaine cohrence tout au long du projet que
seront utiliss ici les composants Zend_Mail et Zend_Search du Zend
Framework.
Installer et configurer le framework Zend
La librairie Zend_Mail est un composant part entire du framework
Zend. Pour rpondre aux besoins technologiques de ce chapitre et des
suivants, il nest pas utile dinstaller lintgralit du framework Zend.
Seuls quelques composants de celui-ci ont leur utilit pour le projet.
Heureusement, la plupart des paquets du Zend Framework sont suffi-
samment dcoupls, autonomes et indpendants les uns des autres pour
pouvoir tre rcuprs individuellement.
La premire tape consiste donc recrer la structure allge du Zend
Framework en commenant par crer un rpertoire Zend dans le rper-
toire lib/vendor/ du projet. Puis les dossiers et fichiers suivants du
Zend Framework doivent tre copis dans ce nouveau rpertoire :
Exception.php
Loader/
Loader.php
Mail/
Mail.php
Mime/
Mime.php
Search/
Le rpertoire Loader/ contient le composant de chargement des classes
du Zend Framework la demande. Les paquets Mail/ et Mime/ contien-
nent les classes ncessaires lenvoi de-mails avec ou sans pices jointes
tandis que le composant Search/ aura la charge dindexer et de retrouver
du contenu pour le moteur de recherche de Jobeet.
1
5


C
o
n
s
t
r
u
i
r
e

d
e
s

s
e
r
v
i
c
e
s

w
e
b
Groupe Eyrolles, 2008
321
Maintenant que les composants du Zend Framework sont intgrs au
projet, il ne reste plus qu indiquer Symfony comment il doit charger
les classes. Cest en ralit le composant Zend_Loader qui a la responsabi-
lit de charger dynamiquement les classes du framework Zend. Le code
ci-dessous prsente de quelle manire ce composant doit tre initialis
depuis la classe de configuration globale du projet.
Implmentation de la mthode registerZend() dans la classe ProjectConfiguration du
fichier config/ProjectConfiguration.class.php
La classe ProjectConfiguration est dote prsent dun nouvel attribut
boolen, protg et statique qui dtermine si les classes du Zend Fra-
mework ont t charges automatiquement ou pas. La mthode
registerZend(), quant elle, a pour mission dajouter le rpertoire lib/
vendor la liste des chemins dans lesquels PHP tentera de trouver et
dimporter les classes demandes, mais aussi et surtout de charger et
dinitialiser le chargement automatique de classes du framework Zend.
Implmenter lenvoi dun e-mail lactivation du
compte de laffili
La dernire tape consiste implmenter la logique mtier de lenvoi de
le-mail destination de laffili lors de lactivation de son compte par un
administrateur. Lenvoi du courrier lectronique est laiss la charge du
contrleur, cest pour cette raison quil trouve naturellement sa place
dans le corps de la mthode executeListActivate().
class ProjectConfiguration extends sfProjectConfiguration
{
static protected $zendLoaded = false;
static public function registerZend()
{
if (self::$zendLoaded)
{
return;
}
set_include_path(sfConfig::get('sf_lib_dir')
.'/vendor'.PATH_SEPARATOR.get_include_path());
require_once sfConfig::get('sf_lib_dir')
.'/vendor/Zend/Loader.php';
Zend_Loader::registerAutoload();
self::$zendLoaded = true;
}
// ...
}
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
322
Implmentation de lenvoi de-mails dans la mthode executeListActivate() du fichier
apps/backend/modules/affiliate/actions/actions.class.php
Le principe de fonctionnement du code mis en avant est simple. Lappel
la mthode statique registerZend() permet dimporter et dinitialiser
la classe de chargement automatique des composants du framework
Zend. Une fois cette opration accomplie, un nouvel objet Zend_Mail est
instanci afin de prparer le-mail qui est finalement envoy son desti-
nataire lappel de la mthode send(). Pour que lenvoi de le-mail fonc-
tionne correctement, ladresse e-mail fictive jobeet@example.com doit
tre remplace par une adresse relle.
Bien sr, il ne sagit ici que dun exemple minimaliste de gnration de-
mail avec cette librairie. Le composant Zend_Mail recle bien dautres
atouts comme lenvoi de-mails au format HTML, le support des pices
jointes, la manipulation des messages via les protocoles SMTP et POP3
La documentation de Zend_Mail ainsi que de nombreux exemples de mise
en pratique sont prsents sur le site officiel du framework Zend.
class affiliateActions extends autoAffiliateActions
{
public function executeListActivate()
{
$affiliate = $this->getRoute()->getObject();
$affiliate->activate();
// send an email to the affiliate
ProjectConfiguration::registerZend();
$mail = new Zend_Mail();
$mail->setBodyText(<<<EOF
Your Jobeet affiliate account has been activated.
Your token is {$affiliate->getToken()}.
The Jobeet Bot.
EOF
);
$mail->setFrom('jobeet@example.com', 'Jobeet Bot');
$mail->addTo($affiliate->getEmail());
$mail->setSubject('Jobeet affiliate token');
$mail->send();
$this->redirect('@jobeet_affiliate');
}
// ...
}
Bhttp://framework.zend.com/
1
5


C
o
n
s
t
r
u
i
r
e

d
e
s

s
e
r
v
i
c
e
s

w
e
b
Groupe Eyrolles, 2008
323
En rsum
Grce larchitecture REST de Symfony, limplmentation de services web
dans les projets se voit grandement simplifie. Bien que le code crit dans ce
chapitre corresponde uniquement un service web accessible en lecture,
vous disposez prsent de toutes les connaissances suffisantes pour dve-
lopper votre propre service web interrogeable en lecture et en criture.
Limplmentation du formulaire de cration dun compte affili dans les
applications frontend et backend a t particulirement facilite dans la
mesure o vous tes maintenant familier avec tout le processus dajout de
nouvelles fonctionnalits un projet Symfony.
Le chapitre suivant implmente la toute dernire fonctionnalit majeure
du site Internet de Jobeet le moteur de recherche dveloppe laide
du composant Zend_Search_Lucene du framework Zend
Groupe Eyrolles, 2008
chapitre 16
Groupe Eyrolles, 2008
Dployer
un moteur de recherche
Lune des fonctionnalits les plus difficiles mettre en uvre
dans une application web dynamique est le moteur de
recherche, dans la mesure o il doit gnralement tre capable
de rechercher dans lintgralit du contenu. Quelques outils
dindexation de contenu open source existent dont le plus
connu, Lucene, dispose dun composant crit en PHP pour le
framework Zend. Cest celui-ci qui sera implment pour
Jobeet dans la suite de ce chapitre.
MOTS-CLS :
BIndexation avec
Zend_Search_Lucene
BTransactions SQL avec Doctrine
BTests unitaires
BTches automatises
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
326
Lapplication Jobeet dispose de tout lquipement ncessaire pour assurer
la cration de nouveaux contenus, ainsi que la diffusion de ces derniers
travers Internet laide des flux ATOM et de son service web. Nan-
moins, il manque une fonctionnalit essentielle au site Internet : un
moteur de recherche. Ce moteur de recherche doit en effet permettre aux
utilisateurs de faciliter leur recherche doffres demploi en saisissant des
mots-cls pertinents. Lobjectif de ce chapitre est de mettre en place pas
pas un moteur de recherche bas sur la solution dindexation Lucene.
Dcouverte de la librairie
Zend_Search_Lucene
Rappels historiques au sujet de Symfony
Avant de plonger tte la premire dans le dveloppement du moteur de
recherche, un bref rappel de lhistorique de Symfony semble opportun.
Lquipe du projet Symfony sefforce depuis toujours de prner les
meilleures pratiques de dveloppement, telles que les tests et le refacto-
ring du code, et par la mme occasion de les mettre en uvre au sein du
framework lui-mme. Lune des principales devises du framework est
par exemple de ne pas rinventer la roue (do not reinvent the wheel), et
cest pour cette raison que Symfony est n il y a quatre ans en embar-
quant deux projets Open Source matures : Mojavi et Propel.
Aujourdhui encore, lorsquil y a un nouveau problme rsoudre,
lquipe de Symfony se pose toujours la question de savoir sil existe dj
ou non une quelconque implmentation rcuprable qui rpond au
besoin, avant de coder quoique ce soit depuis zro.
Prsentation de Zend Lucene
Lobjectif de ce chapitre est dajouter un moteur de recherche lapplica-
tion Jobeet, et heureusement le framework Zend fournit une excellente
librairie nomme Zend_Search_Lucene, qui est un portage en PHP du
clbre projet Open Source Java Lucene. Au lieu de crer un nouveau
moteur de recherche pour Jobeet, ce qui est une tche particulirement
complexe et chronophage, il sera plus pertinent de rutiliser le compo-
sant Zend Lucene. La documentation de Zend_Search_Lucene pr-
sente sur le site officiel du framework Zend dcrit cette librairie de la
manire suivante :
1
6

p
l
o
y
e
r

u
n

m
o
t
e
u
r

d
e

r
e
c
h
e
r
c
h
e
Groupe Eyrolles, 2008
327
Zend Lucene est un moteur de recherche de contenus principalement
textuels crit entirement en PHP 5. Comme il sauvegarde son index sur
le systme de fichiers local et quil ne requiert aucun serveur de base de
donnes, il peut ajouter des capacits de recherche la plupart des sites
Internet conus en PHP 5. Zend_Search_Lucene supporte les fonction-
nalits suivantes :
recherche trie par pertinence : les meilleurs rsultats sortent en
premier ;
diffrents types de requte pour interroger lindex : chanes de carac-
tres, boolen, astrisque, proximit, intervalles et bien dautres ;
recherche sur un champ spcifique (par exemple : titre, auteur, con-
tenus).
Ce chapitre nest pas un tutoriel dutilisation de la librairie Zend Lucene,
mais seulement un exemple dintgration de celle-ci dans lapplication
Jobeet, ou plus gnralement, un exemple dintgration de composants
tierces parties lintrieur dun projet Symfony. Pour en savoir plus au
sujet de cette technologie, la documentation de Zend Lucene est dispo-
nible sur le site officiel du Zend Framework. La librairie Zend Lucene a
dj t installe dans Jobeet au chapitre prcdent. Elle se trouve dans
le rpertoire Search/ du framework Zend.
Indexer le contenu de Jobeet
Le moteur de recherche de Jobeet doit tre capable de retourner toutes les
offres qui correspondent aux mots-cls saisis par lutilisateur. Cependant,
avant de pouvoir rechercher une quelconque information, il est ncessaire
quun index des annonces soit construit en amont. Ce dernier sera stock
sur le systme de fichier local dans le rpertoire data/ du projet.
Crer et rcuprer le fichier de lindex
Zend Lucene fournit deux mthodes pour retrouver un index selon quil
existe dj ou pas. Dans un premier temps, il convient de crer une
mthode helper dans la classe JobeetJobTable qui retourne un index
existant ou bien en gnre un nouveau la vole.
Dclaration des mthodes de cration et de rcupration de lindex des offres
demploi dans le fichier lib/model/doctrine/JobeetJobTable.class.php
static public function getLuceneIndex()
{
ProjectConfiguration::registerZend();
Bhttp://framework.zend.com/
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
328
Mettre jour lindex la srialisation dune offre
Chaque fois quune offre est cre, dite ou bien supprime, lindex de
celle-ci doit tre mis jour afin de rester cohrent. Il est de surcrot pri-
mordial de ne pas indexer les offres expires ou non publies. Le
meilleur endroit pour rgnrer lindex chaque fois quune offre est
srialise en base de donnes est bien videmment la mthode save() de
lobjet. Cette dernire se redfinie de la manire suivante :
Redfinition de la mthode save() de lobjet JobeetJob dans le fichier lib/model/
doctrine/JobeetJob.class.php
La nouvelle mthode updateLuceneIndex() de cette mme classe a pour
rle de gnrer lindex Lucene de cette offre au moment o elle est sria-
lise dans la base de donnes. Le code ci-dessous illustre toute limpl-
mentation de cette mthode.
Implmentation de la mthode updateLuceneIndex() dans le fichier lib/model/
doctrine/JobeetJob.class.php
if (file_exists($index = self::getLuceneIndexFile()))
{
return Zend_Search_Lucene::open($index);
}
else
{
return Zend_Search_Lucene::create($index);
}
}
static public function getLuceneIndexFile()
{
return sfConfig::get('sf_data_dir').'/
job.'.sfConfig::get('sf_environment').'.index';
}
public function save(Doctrine_Connection $conn = null)
{
// ...
$ret = parent::save($conn);
$this->updateLuceneIndex();
return $ret;
}
public function updateLuceneIndex()
{
$index = $this->getTable()->getLuceneIndex();
1
6

p
l
o
y
e
r

u
n

m
o
t
e
u
r

d
e

r
e
c
h
e
r
c
h
e
Groupe Eyrolles, 2008
329
La cration de lindex se droule en trois tapes successives :
1 la premire consiste supprimer lindex existant de loffre si ce der-
nier existe dj car Zend Lucene est incapable de mettre jour un
index existant ;
2 puis le code teste si loffre en cours est expire ou bien si elle nest pas
encore publie. Si cest le cas, il est aucunement ncessaire dindexer
les donnes afin de ne pas risquer de les ressortir lors dune future
recherche ;
3 ensuite, un nouvel objet Zend_Search_Lucene_Document est cr. La
cl primaire de loffre est ajoute lindex mais nest pas indexe afin
quelle serve de rfrence de loffre pour plus tard au moment de la
recherche. En revanche, les valeurs des champs position, company,
location et description de lobjet sont indexes mais ne sont pas
stockes dans lindex : ce seront les objets rels qui seront utiliss
// remove an existing entry
if ($hit = $index->find('pk:'.$this->getId()))
{
$index->delete($hit->id);
}
// don't index expired and non-activated jobs
if ($this->isExpired() || !$this->getIsActivated())
{
return;
}
$doc = new Zend_Search_Lucene_Document();
// store job primary key URL to identify it in the search
results
$doc->addField(Zend_Search_Lucene_Field::UnIndexed('pk',
$this->getId()));
// index job fields
$doc->addField(Zend_Search_Lucene_Field::UnStored('position',
$this->getPosition(), 'utf-8'));
$doc->addField(Zend_Search_Lucene_Field::UnStored('company',
$this->getCompany(), 'utf-8'));
$doc->addField(Zend_Search_Lucene_Field::UnStored('location',
$this->getLocation(), 'utf-8'));
$doc-
>addField(Zend_Search_Lucene_Field::UnStored('description',
$this->getDescription(), 'utf-8'));
// add job to the index
$index->addDocument($doc);
$index->commit();
}
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
330
pour afficher les rsultats. Pour finir, le document est ajout lindex
et ce dernier est valid lappel de la mthode commit().
Scuriser la srialisation dune offre laide dune
transaction Doctrine
Que se passe-t-il si lindexation dune offre choue ou bien si celle-ci nest
pas correctement sauvegarde dans la base de donnes ? Doctrine comme
Zend Lucene lancent une erreur si ce genre de cas venait se produire.
Cependant, dans certaines circonstances, loffre peut avoir t correcte-
ment sauvegarde en base de donnes bien que son index correspondant
nait pas t recr. Afin dviter cela, la meilleure solution est dencapsuler
les deux procdures de mise jour de lobjet lintrieur dune transaction,
et de lannuler ( rollback ) en cas dinterception dune exception. La
transaction permet ainsi de garantir lintgrit de lobjet. En effet, si une
seule manuvre choue, cest tout le processus de mis jour de lobjet qui
est interrompu et rinitialis son tat prcdent.
Implmentation dune transaction Doctrine pour assurer la cohrence entre lindex et
la base de donnes dans le fichier lib/model/doctrine/JobeetJob.class.php
public function save(Doctrine_Connection $conn = null)
{
// ...
$conn = $conn ? $conn : $this->getTable()->getConnection();
$conn->beginTransaction();
try
{
$ret = parent::save($conn);
$this->updateLuceneIndex();
$conn->commit();
return $ret;
}
catch (Exception $e)
{
$conn->rollBack();
throw $e;
}
}
1
6

p
l
o
y
e
r

u
n

m
o
t
e
u
r

d
e

r
e
c
h
e
r
c
h
e
Groupe Eyrolles, 2008
331
Effacer lindex lors de la suppression dune offre
Lorsquune offre est supprime du site Internet (manuellement ou via la
tche automatique de nettoyage des offres primes), son index doit lui
aussi tre retir du systme de fichier afin de garantir la cohrence des
informations de Jobeet. Pour automatiser cette opration, la manire de
procder qui semble la plus pertinente est bien videmment de sur-
charger limplmentation de la mthode delete() de la classe JobeetJob
comme le montre le code ci-dessous.
Redfinition de la mthode delete() des objets JobeetJob dans la classe lib/model/
doctrine/JobeetJob.class.php
Manipuler lindex des offres demploi
Rgnrer tout lindex des offres demploi
Maintenant que la gnration et la suppression de lindex est en place, il ne
reste plus qu recharger les donnes initiales de Jobeet afin de reconstruire
lindex de chaque objet. Pour ce faire, il suffit dexcuter la tche automatique
doctrine:data-load utilise plusieurs reprises au cours de cet ouvrage.
La commande est excute avec loption --env=dev dans la mesure o
lindex est dpendant de lenvironnement et que lenvironnement par
dfaut de la ligne de commande est cli.
Implmenter la recherche dinformations pour Jobeet
La prochaine tape consiste mettre en place la route, la logique mtier
et les vues qui rendent possible la recherche doffres demploi lutilisa-
teur. Limplmentation dune telle fonctionnalit nest quune question
de quelques minutes. Pour commencer, il faut bien videmment dclarer
une nouvelle route dans le fichier de configuration routing.yml de
lapplication frontend.
public function delete(Doctrine_Connection $conn = null)
{
$index = $this->getTable()->getLuceneIndex();
if ($hit = $index->find('pk:'.$this->getId()))
{
$index->delete($hit->id);
}
return parent::delete($conn);
}
$ php symfony doctrine:data-load --env=dev
ENVIRONNEMENTS UNIX
Accs en criture sur lindex
Les utilisateurs de systmes dexploitation bass sur
Unix doivent sassurer que le fichier de lindex est
accessible en criture la fois pour lenvironnement
dexcution en ligne de commande et pour celui
pour le web. Il convient donc de changer les droits
du rpertoire data/ et de vrifier que lutilisateur
du serveur web et de la ligne de commande ont
tous deux accs ce rpertoire en criture.
CONFIGURATION PHP Support des fichiers ZIP
Des avertissements PHP au niveau de la classe
ZipArchive peuvent tre levs, ce qui signifie
alors que lextension zip nest pas compile et
installe avec PHP. Ceci est un bogue connu de la
classe Zend_Loader du framework Zend.
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
332
Ajout de la route job_search au fichier apps/frontend/config/routing.yml
la suite de cela, il convient dimplmenter le contrleur search du
module job comme le montre le code ci-dessous.
Implmentation de la mthode executeSearch() dans le fichier apps/frontend/
modules/job/actions/actions.class.php
Le corps de la mthode executeSearch() est trs basique puisquil ne
ralise que deux actions principales. Tout dabord, une condition vrifie
si lobjet correspondant la requte contient un paramtre query et
stocke sa valeur dans la variable $query. Si ce paramtre nexiste pas ou
bien si sa valeur est nulle, alors la requte est transmise laction index
du mme module ; dans le cas contraire, cest la nouvelle mthode
getForLuceneQuery() qui prend en charge la rcupration des offres cor-
respondantes aux mots-cls passs en paramtre. Limplmentation de
cette dernire se trouve juste aprs.
Implmentation de la mthode getForLuceneIndex() dans le fichier lib/model/
doctrine/JobeetJobTable.class.php
job_search:
url: /search
param: { module: job, action: search }
class jobActions extends sfActions
{
public function executeSearch(sfWebRequest $request)
{
if (!$query = $request->getParameter('query'))
{
return $this->forward('job', 'index');
}
$this->jobs = Doctrine::getTable('JobeetJob')
->getForLuceneQuery($query);
}
// ...
}
public function getForLuceneQuery($query)
{
$hits = $this->getLuceneIndex()->find($query);
$pks = array();
foreach ($hits as $hit)
{
$pks[] = $hit->pk;
}
1
6

p
l
o
y
e
r

u
n

m
o
t
e
u
r

d
e

r
e
c
h
e
r
c
h
e
Groupe Eyrolles, 2008
333
La comprhension du code de cette mthode ne pose pas de relle diffi-
cult. La premire instruction fait appel la mthode find() de lobjet
Zend_Search_Lucene reprsentant lindex du site Internet. La mthode
find() retourne une collection dobjets qui rpondent aux mots-cls de
lutilisateur dans lindex. Puis, la structure conditionnelle foreach() par-
court cette collection afin den rcuprer les valeurs des cls primaires des
offres demploi enregistres dans lindex. Une requte Doctrine est enfin
construite et excute dans le but de retourner une collection des vingt
premiers objets JobeetJob valides qui satisfont la recherche de lutilisateur.
Quant au template searchSuccess.php, il ne tient quen quelques lignes
de PHP grce tous les remaniements et factorisations de code raliss
jusqu prsent.
Contenu du template apps/frontend/modules/job/templates/searchSuccess.php
Enfin, il ne reste plus qu ajouter le moteur de recherche sur chaque
page du site afin que tout soit dfinitivement oprationnel. Pour ce faire,
il suffit dajouter le code HTML dun formulaire basique dans le layout
de lapplication frontend comme le montre le code ci-aprs.
Intgration du moteur de recherche dans le fichier apps/frontend/templates/
layout.php
if (empty($pks))
{
return array();
}
$q = $this->createQuery('j')
->whereIn('j.id', $pks)
->limit(20);

return $this->addActiveJobsQuery($q)->execute();
}
<?php use_stylesheet('jobs.css') ?>
<div id="jobs">
<?php include_partial('job/list', array('jobs' => $jobs)) ?>
</div>
<h2>Ask for a job</h2>
<form action="<?php echo url_for('@job_search') ?>"
method="get">
<input type="text" name="query" value="<?php echo
$sf_request->getParameter('query') ?>" id="search_keywords" />
<input type="submit" value="search" />
<div class="help">
Enter some keywords (city, country, position, ...)
</div>
</form>
REMARQUE Zend Lucene : requtes
supportes pour interroger lindex
La librairie Zend Lucene dfinit un langage de
requte trs riche qui supporte notamment les
oprations boolennes, joker, de proximit et bien
dautres encore. Lensemble de ces fonctionnalits
ainsi que lutilisation de lAPI sont largement
documents dans le manuel dutilisation de Zend
Lucene sur le site officiel du framework Zend
ladresse http://framework.zend.com/
manual/en/zend.search.lucene.html
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
334
Tester la mthode getForLuceneQuery()
de JobeetJob
Afin de valider la bonne intgration de Zend Lucene dans Jobeet ainsi
que le fonctionnement gnral du moteur de recherche, il convient
dcrire quelques sries de tests fonctionnels. Mais quels genres de tests
unitaires est-il possible et judicieux de mettre en uvre ? Bien videm-
ment, il est inutile de tester la libraire Zend Lucene elle-mme dans la
mesure o cela a dj t ralis par lquipe de dveloppement du Zend
Framework. En fait, il sagit de vrifier lintgration de celle-ci au sein de
la classe JobeetJob.
Lidal est donc de tester la mthode getForLuceneQuery() qui fait le
pont entre la libraire Zend_Search_Lucene et le moteur de recherche de
Jobeet. Le code ci-dessous prsente les suites de tests unitaires de la
mthode getForLuceneQuery() ajouter au bas du fichier
JobeetJobTest.php en noubliant pas de mettre jour le compteur de
tests planifis la valeur 7.
Intgration des tests unitaires du moteur de recherche dans le fichier test/lib/
model/JobeetJobTest.php
Lobjectif de ces trois sries de tests unitaires est de contrler quune
offre demploi non publie ou bien supprime napparat pas dans les
$t->comment('->getForLuceneQuery()');
$job = create_job(array('position' => 'foobar', 'is_activated'
=> false));
$job->save();
$jobs = Doctrine::getTable('JobeetJob')
->getForLuceneQuery('position:foobar');
$t->is(count($jobs), 0, '::getForLuceneQuery() does not return
non activated jobs');
$job = create_job(array('position' => 'foobar', 'is_activated'
=> true));
$job->save();
$jobs = Doctrine::getTable('JobeetJob')
->getForLuceneQuery('position:foobar');
$t->is(count($jobs), 1, '::getForLuceneQuery() returns jobs
matching the criteria');
$t->is($jobs[0]->getId(), $job->getId(), '::getForLuceneQuery()
returns jobs matching the criteria');
$job->delete();
$jobs = Doctrine::getTable('JobeetJob')
->getForLuceneQuery('position:foobar');
$t->is(count($jobs), 0, '::getForLuceneQuery() does not return
deleted jobs');
1
6

p
l
o
y
e
r

u
n

m
o
t
e
u
r

d
e

r
e
c
h
e
r
c
h
e
Groupe Eyrolles, 2008
335
rsultats de recherche, alors quinversement, les offres qui correspondent
aux critres de recherche apparaissent dans ces rsultats.
Nettoyer rgulirement lindex des offres
primes
Avec le temps, il arrive que certaines offres atteignent leur date dexpira-
tion et ne soient pas renouveles pour une nouvelle priode de trente
jours. Leurs informations restent alors conserves dans lindex puisque
ces offres sont toujours prsentes dans la base de donnes. De toute vi-
dence, il semble pertinent de nettoyer lindex rgulirement afin de sup-
primer toute information relative une offre prime. Cela aura pour
effet immdiat de librer de la place dans lindex, ce qui le rendra plus
performant par la mme occasion.
La manire idale et efficace pour nettoyer lindex priodiquement est de
crer et dexcuter une commande Symfony laide dune tche plani-
fie. Or, lapplication Jobeet dispose depuis le chapitre 11 dune tche
automatique de nettoyage des offres primes de la base de donnes.
Lobjectif est donc de profiter de lexistence de cette tche pour lenrichir
en lui intgrant le processus de mise jour de lindex.
Implmentation du nettoyage de lindex dans la tche JobeetJobCleanup du fichier
lib/task/JobeetCleanupTask.class.php
protected function execute($arguments = array(), $options =
array())
{
$databaseManager = new sfDatabaseManager($this
->configuration);
// cleanup Lucene index
$index = Doctrine::getTable('JobeetJob')->getLuceneIndex();
$q = Doctrine_Query::create()
->from('JobeetJob j')
->where('j.expires_at < ?', date('Y-m-d'));
$jobs = $q->execute();
foreach ($jobs as $job)
{
if ($hit = $index->find('pk:'.$job->getId()))
{
$hit->delete();
}
}
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
336
Le code mis en avant ici correspond la logique ncessaire pour retirer de
lindex les informations obsoltes. Pour commencer, la requte Doctrine
rcupre la collection dobjets JobeetJob ayant dj expir. Puis, pour
chaque offre demploi, la mthode find() de lindex tente de retrouver les
informations indexes et rfrences la valeur de la cl primaire de
lannonce, et stocke lobjet rsultant dans la variable $hit en cas de succs.
Ds lors, lappel la mthode delete() sur cette dernire suffit effacer
les donnes indexes de loffre courante dans lindex. Enfin, la mthode
optimize() se charge, comme son nom lindique, doptimiser tout lindex
lorsque lensemble des donnes obsoltes a t radiqu.
$index->optimize();
$this->logSection('lucene', 'Cleaned up and optimized the job
index');
// Remove stale jobs
$nb = Doctrine::getTable('JobeetJob')
->cleanup($options['days']);
$this->logSection('doctrine', sprintf('Removed %d stale
jobs', $nb));
}
1
6

p
l
o
y
e
r

u
n

m
o
t
e
u
r

d
e

r
e
c
h
e
r
c
h
e
Groupe Eyrolles, 2008
337
En rsum
Ce seizime chapitre a t loccasion dimplmenter un moteur de
recherche compltement fonctionnel en moins dune heure. Il faut tou-
jours avoir lesprit de vrifier sil existe dj ou non des solutions aux
problmes que lon cherche rsoudre sur un nouveau projet. Il sagit
dabord de vrifier si la solution technique nest pas dj intgre native-
ment dans le framework. Puis, si ce nest pas le cas, le bon rflexe
adopter consiste parcourir le dpt des plug-ins de Symfony, lafft
dun ventuel plug-in existant. Enfin, si la solution technique ne se
trouve pas en ces lieux, il ne faut pas hsiter aller la chercher dans des
ressources en ligne comme les bibliothques Open Source telles que le
Zend Framework ou les composants ezComponents.
Le chapitre suivant aborde les notions dAjax et de JavaScript non
intrusif afin damliorer la ractivit du moteur de recherche en mettant
jour les rsultats en temps rel chaque fois que lutilisateur saisit de
nouvelles lettres sur son clavier dans le formulaire de recherche
Groupe Eyrolles, 2008
chapitre 17
Groupe Eyrolles, 2008
Dynamiser linterface
utilisateur avec Ajax
Avec larrive des navigateurs web modernes il y a quelques
annes, de nombreuses applications web se sont dotes
dinterfaces riches (RIA) et dynamiques reposant sur
la technologie JavaScript. Lessor des frameworks JavaScript
tels que Prototype, jQuery ou MooTools, ont par la mme
occasion largement particip au dveloppement de ces
interfaces et plus particulirement ladoption de lAjax.
Symfony supporte nativement les requtes HTTP asynchrones
(Ajax) et en simplifie grandement la gestion.
MOTS-CLS :
BJavaScript, Ajax et jQuery
BTests fonctionnels
BCouche contrleur
S
y
m
f
o
n
y


M
i
e
u
x

d

v
e
l
o
p
p
e
r

e
n

P
H
P

a
v
e
c

S
y
m
f
o
n
y

1
.
2

e
t

D
o
c
t
r
i
n
e
Groupe Eyrolles, 2008
340
Le chapitre prcdent a t loccasion dinstaller un moteur de recherche
puissant et fonctionnel pour Jobeet laide de la librairie Zend Lucene du
framework Zend. Lobjectif de ces proc