Vous êtes sur la page 1sur 129

Cration dun blog avec Symfony2

Introduction
Ce tutoriel va vous guider dans le processus de cration dun blog complet avec Symfony2.
La distribution standard du framework sera utilise, qui inclut les principaux composants
ncessaires la cration de vos propres sites web. Le tutoriel est dcoup en plusieurs parties,
qui couvrent chacune des aspects diffrents de Symfony2 et de ses composants. Il est prvu
pour tre utilis de la mme manire que Jobeet pour Symfony 1.

Chapitres du tutoriel

[Partie 1] - Configuration de Symfony2 et utilisation des templates

[Partie 2] - Page de contact : validateurs, formulaires et envoi demails.

[Partie 3] - Le modle darticle : utilisation de Doctrine 2 et des donnes factices

[Partie 4] - Le modle de commentaires : ajouter des commentaires, dpts Doctrine 2


et migrations.

[Partie 5] - Personnalisation de la vue : extensions Twig, barre latrale et Assetic

[Partie 6] - Les tests unitaires et fonctionnels avec PHPUnit

Contenu
Le but de ce tutoriel est de couvrir les tches que vous allez rgulirement tre amen
raliser lors de la cration dun site web avec Symfony2.
1. Les bundles
2. Les contrleurs
3. Les Template (avec Twig)
4. Le modle - Doctrine 2
5. Les migrations
6. Les donnes factices
7. Les validateurs
8. Les formulaires

9. Le routage
10. Gestion des fichiers externes
11. Les emails
12. les environnements
13. Personnalisation des pages derreur
14. La scurit
15. Lutilisateur et les sessions
16. Gnration de CRUD
17. Le cache
18. Les tests
19. Le dploiement
Symfony2 est fortement personnalisable et propose diffrentes manires de raliser un mme
tche. On peut citer par exemple le format de configuration qui peut tre le YAML, le XML,
le PHP ou les annotations, ainsi que la cration de template en PHP ou laide de Twig. Par
souci de simplicit, nous utiliserons le format YAML et les annotations pour la configuration,
et Twig pour les templates. Le livre Symfony propose de nombreux exemples de lutilisation
des autres mthodes. Si dautres personnes souhaitent contribuer complter des mthodes
alternatives, nhsitez pas faire un fork du projet sur Github puis proposer un pull :)

[Partie 1] - Configuration de Symfony2 et utilisation des templates

Je propose galement des formations en petits groupes sur 2 3 jours, plus dinfos sur la page
ddie. Nhsitez pas me contacter (06.62.28.01.87 ou clement [@] keiruaprod.fr) pour en
discuter !
Introduction

Ce chapitre va couvrir les premires tapes de la cration dun site web avec Symfony2. Nous
allons tlcharger et configurer la distribution standard de Symfony2, crer un bundle pour le
Blog et construire le template HTML principal. A la fin de ce chapitre, vous aurez configur
un site avec Symfony2 qui sera disponible une adresse locale, par exemple
http://symblog.dev/. Le site web va contenir la structure HTML principale du blog, ainsi
que du contenu factice.
Les thmes suivants vont tre abords au cours de ce chapitre:
1. Mise en place dune application Symfony2
2. Configuration dun domaine de dveloppement
3. Les Bundles Symfony2
4. Les routes
5. Les contrleurs
6. Les templates avec Twig
Tlchargement et installation

Comme tabli ci-dessus, nous allons utiliser la distribution standard de Symfony2 (Symfony2
Standard Distribution). Cette distribution est livre avec les librairies du moteur de Symfony2,
ainsi quavec les principaux bundles ncessaires au dveloppement dun site web. Vous
pouvez tlcharger larchive correspondante sur le site de Symfony2. Comme je ne souhaite
pas rpter le contenu de lexcellente documentation propose dans le livre sur Symfony2,
vous pouvez consulter le chapitre Installation et configuration de Symfony2 pour des besoins
spcifiques. Cela vous permettra de choisir quelle archive choisir, comment installer les
vendors requis, et enfin comment assigner les permissions aux rpertoires qui le ncessitent.
Attention
Il est important daccorder un soin particulier la section Mise en place des permissions du
chapitre dinstallation. Cela dtaille les diverses manires daccorder des permissions aux
rpertoires app/cache et app/logs afin que les utilisateurs du serveur web aient les droits
daccs en criture.
Cration dun domaine de dveloppement

Pour ce tutorial, nous allons utiliser le domaine local http://symblog.dev/, nanmoins vous
pouvez utiliser celui de votre choix. Ces instructions sont spcifiques Apache et supposent
quil est dj install et fonctionne sur votre machine. Si vous tes dj laise avec la mise
en place de domaines locaux ou bien si vous utilisez un serveur web diffrent tel que nginx
vous pouvez sauter cette section.
Note
Ces tapes ont t ralises sur la distribution Linux Fedora. Il est donc possible que les
chemins daccs puissent diffrer si vous utilisez un autre systme dexploitation.
Commenons par crer un hte virtuel avec Apache. Dans le fichier de configuration
dApache, ajoutez les paramtres suivants en prenant bien soin de changer les chemins de
DocumentRoot et Directory correctement. Le chemin et le nom de votre fichier de
configuration Apache peut fortement varier selon votre systme dexploitation. Avec Fedora,
il est situ dans /etc/httpd/conf/httpd.conf. Vous devrez diter ce fichier avec les
privilges sudo .
# /etc/httpd/conf/httpd.conf
NameVirtualHost 127.0.0.1
<VirtualHost 127.0.0.1>
ServerName symblog.dev
DocumentRoot "/var/www/html/symblog.dev/web"
DirectoryIndex app.php
<Directory "/var/www/html/symblog.dev/web">
AllowOverride All
Allow from All
</Directory>
</VirtualHost>

Ensuite ajoutez un nouveau domaine la fin du fichier hte situ dans /etc/hosts. A
nouveau, vous devrez diter ce fichier avec les privilges sudo .
# /etc/hosts
127.0.0.1

symblog.dev

Finalement, noubliez pas de redmarrer le service Apache, afin de mettre jour les
paramtres de configuration avec les informations que nous venons de spcifier.
$ sudo service httpd restart

Tip
Si vous passez votre temps crer des domaines virtuels, vous pouvez simplifier ce processus
en utilisant des htes virtuels dynamiques.
Vous devriez dsormais pouvoir visiter lurl http://symblog.dev/app_dev.php/.

Sil sagit de votre premire visite sur la page daccueil de Symfony2, prenez un peu de temps
pour regarder les pages de dmonstration. Sur chacune dentre elles vous pourrez trouver les
bouts de code utiliss sous le capot.
Note
Vous pouvez galement remarquer une barre doutils au bas de lcran daccueil. Il sagit de la
barre doutils pour les dveloppeurs, qui fournit des informations prcieuses sur ltat de
lapplication. Parmi ces informations, vous pourrez trouver le temps dexcution de la page,
lutilisation mmoire, les requtes effectues dans la base de donnes, ltat
dauthentification, et beaucoup plus. Par dfaut, la barre doutils est seulement visible dans

lenvironnement dev, car fournir la barre doutils dans lenvironnement de production serait
une grosse faille de scurit: cel exposerait beaucoup dinformations sur le fonctionnement
interne de lapplication. Des rfrences cette barre doutils seront faites au cours de ce
tutoriel afin de vous apprendre lutiliser.
Configurer Symfony2 : linterface web

Symfony2 propose une interface web pour configurer divers aspects du site web tels que les
paramtres de la base de donnes. Nous avons besoin dune base de donnes pour ce projet,
commenons par utiliser loutil de configuration.
Rendez-vous ladresse http://symblog.dev/app_dev.php/ et cliquez sur le bouton
Configure. Entrez les dtails pour paramtrer lusage de votre base de donnes avec
Symfony2 (ce tutorial suppose lutilisation de MySQL, mais vous pouvez choisir nimporte
quelle base de donnes laquelle vous avez accs). Sur la page suivante, poursuivez par la
gnration dune cl CSRF. Vous seront ensuite prsents les paramtres de Symfony2 que
lapplication a gnr pour vous. Attention la remarque figurant sur cette page, il y a des
chances que votre fichier app/paramaters.ini ne soit pas accessible en criture et que vous
deviez copier/coller les paramtres dans ce fichier (Ces paramtres peuvent alors remplacer
ceux dj existants).
Les bundles : Les briques lmentaires de Symfony2

les bundles sont les blocs de construction lmentaires de nimporte quelle application
Symfony2, en fait le framework Symfony2 est lui mme un bundle. Les bundles permettent
de sparer le code en briques fonctionnelles et rutilisables. Ils encapsulent le fonctionnement
des diverses composantes telles que les contrleurs, le modle, les templates ainsi que les
diverses ressources, aussi bien images que CSS. Nous allons crer un bundle pour notre site
web dans lespace de nom (namespace) Blogger. Si vous ntes pas familier avec les espaces
de nom en PHP vous devriez passer un peu de temps sur le sujet, car ils sont trs largement
utiliss. Dans Symfony2, toute portion de code se trouve lintrieur dun espace de nom.
Regardez le chargement automatique des classes Symfony2 pour explorer en dtail le
chargement automatique des classes dans Symfony2.
Tip
Une bonne comprhension des espaces de nom peut liminer les problmes usuels auxquels
vous pourriez faire face quand les structures de rpertoires ne refltent pas correctement les
structures despaces de noms.
Cration du bundle.

Pour encapsuler les fonctionnalits utilises par le blog, nous allons crer le bundle du blog,
qui va stocker tous les fichiers requis et pourrait alors tre dpos directement dans nimporte
quelle autre application Symfony2. Symfony2 propose un large ventail de tches pour nous

assister dans la ralisation doprations courantes. Une de ces oprations courantes est la
cration dun bundle.
Pour dmarrer le gnrateur de bundle, utilisez la commande suivante. Des informations
permettant de configurer le bundle vous seront demandes. Slectionner chaque fois la
proposition par dfaut.
$ php app/console generate:bundle --namespace=Blogger/BlogBundle
--format=yml

Une fois le travail du gnrateur achev, Symfony2 aura agenc pour vous les divers lments
de base de la structure du bundle. Un certain nombre de changements sont noter ici.
Tip
Vous ntes pas obliger dutiliser les gnrateurs que Symfony2 propose, ils sont seulement l
pour vous guider. Vous auriez pu crer manuellement la structure de rpertoires et de fichiers
du bundle. Bien quil nest pas obligatoire dutiliser les gnrateurs, ils ont pour avantage
dtre rapide utiliser et ralisent toutes les tches requises pour quun bundle puisse
fonctionner, par exemple lenregistrement du bundle.
Lenregistrement du bundle

Notre nouveau bundle BloggerBlogBundle a t enregistr dans le noyau (Kernel) situ dans
app/AppKernel.php. Symfony2 a besoin que nous enregistrions tous les bundles utiliss par
lapplication. Bous pourrez galement remarquer que certains bundles sont seulement
enregistrs dans les environnements dev ou test. Charger ces bundles dans lenvironnement
de production prod apporterait des calculs supplmentaires pour des fonctionnalits qui ne
seraient pas ncessaires. Le code suivant montre comment notre BloggerBlogBundle a t
enregistr
// app/AppKernel.php
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(
// ..
new Blogger\BlogBundle\BloggerBlogBundle(),
);
// ..
}

return $bundles;

// ..
}

Les routes

Le routage du bundle a t import dans le principal fichier de routage de lapplication, situ


dans app/config/routing.yml.

# app/config/routing.yml
BloggerBlogBundle:
resource: "@BloggerBlogBundle/Resources/config/routing.yml"
prefix:
/
Loption prefix nous permet dassocier le routage entier du BloggerBlogBundle

avec un
prfixe. Dans notre cas, nous avons opt pour utiliser le chemin par dfaut, qui est /. Si par
exemple vous vouliez que toutes les routes soient prfixes par /blogger, vous pouvez
changer le prfixe pour prefix: /blogger.
Structure par dfaut

Larchitecture de rpertoire par dfaut du bundle a t cr dans le rpertoirre src. Cela


commence par le rpertoire Blogger qui est associe lespace de nom Blogger dans lequel
nous avons cre notre bundle. Dans ce rpertoire se trouve le rpertoire BlogBundle qui
contient le bundle. Le contenu de ce dossier va tre dtaill mesure que nous avancerons
dans ce tutoriel. Si vous tes familier avec larchitecture MVC, certain des noms de
rpertoires doivent parler deux mme.
Le contrleur par dfaut

Grce au gnrateur de bundle, Symfony2 a cr pour nous un contrleur par dfaut. Nous
pouvons utiliser ce contrleur en allant ladresse
http://symblog.dev/app_dev.php/hello/symblog. Vous devriez voir une page trs
simple. Essayez de remplacer le symblog la fin de ladresse par votre nom. Nous allons
examiner comment cette page a t gnre.
Routage

Le fichier de routage du BloggerBlogBundle situ dans


src/Blogger/BlogBundle/Resources/config/routing.yml

contient les rgles de routage

par dfaut
# src/Blogger/BlogBundle/Resources/config/routing.yml
BloggerBlogBundle_homepage:
pattern: /hello/{name}
defaults: { _controller: BloggerBlogBundle:Default:index }

Le routage est compos dun motif et de paramtres par dfaut. Le motif est compar
lURL, les paramtres dsignent quel contrleur excuter lorsque la route est ligible. Dans le
motif /hello/{name}, le substitut {name} va correspondre nimporte quelle type de valeur
car rien de spcifique na t prcis. Cette route ne prcise galement aucune culture, format
ou mthode HTTP. Comme aucune mthode HTTP nest prcise, les requtes de type GET,
POST, PUT ou autre sont ligibiles lors de la comparaison du motif.
Si une adresse valide tous les critres prciss par une route, alors elle sera excute par le
contrleur dcrit dans loption _controller. Cette option contient le nom logique du contrleur
qui permet Symfony2 de lassocier un fichier spcifique. Lexemple ci-dessus va conduire
lexcution de laction index du contrleur Default situ dans le fichier
src/Blogger/BlogBundle/Controller/DefaultController.php.

Le contrleur

Le contrleur dans cet exemple est trs simple. La classe``DefaultController`` tend la classe
Controller qui propose des mthodes utiles telles que la mthode render utilise ci dessous.
Comme notre route dfinit un substitut, il est pass comme argument notre action sous le
nom $name. Laction ne fait rien de plus quappeler la mthode render en lui prcisant
dutiliser le fichier template index.html.twig situ dans le dossier de vues (Views/) du
contrleur Default de BloggerBlogBundle pour laffichage. Le format du nom de template
est bundle:controlleur:template. Dans notre cas il sagit de
BloggerBlogBundle:Default:index.html.twig, qui associe le template
index.html.twig, dans le fichier de vues Default du BloggerBlogBundle, ou
physiquement au fichier
src/Blogger/BlogBundle/Resources/views/Default/index.html.twig. Un tel format
de nommage pour les template permet de rfrer des bundles depuis nimporte o dans
lapplication, ou mme dans un autre bundle. Nous verrons cela plus tard dans le chapitre.
Nous passons galement la variable $name au template via le paramtre array fourni la
mthode render.
<?php
// src/Blogger/BlogBundle/Controller/DefaultController.php
namespace Blogger\BlogBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DefaultController extends Controller
{
public function indexAction($name)
{
return $this->render('BloggerBlogBundle:Default:index.html.twig',
array('name' => $name));
}
}

Le template (la vue)

Comme vous pouvez le voir, le template est trs simple. Il affiche Hello, suivi de largument
name qui a t pass en paramtre par le contrleur.
{# src/Blogger/BlogBundle/Resources/views/Default/index.html.twig #}
Hello {{ name }}!

Nettoyage

Comme nous navons pas besoin de certains des fichiers par dfaut crs par le gnrateur,
nous allons faire un peu de nettoyage.
Le fichier du contrleur src/Blogger/BlogBundle/Controller/DefaultController.php
peut tre supprim, ainsi que le rpertoire pour la vue et son contenu
src/Blogger/BlogBundle/Resources/views/Default/. Finalement, supprimez la route
dfinie dans src/Blogger/BlogBundle/Resources/config/routing.yml

Template

Il y a 2 options par dfaut pour les templates lorsque lon utilise Symfony2; Twig et PHP.
Vous pouvez bien sr nutiliser ni lun ni lautre et opter pour une autre librairie. Cest
possible grce au container dinjection de dpendances. Nous allons utiliser Twig comme
moteur de template pour un certain nombre de raisons.
1. Twig est rapide - Les templates twig sont compils en classes PHP, il y a
donc trs peu de surcharge lors de lutilisation des templates Twig.
2. Twig est concis - Twig nous permet de raliser les fonctionnalits lies au
templates en trs peu de code. Cest comparer avec le PHP, qui peut
parfois savrer trs verbeux.
3. Twig supportes lhritage de template - Il sagit dune de mes prfres.
Les templates ont la capacit dtendre et surcharger dautres templates,
ce qui permet aux templates enfants de remplacer ce qui t propos par
dfaut par les parents.
4. Twig est sr - Twig chappe par dfaut ce quil affiche, et propose mme un
environnement de type bac sable pour les templates imports.
5. Twig est extensible - Twig propose de base un certain nombre de
fonctionnalits rcurrentes que vous tes en droit dattendre dun moteur
de template, mais pour les situations ou vous pourriez avoir des besoins
spcifiques, il est facile dtendre Twig.

Il sagit l de seulement quelques uns des bnfices de Twig. Pour trouver plus de raisons
pour lesquelles vous devriez utiliser Twig, rendez vous sur le site officiel de Twig.
Structure de prsentation

Comme Twig supporte lhritage de template, nous allons mettre en place lapproche
dhritage 3 niveaux. Cette approche nous permet de modifier la vue a 3 niveaux distincts
lintrieur de lapplication, ce qui permet pas mal de personnalisation.
Template principal - Niveau 1

Commenons par crer le bloc de base du template pour Symblog. Nous avons pour cela
besoin de 2 fichiers, le template et son fichier CSS associ. Comme Symfony2 supporte
lHTML5 nous allons galement nous en servir.
<!-- app/Resources/views/base.html.twig -->
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html"; charset=utf-8"
/>
<title>{% block title %}symblog{% endblock %} - symblog</title>
<!--[if lt IE 9]>
<script
src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->

{% block stylesheets %}
<link href='http://fonts.googleapis.com/css?
family=Irish+Grover' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?
family=La+Belle+Aurore' rel='stylesheet' type='text/css'>
<link href="{{ asset('css/screen.css') }}" type="text/css"
rel="stylesheet" />
{% endblock %}
<link rel="shortcut icon" href="{{ asset('favicon.ico') }}" />
</head>
<body>
<section id="wrapper">
<header id="header">
<div class="top">
{% block navigation %}
<nav>
<ul class="navigation">
<li><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
{% endblock %}
</div>
<hgroup>
<h2>{% block blog_title %}<a href="#">symblog</a>{%

endblock %}</h2>

<h3>{% block blog_tagline %}<a href="#">creating a blog


in Symfony2</a>{% endblock %}</h3>
</hgroup>
</header>
<section class="main-col">
{% block body %}{% endblock %}
</section>
<aside class="sidebar">
{% block sidebar %}{% endblock %}
</aside>
<div id="footer">
{% block footer %}
Symfony2 blog tutorial - created by <a
href="https://github.com/dsyph3r">dsyph3r</a>
{% endblock %}
</div>
</section>
{% block javascripts %}{% endblock %}
</body>
</html>

Note
Il y a 3 fichiers externes utiliss par le template, 1 fichier JavaScript et 2 fichiers CSS. Le
fichier JavaScript rsoud le problme du dfaut de support dHTML5 dans les navigateurs IE
antrieurs la version 9. Les 2 fichiers CSS importent des polices de caractres web Google.

Ce template met en place la structure principale de notre site de blogging. La plupart du


fichier est compos de HTML, avec dtranges commandes Twig. Cest ces directives Twig
que nous allons nous intresser maintenant.
Commenons pour nous intresser la partie HEAD du document. Regardons la balise title:
<title>{% block title %}symblog{% endblock %} - symblog</title>
La premire chose que vous aller remarquer est ltrange tag {%. Ce ni du HTML,

ni du PHP
non plus. Il sagit dun des 3 tags de Twig. Ce tag signifie Fais quelque chose. Il est utilis
pour executer des blocs de code tels que les structures de contrle, et pour dfinir des blocs.
Une liste complte des structures de contrle est disponible dans la documentation de Twig.
Le bloc Twig que nous avons dfini dans le titre fait 2 choses; Il cre un identificateur de
block nomm title, et lui fournit une valeur de contenu par dfaut entre les directives block
et endblock. En dfinissant un bloc, nous pouvons nous servir du modle dhritage de Twig.
Par exemple, sur une page qui sert afficher un article, on peut souhaiter que le titre de la
page reflte le titre de larticle. Cela peut tre ralis en tendant le template et en
surchargeant le bloc title.
{% extends '::base.html.twig' %}
{% block title %}The blog title goes here{% endblock %}

Dans lexemple ci dessus, nous avons tendu le template de base de lapplication qui
dfinissait initialement le bloc title. Vous pourrez remarquer que le format de template
utilis dans la directive extends ne contient ni la partie Bundles ni la partie Controlleur
que nous avons voqus prcdemment: souvenez vous du format
bundle:controller:template. En ne prcisant ni le Bundle ni le Controlleur, on spcifie
lusage des templates au niveau de lapplication, cest dire ceux situs dans
app/Resources/views/.
Ensuite nous avons dfini un autre bloc de titre et avons mis dedans du contenu, dans le cas
prsent le titre du blog. Comme le template parent a dj dfini un block title, il est
remplac par le nouveau. Le titre serait dsormais The blog title goes here - symblog. Cette
fonctionnalit propose par Twig va tre largement utilise lors de la cration de templates.
Dans le bloc de la feuille de style, nous avons introduit le tag Twig suivant, {{, qui signifie
Dis quelque chose.
<link href="{{ asset('css/screen.css') }}" type="text/css" rel="stylesheet"
/>

Ce tag est utilis pour afficher la valeur dune variable ou dune expression. Dans lexemple
ci-dessus, il affiche la valeur de la fonction asset, qui nous fournit une manire portable de
faire le lien avec les fichiers manipuls par lapplication, tels que les fichiers CSS, JavaScript
et les images.
Le tag {{ peut galement tre combin avec des filtres pour manipuler la sortie avant son
affichage.

{{ blog.created|date("d-m-Y") }}

Pour une liste complte des filtres, se rfrer la documentation de Twig.


Le dernier tag de Twig, que nous navons pas vu dans les templates, est le tag de
commentaires {#. Son usage est le suivant :
{# The quick brown fox jumps over the lazy dog #}

Il ny a pas dautre concept introduit dans ce template. Il fournit la structure principale, prte
tre personnalise selon nos besoins.
Ensuite, il est temps dajouter du style. Crez une feuille de style dans web/css/screen.css
et ajoutez le contenu suivant. Cela va ajouter du style pour le template principal.
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,
a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,
strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,l
abel,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,d
etails,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,se
ction,summary,time,mark,audio,video{border:0;fontsize:100%;font:inherit;verticalalign:baseline;margin:0;padding:0}article,aside,details,figcaption,figure,f
ooter,header,hgroup,menu,nav,section{display:block}body{lineheight:1}ol,ul{liststyle:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:be
fore,q:after{content:none}table{border-collapse:collapse;border-spacing:0}
body { line-height: 1;font-family: Arial, Helvetica, sans-serif;font-size:
12px; width: 100%; height: 100%; color: #000; font-size: 14px; }
.clear { clear: both; }
#wrapper { margin: 10px auto; width: 1000px; }
#wrapper a { text-decoration: none; color: #F48A00; }
#wrapper span.highlight { color: #F48A00; }
#header { border-bottom: 1px solid #ccc; margin-bottom: 20px; }
#header .top { border-bottom: 1px solid #ccc; margin-bottom: 10px; }
#header ul.navigation { list-style: none; text-align: right; }
#header .navigation li { display: inline }
#header .navigation li a { display: inline-block; padding: 10px 15px;
border-left: 1px solid #ccc; }
#header h2 { font-family: 'Irish Grover', cursive; font-size: 92px; textalign: center; line-height: 110px; }
#header h2 a { color: #000; }
#header h3 { text-align: center; font-family: 'La Belle Aurore', cursive;
font-size: 24px; margin-bottom: 20px; font-weight: normal; }
.main-col { width: 700px; display: inline-block; float: left; border-right:
1px solid #ccc; padding: 20px; margin-bottom: 20px; }
.sidebar { width: 239px; padding: 10px; display: inline-block; }
.main-col a { color: #F48A00; }
.main-col h1,
.main-col h2
{ line-height: 1.2em; font-size: 32px; margin-bottom: 10px; fontweight: normal; color: #F48A00; }
.main-col p { line-height: 1.5em; margin-bottom: 20px; }

#footer { border-top: 1px solid #ccc; clear: both; text-align: center;


padding: 10px; color: #aaa; }

Template du Bundle - Niveau 2

Nous allons maintenant avancer vers la cration de la prsentation pour le bundle Blog. Crez
un fichier dans src/Blogger/BlogBundle/Resources/views/layout.html.twig et
ajoutez-y le contenu suivant :
{# src/Blogger/BlogBundle/Resources/views/layout.html.twig #}
{% extends '::base.html.twig' %}
{% block sidebar %}
Sidebar content
{% endblock %}

A premire vue, ce template peut sembler un peu simple, mais sa simplicit est sa force. Tout
dabord, il tend le template de base de lapplication que nous avons cr prcdemment.
Ensuite, il remplace le bloc de la barre latrale avec un contenu factice. Comme la barre
latrale va tre prsente dans toutes les pages de notre blog, il est logique de raliser la
personnalisation ce niveau l. Vous pourriez demander pourquoi nous ne faisons pas la
personnalisation dans le fichier de base de lapplication, car il est galement prsent dans
toutes les pages. Cest simple, lapplication ne connait rien propos dun bundle et cele ne
devrait jamais tre le cas. Le bundle devrait contenir toutes ses fonctionnalits et afficher la
barre latrale fait partie dune de ces fonctionnalites. Ok, dans ce cas pourquoi ne plaons
nous pas la barre latrale dans chacune des pages de template ? Cest nouveau trs simple,
car il faudrait dupliquer la barre latrale chaque fois que nous voudrions ajouter une page.
Plus loin ce template du second tage va nous donner de la flexibilit pour ajouter de la
personnalisation pour des besoins futurs et tous les templates enfants en hriteront. Par
exemple, nous pourrions vouloir afficher le pied de page sur toutes les pages, et ce serait
lendroit idal pour faire ceci.
Template de page - Niveau 3

Nous sommes enfin prt pour la disposition du contrleur. Ces agencements vont
rgulirement tre lis des actions du contrleur, par exemple laction show va avoir un
template show.
Commenons par crer le contrleur pour la page daccueil et son template. Comme cest la
premire page que nous allons crer, nous allons avoir besoin de crer le contrleur. Crez le
contrleur dans src/Blogger/BlogBundle/Controller/PageController.php et ajoutez-y
le code suivant:
<?php
// src/Blogger/BlogBundle/Controller/PageController.php
namespace Blogger\BlogBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class PageController extends Controller
{
public function indexAction()

{
}

return $this->render('BloggerBlogBundle:Page:index.html.twig');

Maintenant nous allons crer le template pour cette action. Comme vous pouvez le voir dans
ce contrleur Page, nous allons afficher le template index. Crez le template dans
src/Blogger/BlogBundle/Resources/views/Page/index.html.twig
{# src/Blogger/BlogBundle/Resources/views/Page/index.html.twig #}
{% extends 'BloggerBlogBundle::layout.html.twig' %}
{% block body %}
Blog homepage
{% endblock %}

Cela prsente le format de template final que nous pouvons spcifier. Dans cet exemple, le
template BloggerBlogBundle::layout.html.twig est tendu l o la partie Controlleur
du nom du template est omise. En excluant la partie Controlleur, nous prcisons lutilisation
au niveau du bundle du template cr dans
src/Blogger/BlogBundle/Resources/views/layout.html.twig.
Maintenant ajoutons une route pour notre page daccueil. Mettez jour la configuration de
routage du bundle situ dans src/Blogger/BlogBundle/Resources/config/routing.yml.
# src/Blogger/BlogBundle/Resources/config/routing.yml
BloggerBlogBundle_homepage:
pattern: /
defaults: { _controller: BloggerBlogBundle:Page:index }
requirements:
_method: GET

Finalement, nous devons supprimer la route par dfaut pour lcran daccueil de Symfony2.
Supprimez la route _welcome en haut du fichier de routage de dev, situ dans le fichier
app/config/routing_dev.yml.
Nous sommes dsormais prt voir notre template pour le blog. Rendez vous avec votre
navigateur ladresse http://symblog.dev/app_dev.php/.

Vous devriez dsormais voir lagencement de base du blog, avec le contenu principal du blog,
et la barre latrale qui reflte lec blocs que nous avons surchargs dans les templates adquats.
La page A propos

Notre dernire mission dans cette partie du tutorial est de crer une page statique pour la page
A propos. Cela va montrer comment crer un lien entre plusieurs pages, et renforcer un peu
plus lapproche 3 niveaux que nous avons adopt.
La route

Lors de la cration dune nouvelle page, une des premires tches devrait tre la cration
dune route. Ouvrez le fichier de route du BloggerBlogBundle situ dans

src/Blogger/BlogBundle/Resources/config/routing.yml

et ajoutez la rgle de routage

suivante :
# src/Blogger/BlogBundle/Resources/config/routing.yml
BloggerBlogBundle_about:
pattern: /about
defaults: { _controller: BloggerBlogBundle:Page:about }
requirements:
_method: GET

Le contrleur

Ouvrez ensuite le contrleur Page situ dans


src/Blogger/BlogBundle/Controller/PageController.php

et ajoutez laction pour grer

la page A propos.
// src/Blogger/BlogBundle/Controller/PageController.php
class PageController extends Controller
{
// ..
public function aboutAction()
{
return $this->render('BloggerBlogBundle:Page:about.html.twig');
}
}

La vue

Concernant la vue, crez un nouveau fichier situ dans


src/Blogger/BlogBundle/Resources/views/Page/about.html.twig

et copiez-y le

contenu suivant.
{# src/Blogger/BlogBundle/Resources/views/Page/about.html.twig #}
{% extends 'BloggerBlogBundle::layout.html.twig' %}
{% block title %}About{% endblock%}
{% block body %}
<header>
<h1>About symblog</h1>
</header>
<article>
<p>Donec imperdiet ante sed diam consequat et dictum erat faucibus.
Aliquam sit
amet vehicula leo. Morbi urna dui, tempor ac posuere et, rutrum at
dui.
Curabitur neque quam, ultricies ut imperdiet id, ornare varius
arcu. Ut congue
urna sit amet tellus malesuada nec elementum risus molestie. Donec
gravida
tellus sed tortor adipiscing fringilla. Donec nulla mauris, mollis
egestas
condimentum laoreet, lacinia vel lorem. Morbi vitae justo sit amet
felis
vehicula commodo a placerat lacus. Mauris at est elit, nec vehicula
urna. Duis a
lacus nisl. Vestibulum ante ipsum primis in faucibus orci luctus et
ultrices

posuere cubilia Curae.</p>


</article>
{% endblock %}
Cette page A propos ne contient rien de spectaculaire.

Son seul rle est dafficher un fichier


de template qui possde un contenu factice. cela nous amne nanmoins notre prochaine
tche.
Lier les pages

Nous avons dsormais la page A propos en tat de fonctionnement: rendez vous ladresse
http://symblog.dev/app_dev.php/about pour vous en assurer. Par contre, il ny a
actuellement aucun autre moyen pour un utilisateur de voir la page A propos, que de taper
ladresse complte comme nous venons de le faire. Comme vous pouvez vous en douter,
Symfony2 va nous permettre de crer un lien. Il permet de de faire correspondre des adresses
comme nous lavons dj vu, et peut galement gnrer des adresses pour ces routes. Vous
devriez toujours utiliser les fonctions de routage proposes par Symfony2. Ne soyez jamais
tent, dans vos application, de faire la chose suivante :
<a href="/contact">Contact</a>
<?php $this->redirect("/contact"); ?>

Vous vous demandez srement ce qui ne va pas avec cette approche, cest peut-tre la manire
que vous avez toujours utilise pour faire des liens entre les pages. Nanmoins, il y a un
certain nombre de problmes avec cette approche :
1. Cela utilise un lien cod en dur et ignore tout du systme de routage de
Symfony2. Si vous vouliez changer ladresse de la page de contact
nimporte quel moment, vous devriez trouver toutes les rfrences au lien
en dur et les changer.
2. Cela ignore le contrleur denvironnement. Les environnements sont
quelque chose que nous navons pas vraiment expliqu jusqu prsent,
mais vous vous en tes servis. Le contrleur de faade app_dev.php nous
donne accs notre application dans lenvironnement dev. Si nous devions
remplacer app_dev.php par app.php, alors nous ferions tourner lapplication
dans lenvironnement de production. La signification de ces
environnements sera explique plus loin dans le tutoriel, mais pour le
moment, il est important de noter que les liens en dur ne maintiennent pas
lenvironnement dans lequel nous sommes car le contrleur de faade
nest pas prfix dans lURL.

La manire correcte de faire des liens entre les pages est dutiliser les fonctions path et url
proposes par Twig. Elles sont toutes les deux trs proches, sauf que la fonction url renvoit
une URL absolue. Mettons jour le template principal de notre application situ dans
app/Resources/views/base.html.twig pour faire le lien entre la page daccueil et la page
d propos.
<!-- app/Resources/views/base.html.twig -->
{% block navigation %}
<nav>

<ul class="navigation">
<li><a
href="{{ path('BloggerBlogBundle_homepage') }}">Home</a></li>
<li><a
href="{{ path('BloggerBlogBundle_about') }}">About</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
{% endblock %}

Maintenant mettez jour votre navigateur pour voir les liens vers la page daccueil (Home) et
vers la page A propos (About) fonctionner comme attendu. Si vous regardez le code source
de ces pages, vous pourrez remarquer que les liens ont t prfixs par /app_dev.php/. Il
sagit du contrleur de faade expliqu plus tt, et vous pouvez voir que lutilisation de path
maintient sa prsence.
Finalement, mettons jour le lien sur le logo pour vous rediriger vers la page daccueil. Pour
cela, mettez jour le template situ dans app/Resources/views/base.html.twig.
<!-- app/Resources/views/base.html.twig -->
<hgroup>
<h2>{% block blog_title %}<a
href="{{ path('BloggerBlogBundle_homepage') }}">symblog</a>{% endblock
%}</h2>
<h3>{% block blog_tagline %}<a
href="{{ path('BloggerBlogBundle_homepage') }}">creating a blog in
Symfony2</a>{% endblock %}</h3>
</hgroup>

Conclusion

Nous avons couvert les domaines de base dune application Symfony2, commencer par la
configuration et la mise en place de lapplication. Nous avons commenc explorer les
concepts fondamentaux derrire une application Symfony2, en particulier le routage et le
moteur de template Twig.
Nous verrons par la suite la cration dune page de contact. Cette page est lgrement plus
complique que la page A propos car elle permet aux utilisateurs dintragir avec un
formulaire pour envoyer des requtes. Le chapitre suivant va prsenter les concepts de
validateurs et de formulaires.

[Partie 2] - Page de contact : validateurs,


formulaires et envoi demails.
Je propose galement des formations en petits groupes sur 2 3 jours, plus dinfos sur la page
ddie. Nhsitez pas me contacter (06.62.28.01.87 ou clement [@] keiruaprod.fr) pour en
discuter !

Introduction
Maintenant, nous avons mis en place le template HTML de base. Il est dsormais temps de
crer une des pages fonctionnelles. Nous allons commencer avec une des pages les plus
simples, la page de contact. A la fin de ce chapitre, nous aurons une page de contact qui
permet aux utilisateurs denvoyer des messages au webmaster. Ces requtes lui seront
envoyes par email.
Les thmes suivants seront abords au cours de ce chapitre :
1. Les validateurs
2. Les formulaires
3. La mise en place de paramtres de configuration pour un bundle

La page de contact
Routage
Comme avec la page A propos que nous avons cre dans le chapitre prcdent, nous allons
commencer par dfinir la route vers la page de contact. Ouvrez le fichier de routage du
BloggerBlogBundle situ dans
src/Blogger/BlogBundle/Resources/config/routing.yml et ajoutez-y la rgle de
routage suivante :
# src/Blogger/BlogBundle/Resources/config/routing.yml
BloggerBlogBundle_contact:
pattern: /contact
defaults: { _controller: BloggerBlogBundle:Page:contact }
requirements:
_method: GET

Il ny a rien de nouveau ici, la rgle associe le lien /contact``pour la mthode HTTP


``GET et excute laction contact du contrleur Page dans le BloggerBlogBundle.

Contrleur

Ensuite, ajoutons laction pour la page de contact dans le contrleur Page du


BloggerBlogBundle situ dans
src/Blogger/BlogBundle/Controller/PageController.php.
// src/Blogger/BlogBundle/Controller/PageController.php
// ..
public function contactAction()
{
return $this->render('BloggerBlogBundle:Page:contact.html.twig');
}
// ..

Pour le moment cette action est trs simple, elle affiche simplement la vue de la page de
contact. Nous reviendrons sur ce contrleur plus tard.

Vue
Crez la vue pour la page de contact dans
src/Blogger/BlogBundle/Resources/views/Page/contact.html.twig

et ajoutez-y le

contenu suivant.
{# src/Blogger/BlogBundle/Resources/views/Page/contact.html.twig #}
{% extends 'BloggerBlogBundle::layout.html.twig' %}
{% block title %}Contact{% endblock%}
{% block body %}
<header>
<h1>Contact symblog</h1>
</header>
<p>Want to contact symblog?</p>
{% endblock %}

Ce template est galement trs simple. Il tend la mise en page du template de


BloggerBlogBundle remplace le bloc de titre pour un titre personnalis et dfinit du contenu
pour le bloc body.

Lien vers la page


Finalement nous devons mettre jour le lien dans le template de lapplication, situ dans
app/Resources/views/base.html.twig pour crer le lien vers la page de contact.
<!-- app/Resources/views/base.html.twig -->
{% block navigation %}
<nav>
<ul class="navigation">
<li><a
href="{{ path('BloggerBlogBundle_homepage') }}">Home</a></li>
<li><a
href="{{ path('BloggerBlogBundle_about') }}">About</a></li>
<li><a
href="{{ path('BloggerBlogBundle_contact') }}">Contact</a></li>
</ul>
</nav>

{% endblock %}

Si vous vous rendez avec votre navigateur ladresse http://symblog.dev/app_dev.php/


et cliquez sur le lien vers la page de contact dans la barre de navigation, vous devriez voir une
page de contact trs simple. Maintenant que la page est correctement mise en place, il est
temps de commencer travailler sur le formulaire de contact. Cest dcoup en 2 parties
distinctes: le validateur et le formulaire. Avant de parler de ces deux sujets, il faut rflchir
la manire dont nous allons grer les donnes du formulaire de contact.

Lentit Contact
Commenons par crer une classe qui reprsente une requte de contact par un utilisateur.
Nous voulons rcuprer des informations de base telles que le nom, le sujet de la requte ainsi
que le message que lutilisateur souhaite envoyer. Crez un nouveau fichier dans
src/Blogger/BlogBundle/Entity/Enquiry.php et collez-y le contenu suivant.
<?php
// src/Blogger/BlogBundle/Entity/Enquiry.php
namespace Blogger\BlogBundle\Entity;
class Enquiry
{
protected $name;
protected $email;
protected $subject;
protected $body;
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
public function getEmail()
{
return $this->email;
}
public function setEmail($email)
{
$this->email = $email;
}
public function getSubject()
{
return $this->subject;
}
public function setSubject($subject)

{
}

$this->subject = $subject;

public function getBody()


{
return $this->body;
}
public function setBody($body)
{
$this->body = $body;
}
}

Comme vous pouvez le voir cette classe dfinit simplement quelques membres protgs ainsi
que leurs accesseurs. Rien ici nindique comment valider les donnes, ni comment ces
donnes sont lies aux lments du formulaire. Nous reviendrons l dessus plus tard.
Note
Faisons une petite digression pour parler de lutilisation des espaces de nom dans Symfony2.
La classe dentit que nous avons cre est dfinie par lespace de nom
Blogger\BlogBundle\Entity. Comme le chargement automatique de Symfony2 supporte le
standard PSR-0, lespace de nom reflte directement la structure de rpertoires du bundle. La
classe dentit Enquiry est situe dans src/Blogger/BlogBundle/Entity/Enquiry.php, ce
qui permet Symfony2 de charger cette classe automatiquement de manire correcte.
Comment le chargeur automatique de Symfony2 sait que lespace de nom Blogger se trouve
dans le rpertoire src ? Cest grce la configuration du chargement automatique dans
app/autoloader.php

// app/autoloader.php
$loader->registerNamespaceFallbacks(array(
__DIR__.'/../src',
));

Cette ligne de code enregistre les rpertoires utiliser pour tous les espaces de noms qui ne
sont pas enregistrs. Comme lespace de nom Blogger nest pas enregistr, le chargement
automatique des classes de Symfony2 va chercher les fichiers requis dans le rpertoire src
Le chargement automatique et les espaces de nom sont des concepts trs puissant dans
Symfony2. Si vous avez des problmes dans lesquels PHP narrivent pas trouver une ou
plusieurs classes, il y a des chances pour quil y ait des erreurs dans votre espace de nom ou
dans votre structure de rpertoires. Vrifiez galement que les espaces de nom soient bien
enregistrs dans le chargeur comme vu au dessus. Vous ne devriez jamais tre tents de
rparer cel en utilisant les directives PHP require ou include.

Formulaires
Ensuite, nous allons crer le formulaire. Symfony2 est livr avec une librairie de formulaires
trs puissante qui rend trs facile le fait de travailler avec les formulaires. Comme avec tous
les autres composants de Symfony2, le composant de formulaire peut tre utilis lextrieur

de Symfony2 pour vos propores projets. La source du composant de formulaire est disponible
sur Github. Nous allons commencer par crer une classe AbstractType qui reprsente le
formulaire de contact. Nous aurions pu crer le formulaire directement dans le contrleur et
ne pas nous embter avec cette classe, nanmoins sparer le formulaire dans sa propre classe
nous permet de rutiliser le formulaire dans lapplication. Cela nous vite galement
dencombrer le contrleur, car aprs tout, il est cens tre simple : son but est de faire le lien
entre le modle et la vue.

EnquiryType
Crez un nouveau fichier dans src/Blogger/BlogBundle/Form/EnquiryType.php et
collez-y le contenu suivant.
<?php
// src/Blogger/BlogBundle/Form/EnquiryType.php
namespace Blogger\BlogBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class EnquiryType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('name');
$builder->add('email', 'email');
$builder->add('subject');
$builder->add('body', 'textarea');
}

public function getName()


{
return 'contact';
}

La classe EnquiryType nous permet de prsenter la classe FormBuilder. la classe


FormBuilder est votre meilleur atout lorsquil est question de crer des formulaires. Elle est
capable de simplifier le processus de dfinition des champs partir des mtadonnes quun
champ possde. Comme notre entit est trs simple, nous navons dfini aucune mtadonne,
donc le FormBuilder va crer des champs de texte par dfaut. Cest adapt la plupart des
champs, sauf pour le corps du message pour lequel nous souhaitons utiliser une textarea, et
pour ladresse email pour laquelle nous allons utiliser le nouveau champ dadresse email
propos par lHTML5.
Note
Un aspect cl prendre en compte est le fait que la mthode getName doit renvoyer un
identifiant unique.

Crer le formulaire dans le contrleur

Nous avons dsormais dfini lentit Enquiry et la classe EnquiryType, nous pouvons
dsormais mettre jour laction pour la page de contact afin de sen servir. Remplacez le
contenu de la mthode contactAction dans le fichier
src/Blogger/BlogBundle/Controller/PageController.php par le suivant :
// src/Blogger/BlogBundle/Controller/PageController.php
public function contactAction()
{
$enquiry = new Enquiry();
$form = $this->createForm(new EnquiryType(), $enquiry);
$request = $this->getRequest();
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
// Perform some action, such as sending an email
// Redirect - This is important to prevent users re-posting
// the form if they refresh the page
return $this->redirect($this>generateUrl('BloggerBlogBundle_contact'));
}
}
return $this->render('BloggerBlogBundle:Page:contact.html.twig', array(
'form' => $form->createView()
));
}

Nous commenons par crer une instance de lentit Enquiry. Cette entit reprsente les
donnes dun message sur la page de contact. Nous crons ensuite le formulaire
correspondant: nous spcifions le type EnquiryType cr prcdemment, et passons en
paramtres notre object entit $enquiry. La mthode createForm est capable dutiliser ces 2
patrons pour crer la reprsentation dun formulaire.
Comme cette action du contrleur va maintenant soccuper dafficher et traiter le formulaire
qui lui est soumis, nous devons faire attention la mthode HTTP utilise. Les formulaires
soumis sont gnralement envoys via la mthode POST, et notre formulaire ny fera pas
exception. Si la requte est de type POST, un appel la mthode bindRequest va transformer
les donnes soumises pour les associes notre objet $enquiry. A ce moment-l, lobjet
$enquiry contiendra une reprsentation de ce que lutilisateur aura envoy.
Nous vrifions ensuite que le formulaire est valide. Comme nous navons pas prcis de
validateurs pour le moment, le formulaire sera toujours valide.
Enfin, nous prcisons le template utiliser pour laffichage. Notez que nous passons
galement la vue une reprsentation du formulaire afficher, ce qui nous permet deffectuer
laffichage adquat dans la vue.
Comme nous avons utilis 2 nouvelles classes dans notre contrleur, nous devons importer les
espaces de nom correspondants. Mettez jour le dbut du fichier
src/Blogger/BlogBundle/Controller/PageController.php avec le contenu suivant.

<?php
// src/Blogger/BlogBundle/Controller/PageController.php
namespace Blogger\BlogBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
// Import new namespaces
use Blogger\BlogBundle\Entity\Enquiry;
use Blogger\BlogBundle\Form\EnquiryType;
class PageController extends Controller
// ..

Affichage du formulaire
Grce Twig, laffichage de formulaires est trs simple. Twig propose en effet un systme par
couches pour laffichage de formulaires qui permet soit dafficher un formulaire comme une
unique entit, soit comme des lments individuels, selon le besoin de personnalisation
ncessaire.
Afin de dmontrer la puissance des mthodes de Twig, nous allons utiliser le bout de code
suivante pour afficher le formulaire entier :
<form action="{{ path('BloggerBlogBundle_contact') }}" method="post"
{{ form_enctype(form) }}>
{{ form_widget(form) }}
<input type="submit" />
</form>

Bien que cette mthode soit utile et trs simple durant la phase de prototypage, cela savre
limit lorsque le besoin de personnalisation est important, ce qui est souvent le cas avec les
formulaires.
Pour notre formulaire de contacts, nous allons opter pour un compromis. Remplacez le code
du template src/Blogger/BlogBundle/Resources/views/Page/contact.html.twig par
le suivant :
{# src/Blogger/BlogBundle/Resources/views/Page/contact.html.twig #}
{% extends 'BloggerBlogBundle::layout.html.twig' %}
{% block title %}Contact{% endblock%}
{% block body %}
<header>
<h1>Contact symblog</h1>
</header>
<p>Want to contact symblog?</p>
<form action="{{ path('BloggerBlogBundle_contact') }}" method="post" {{
form_enctype(form) }} class="blogger">
{{ form_errors(form) }}
{{ form_row(form.name) }}
{{ form_row(form.email) }}
{{ form_row(form.subject) }}

{{ form_row(form.body) }}
{{ form_rest(form) }}
<input type="submit" value="Submit" />
</form>
{% endblock %}

Comme vous pouvez le voir, nous utilisons 4 nouvelles fonctions Twig pour afficher notre
formulaire.
La premire fonction form_enctype dfinit le type de contenu du formulaire. Cest ncessaire
lorsquun formulaire traite avec des fichiers uploads. Ce nest donc pas ncessaire pour le
moment, mais cest une bonne habitude que de lutiliser pour tous les formulaires au cas o
lupload de fichier soit ajout dans le futur. Dboguer un formulaire qui traite de lupload de
fichier dans lequel le type de contenu nest pas spcifi peut tre un vrai casse tte !
La seconde fonction form_errors affichera les erreurs du formulaire dans le cas o la
validation choie.
La 3me fonction form_row affiche les lments lis un champ. Cela comporte toutes les
erreurs associes au champ, ltiquette lie au champ ainsi que llment du champ de
formulaire lui mme.
Enfin, nous utilisons la fonction form_rest. Cest toujours une bonne habitude dutiliser cette
fonction la fin de laffichage pour afficher les champs qui auraient p tre oublis, ce qui
inclue les champs cachs ainsi que le jeton CSRF de Symfony2.
Note
Les attaques de type Cross-site request forgery (CSRF) sont expliques en dtail dans le
chapitre sur les formulaires du livre Symfony2.

Donner du style au formulaire.


Si vous regardez maintenant notre formulaire via la page
http://symblog.dev/app_dev.php/contact, vous remarquerez quil nest pas trs
engageant. Ajoutons lui du style pour amliorer son rendu. Comme les styles sont spcifiques
notre bundle de blog, nous allons les crer dans une feuille de style lintrieur mme du
bundle. Crez un nouveau fichier dans
src/Blogger/BlogBundle/Resources/public/css/blog.css et copiez-y le contenu
suivant :
.blogger-notice { text-align: center; padding: 10px; background: #DFF2BF;
border: 1px solid; color: #4F8A10; margin-bottom: 10px; }
form.blogger { font-size: 16px; }
form.blogger div { clear: left; margin-bottom: 10px; }
form.blogger label { float: left; margin-right: 10px; text-align: right;
width: 100px; font-weight: bold; vertical-align: top; padding-top: 10px; }
form.blogger input[type="text"],
form.blogger input[type="email"]
{ width: 500px; line-height: 26px; font-size: 20px; min-height: 26px; }

form.blogger textarea { width: 500px; height: 150px; line-height: 26px;


font-size: 20px; }
form.blogger input[type="submit"] { margin-left: 110px; width: 508px; lineheight: 26px; font-size: 20px; min-height: 26px; }
form.blogger ul li { color: #ff0000; margin-bottom: 5px; }

Nous devons informer lapplication que nous souhaitons utiliser cette feuille de style. Nous
pourrions importer la feuille de style dans le template de la page de contact, mais comme
dautres templates pourraient utiliser cette feuille de style par la suite, cela a plus de sens de
limporter dans le layout que nous avons cr pour notre BloggerBlogBundle du chapitre 1.
Ouvrez ce fichier, situ dans
src/Blogger/BlogBundle/Resources/views/layout.html.twig et mettez-y le contenu
suivant :
{# src/Blogger/BlogBundle/Resources/views/layout.html.twig #}
{% extends '::base.html.twig' %}
{% block stylesheets %}
{{ parent() }}
<link href="{{ asset('bundles/bloggerblog/css/blog.css') }}"
type="text/css" rel="stylesheet" />
{% endblock %}
{% block sidebar %}
Sidebar content
{% endblock %}

Remarquez que nous avons dfini un bloc stylesheets qui remplace le bloc dfini dans le
template parent. Il est important de remarquer lappel la fonction parent. Cette fonction se
charge dimporter le contenu du bloc dans le template parent
app/Resources/base.html.twig, dans le cas prsent celui soccupant des feuilles de style,
cela nous permet dajouter nos nouvelles feuilles de style la suite. Nous ne voulons pas
supprimer les feuilles de style existantes.
Afin que la fonction asset fasse les bons liens avec les ressources, nous devons copier ou
dplacer les ressources du bundle dans le rpertoire web de lapplication. Cela peut tre fait
par la commande suivante:
$ php app/console assets:install web --symlink

Note
Si vous utilisez un systme dexploitation qui ne supporte pas les liens symboliques
(symlink), tel que Windows, vous devez supprimer loption symlink comme ceci :
php app/console assets:install web

Cette mthode va en fait copier les ressources prsentes dans le rpertoire public des bundles
dans le rpertoire web de lapplication. Comme les fichiers sont copis, il est ncessaire de
lancer cette commande chaque fois que vous faites une modification dans les ressources
publiques utilises par un bundle.

Vous pouvez maintenant rafrachir la page de contact, dans laquelle le nouveau style
sapplique au formulaire. Cest quand mme un peu plus sympa comme a non ?

Tip
Alors que la fonction asset nous permet dutiliser les ressources, il y a une meilleure
alternative pour cette opration, il sagit dAssectic. Cette librairie, crite par Kris Wallsmith
est fournie par dfaut avec la distribution standard de Symfony2. Elle permet une gestion des
ressources bien meilleure que celle propose par Symfony2. Assetic permet de lancer des
filtres sur les fichiers pour les fusionner automatiquement, les allger ou les compresser. Elle
permet galement de lancer des filtres de compression sur les images. Enfin Assetic nous
permet de faire rfrence des ressources directement lintrieur des rpertoires publics des

bundles, sans avoir lancer la commande assets:install. Nous reviendrons plus en dtail
sur ce sujet dans un chapitre ultrieur.

Echec la soumission
Les plus enthousiastes parmi vous auront probablement dj essay de soumettre le
formulaire, et seront tombs sur une erreur de Symfony2.

Ce message derreur nous indique quil ny a pas de route correspondante ladresse


/contact pour la mthode HTTP POST. La route que nous avons dfinie naccepte que les
requtes de type GET et HEAD, car nous lavons configure comme cela.
Mettons jour notre route de contact dans
src/Blogger/BlogBundle/Resources/config/routing.yml

afin de permettre galement

les requtes de type POST.


# src/Blogger/BlogBundle/Resources/config/routing.yml
BloggerBlogBundle_contact:
pattern: /contact
defaults: { _controller: BloggerBlogBundle:Page:contact }
requirements:
_method: GET|POST

Tip
Vous vous demandez probablement pourquoi la route acceptait les requtes de type HEAD,
alors que seul GET avait t spcifi. Et bien parce quune requte HEAD est de type GET o
seules les en-ttes HTTP sont retournes.

Maintenant vous pouvez soumettre le formulaire qui devrait fonctionner comme attendu, bien
quil ne fasse rien de particulier pour le moment. La page redirige simplement nouveau vers
le formulaire de contact.

Validateurs.
Les validateurs de Symfony2 permettent de valider les donnes. La validation est une tche
courante lorsquil est question de valider les donnes de formulaire. Cette tche doit tre
ralise avant que les donnes ne soient envoyes vers une base de donnes. Les validateurs
Symfony2 nous permettent de sparer notre logique de validation des composants qui
pourraient sen servir, tels que les composants de formulaire ou de base de donne. Cette
approche signifie que nous allons avoir un jeu de rgles de validation par objet.
Commenons par mettre jour lentit Enquiry dans
src/Blogger/BlogBundle/Entity/Enquiry.php pour spcifier quelques validateurs.
Noubliez pas dajouter les 5 nouvelles dclarations avec use au dbut du fichier.
<?php
// src/Blogger/BlogBundle/Entity/Enquiry.php
namespace Blogger\BlogBundle\Entity;
use
use
use
use
use

Symfony\Component\Validator\Mapping\ClassMetadata;
Symfony\Component\Validator\Constraints\NotBlank;
Symfony\Component\Validator\Constraints\Email;
Symfony\Component\Validator\Constraints\MinLength;
Symfony\Component\Validator\Constraints\MaxLength;

class Enquiry
{
// ..
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('name', new NotBlank());
$metadata->addPropertyConstraint('email', new Email());
$metadata->addPropertyConstraint('subject', new NotBlank());
$metadata->addPropertyConstraint('subject', new MaxLength(50));
$metadata->addPropertyConstraint('body', new MinLength(50));
}
// ..
}

Afin de dfinir les validateurs, nous devons implmenter la mthode statique


loadValidatorMetadata, qui nous fournit un objet de type ClassMetadata. Nous pouvons
utiliser cet objet pour dfinir des contraintes sur les proprits de nos entits membres. La
premire ligne applique la contrainte NotBlank la propritt name. Les validateurs sont aussi
simples quils y paraissent: celui-ci se contente de renvoyer vrai si la valeur valider nest pas
vide. Nous mettons ensuite en place la validation pour le champ email. Le systme de
validation nous fournit en effet une rgle de validation pour ce type de champ, qui va mme

vrifier le MX record (quivalent en gros du DNS, mais pour les adresses mail) afin de
sassurer que le domaine est valide. Pour lattribut subject, nous voulons la fois nous
assurer que le champ nest pas vide et quil ne dpasse pas une taille maximale, ce qui est fait
avec les contraintes NotBlank et MaxLength: il est en effet possible dappliquer autant de
rgles de validations quon le souhaite.
Une liste complte des contraintes de validation est disponible dans les documents de
rfrence de Symfony2. Il est galement possible de crer des rgles de validation
personnalises.
Vous pouvez maintenant soumettre le formulaire de contact, et les donnes soumises passent
dans les contraintes de validation: essayez de mettre une adresse email invalide. Vous devriez
voir un message derreur qui vous informe que laddresse nest pas valide. Chaque validateur
propose un message par dfaut qui peut tre remplac si ncessaire. Pour changer le message
par dfaut du validateur du champ email, vous pouvez par exemple faire :
$metadata->addPropertyConstraint('email', new Email(array(
'message' => 'symblog does not like invalid emails. Give me a real
one!'
)));

Tip
Si vous utilisez un navigateur qui supporte le HTML5 (il y a de grandes chances pour que ce
soit le cas), des messages HTML5 vont apparatre pour vous faire respecter certaines
contraintes partir des mtadonnes de votre objet Entity. Vous pouvez le voir pour
llment email, dont le code HTML est le suivant :
<input type="email" value="" required="required" name="contact[email]"
id="contact_email">

Il utilise un des nouveaux champs HTML5, dont lattribut required est dfini. La validation
ct client est bien dans le sens o elle ne ncessite pas un aller-retour avec le serveur pour
valider le formulaire, nanmoins elle ne devrait pas tre utilise seule. Vous devriez toujours
valider les donnes ct serveur, car il est trs facile pour un utilisateur de passer outre la
validation ct client.

Envoyer lemail
Bien que notre formulaire nous permettre actuellement de soumettre des requtes, rien ne leur
arrive rellement pour le moment. Mettons jour le contrleur afin denvoyer un email au
webmaster du blog. Symfony2 est livr avec la librairie denvoi demail Swift Mailer. Il sagit
dune librairie trs puissante, nous allons seulement effleurer la surface de ce quil est
possible de faire avec.

Configurer les paramtres de Swift Mailer


Swift Mailer est dj configure de base dans la distribution standard de Symfony2,
nanmoins nous devons configurer quelques paramtres concernant la mthode denvoi, et lui

fournir les accrditations ncessaires pour raliser cette tche. Ouvrez le fichier de paramtres
dans app/parameters.ini et trouvez les paramtres prfixs par mailer_.
mailer_transport="smtp"
mailer_host="localhost"
mailer_user=""
mailer_password=""

Swift Mailer propose un certain nombre de mthodes pour envoyer les emails, entre autre
lutilisation dun serveur SMTP, linstallation locale de sendmail, ou mme lutilisation dun
compte GMail. Par souci de simplicit, cest cette dernire mthode que nous allons utiliser.
Mettez jour les paramtres comme suit, en remplaant votre nom dutilisateur (username) et
votre mot de passe (password) lorsque cest ncessaire.
mailer_transport="gmail"
mailer_encryption="ssl"
mailer_auth_mode="login"
mailer_host="smtp.gmail.com"
mailer_user="your_username"
mailer_password="your_password"

Warning
Faites attention si vous utilisez un systme de contrle de version (SCV) tel que Git pour
votre projet, en particulier si votre dpt est accessible publiquement nimporte qui. Vous
devriez vous assurer que les fichiers contenant des informations sensibles, tel que
app/parameters.ini, sont dans la liste des fichier ignorer. Une approche courante consiste
suffixer le nom de fichier qui a des informations sensibles, tel que app/parameters.ini,
avec .dist. Vous pouvez alors proposer des valeurs par dfaut pour les paramtres sensibles
dans ce fichier, et lajouter votre gestionnaire de version, pendant que le vrai fichier, par
exemple app/parameters.ini est inclus dans la liste de ceux ignorer. Vous pouvez alors
dployer les fichiers *.dist avec votre projet et permettre aux dveloppeurs de supprimer
lextension .dist et renseigner les paramtres requis.

Mise jour du contrleur


Mettez jour le contrleur de Page situ dans
src/Blogger/BlogBundle/Controller/PageController.php

avec le contenu suivant :

// src/Blogger/BlogBundle/Controller/PageController.php
public function contactAction()
{
// ..
if ($form->isValid()) {
$message = \Swift_Message::newInstance()
->setSubject('Contact enquiry from symblog')
->setFrom('enquiries@symblog.co.uk')
->setTo('email@email.com')
->setBody($this>renderView('BloggerBlogBundle:Page:contactEmail.txt.twig', array('enquiry'
=> $enquiry)));
$this->get('mailer')->send($message);

$this->get('session')->setFlash('blogger-notice', 'Your contact


enquiry was successfully sent. Thank you!');
// Redirect - This is important to prevent users re-posting
// the form if they refresh the page
return $this->redirect($this>generateUrl('BloggerBlogBundle_contact'));
}
// ..
}

Une fois que la librairie Swift Mailer nous a permis de crer une instance dun objet
Swift_Message, il est utilis pour envoyer lemail.
Note
Comme la librairie Swift Mailer nutilise pas les espaces de nom, nous devons prfixer la
classe avec avec un \. Cela indique PHP de raliser lchappement vers l espace global.
Vous devrez prfixer tous les classes et fonctions qui ne sont pas dans un espace de nom avec
un \. Si vous ne placiez pas prfixe avant la classe Swift_Message, PHP chercherait alors la
classe dans lespace de nom actuel, dans cet exemple Blogger\BlogBundle\Controller,
conduisant lapparition dune erreur, car la classe ne peut lgitimement pas tre trouve.
Nous avons galement mis un message flash sur la session. Les messages flash sont des
messages qui sont affichs seulement aprs une requte, ensuite ils sont supprims par
Symfony2. Le message flash sera affich dans le template actuel pour informer lutilisateur
que le message a t envoy. Comme les messages flash ne sont affichs quaprs une requte
unique, ils sont parfaits pour notifier lutilisateur de la russite (ou lchec) des actions
prcdentes.
Pour afficher les messages flash nous devons mettre jour le template de la page de contact
situ dans src/Blogger/BlogBundle/Resources/views/Page/contact.html.twig.
Mettez jour le contenu du template avec ce qui suit :
{# src/Blogger/BlogBundle/Resources/views/Page/contact.html.twig #}
{# rest of template ... #}
<header>
<h1>Contact symblog</h1>
</header>
{% if app.session.hasFlash('blogger-notice') %}
<div class="blogger-notice">
{{ app.session.flash('blogger-notice') }}
</div>
{% endif %}
<p>Want to contact symblog?</p>
{# rest of template ... #}

Cela vrifie quil y a un message flash afficher avec pour identificateur blogger-notice, et
si cest le cas on laffiche.

Enregistrer lemail du webmaster


Symfony2 propose un systme de configuration que nous pouvons utiliser pour dfinir nos
propres paramtres. Nous allons utiliser cette mthode afin de dfinir une adresse email pour
le webmaster, plutt que de la coder en dur dans le contrleur comme nous lavons fait au
dessus. De cette manire, nous pourrons facilement rutiliser cette variable dautres endroits
sans dupliquer le code. De plus, lorsque votre blog aura gnr tellement de traffic que les
requtes seront trop pnibles grer par le webmaster, il sera peut tre temps de les dlguer
votre assistant. Crez un nouveau fichier dans
src/Blogger/BlogBundle/Resources/config/config.yml et collez-y le code suivant :
# src/Blogger/BlogBundle/Resources/config/config.yml
parameters:
# Blogger contact email address
blogger_blog.emails.contact_email: contact@email.com

Lorsque lon dfinit des paramtres, cest une bonne habitude de dcouper le nom de la
variable en un certain nombre de composants. La premire partie devrait tre le nom du
bundle en minuscule, en utilisant des underscore _ pour sparer les mots. Dans cet exemple,
nous avons remplac BloggerBlogBundle par blogger_blog. Le reste du nom de paramtres
peut contenir nimporte quel nombre de parties, spares par des points .. Cela nous permet
de regrouper les paramtres logiquement.
Afin que lapplication Symfony2 puisse utiliser ces nouveaux paramtres, nous devons
importer le nouveau fichier de configuration situ dans app/config/config.yml. Pour
raliser cela, mettez jour les imports au dbut du fichier par ce qui suit :
# app/config/config.yml
imports:
# .. existing import here
- { resource: @BloggerBlogBundle/Resources/config/config.yml }

Le chemin dimport est le chemin physique du fichier sur le disque. La directive


@BloggerBlogBundle va va se rendre au chemin du BloggerBlogBundle, qui est
src/Blogger/BlogBundle.
Nous pouvons enfin mettre jour laction de contact, afin de nous servir de ce nouveau
paramtre.
// src/Blogger/BlogBundle/Controller/PageController.php
public function contactAction()
{
// ..
if ($form->isValid()) {
$message = \Swift_Message::newInstance()
->setSubject('Contact enquiry from symblog')
->setFrom('enquiries@symblog.co.uk')
->setTo($this->container>getParameter('blogger_blog.emails.contact_email'))
->setBody($this>renderView('BloggerBlogBundle:Page:contactEmail.txt.twig', array('enquiry'
=> $enquiry)));

$this->get('mailer')->send($message);
// ..
}
// ..
}

Tip
Comme le fichier de configuration est import au dbut du fichier de configuration de
lapplication, il est facile de remplacer nimporte lequel des paramtres imports. Par
exemple, en ajoutant ce qui suit la fin du fichier app/config/config.yml, nous pourrions
remplacer les valeurs des paramtres du bundle.
# app/config/config.yml
parameters:
# Blogger contact email address
blogger_blog.emails.contact_email: assistant@email.com

Cette personnalisation permet au bundle de proposer des valeurs par dfaut que lapplication
peut par la suite remplacer.
Note
Bien quil soit facile de crer des paramtres de configuration par cette mthode, Symfony2
propose galement une mthode dans laquelle on expose une configuration smantique pour
le bundle. Nous dtaillerons cette mthode plus loin dans le tutoriel.

Crer un template pour les email


Le corps de lemail est dcrit dans un template. Crez ce template dans
src/Blogger/BlogBundle/Resources/view/Page/contactEmail.txt.twig

et mettez y ce

qui suit :
{# src/Blogger/BlogBundle/Resources/view/Page/contactEmail.txt.twig #}
A contact enquiry was made by {{ enquiry.name }} at {{ "now" | date("Y-m-d
H:i") }}.
Reply-To: {{ enquiry.email }}
Subject: {{ enquiry.subject }}
Body:
{{ enquiry.body }}

Le contenu de lemail est celui que lutilisateur vient de soumettre.


Vous avez peut-tre remarqu que lextension de ce template est diffrente de celle des autres
templates que nous avons crs jusque l. Il utilise lextension .txt.twig. La premire partie
de lextension, .txt, spcifie le format du fichier gnrer. Parmi les formats courants, on
peut noter .txt, .html, .css, .js, .xml et .json. La seconde partie de lextension dfinit quel
moteur de template utiliser. Dans le cas prsent Twig. Une extension en .php signifierait
lutilisation de PHP pour le rendu du template.

Lorsque vous soumettez une requte un email va tre envoy laddresse dfinie dans le
paramtre blogger_blog.emails.contact_email.
Tip
Symfony2 nous permet de configurer le comportement de la librairie Swift Mailer lorsque
lon travaille dans des environnements de dveloppement Symfony2 diffrents. Nous pouvons
ds prsent voir cela dans lenvironnement test. Par dfaut, la distribution standard de
Symfony2 est configure de telle sorte que Swift Mailer nenvoie pas demails lorsque
lapplication est dans lenvironnement test. Cest dfini dans le fichier
app/config/config_test.yml.
# app/config/config_test.yml
swiftmailer:
disable_delivery: true

Il pourrait tre utile de dupliquer cette fonctionnalit pour lenvironnement dev. Aprs tout,
vous ne voulez pas envoyer accidentellement un email une adresse incorrecte au cours du
dveloppement. Pour cela, ajoutez la configuration ci dessus au fichier de configuration de
lenvironnement dev, dans app/config/config_dev.yml.
Vous vous demandez peut-tre comment il est possible que sassurer que les emails sont
envoys, et plus prcisment quel est leur contenu, tant donn quils ne sont plus envoys
une adresse email. Symfony2 propose une solution cela via la barre doutil de
dveloppement. Lorsquun email est envoy, une notification par email va apparatre dans la
barre doutils, qui contient toutes les informations propos de lemail que Swift Mailer aurait
dlivre.

Si vous ralisez une redirection aprs lenvoi dun email, comme nous le faisons avec le
formulaire de contact, vous devrez paramtrer la valeur de intercept_redirects dans
app/config/config_dev.yml true afin de voir les notifications denvoi demail dans la
barre doutils.
Nous aurions pu configurer Swift Mailer pour envoyer tous les emails une adresse
spcifique dans lenvironnement dev en plaant le code suivant dans le fichier de
configuration de correspondant, app/config/config_dev.yml.
# app/config/config_dev.yml
swiftmailer:
delivery_address: development@symblog.dev

Conclusion
Nous avons prsent les concepts derrire un des aspects les plus fondamentaux de nimporte
quel site web: les formulaires. Symfony2 propose une excellente librairie de formulaires et de
validateurs qui nous permet de sparer la logique de validation du formulaire de telle sorte
quelle puisse tre utilise ailleurs dans lapplication (dans le modle par exemple). Nous

avons galement vu comment mettre en place des paramtres de configuration personnaliss


qui peuvent tre utiliss dans lapplication. Enfin nous avons vu comment envoyer des emails
grce la librairie Swift Mailer.
Dans la prochaine partie, nous allons aborder un des grands axes de ce tutoriel, le modle.
Nous allons prsenter Doctrine 2 et nous en servir pour construire notre modle pour les
articles, construire la page daffichage des articles, et explorer le thme des donnes factices.

[Partie 3] - Le modle darticle : utilisation de Doctrine 2 et des donnes factices

Je propose galement des formations en petits groupes sur 2 3 jours, plus dinfos sur la page
ddie. Nhsitez pas me contacter (06.62.28.01.87 ou clement [@] keiruaprod.fr) pour en
discuter !
Introduction

Dans ce chapitre, nous allons commencer explorer le modle darticle. Ce modle sera
implment en utilisant lORM (pour Object Relation Mapper, soit Lien Objet-Relation)
Doctrine 2 . Doctrine 2 nous permet de faire persister nos objets PHP. Il propose galement un
dialecte SQL personnel appel DQL (pour Doctrine Query Language, ou Langage de
requtes de Doctrine). En plus de Doctrine 2, nous allons galement aborder le concept de
donnes factices (data fixtures). Cest un mcanisme permettant de peupler la base de donne
de lenvironnement de dveloppement et de test avec des donnes de test adquates. A la fin
de ce chapitre nous aurons dfini le modle darticle, mis jour la base de donne afin quelle
reflte ce changement, et cr des articles factices. Nous aurons galement construit les bases
de la page daffichage des article.
Doctrine 2: Le modle

Afin que notre blog fonctionne, il nous faut un moyen de faire persister les donnes. Doctrine
2 fournit une librairie dORM conue exactement dans cette optique. Doctrine 2 est conu au
dessus dune couche dabstraction de base de donne trs puissante qui la rend indpendante
de la base de donne utilise : cela permet dutiliser diffrents moteurs de stockage tels que
MySQL, PostgreSQL ou SQLite. Nous allons utiliser MySQL dans ce tutorial, mais nimporte
quel moteur peut tre utilis la place.
Tip
Si vous ntes pas familier avec les ORM, nous allons en dtailler le principe. La dfinition de
Wikipedia dit:
Un mapping objet-relationnel (en anglais object-relational mapping ou ORM) est une
technique de programmation informatique qui cre lillusion dune base de donnes oriente
objet partir dune base de donnes relationnelle en dfinissant des correspondances entre
cette base de donnes et les objets du langage utilis. On pourrait le dsigner par ``
correspondance entre monde objet et monde relationnel ``
Ce quun ORM facilite, cest la traduction des donnes dune base de donne
relationnelle en des objets PHP que lon peut manipuler. Cela permet
dencapsuler des oprations que lon souhaite raliser sur une table lintrieur
dune classe. Prenons lexemple dune table pour grer les utilisateurs. Elle
contiendra probablement des champs tels que le nom dutilisateur, son mot de
passe, son nom et sa date de naissance. Lorm va nous permettre dappeller des
mthodes getUsername() ou setPassword() dans la classe PHP. Les ORM vont bien

plus loin que cela nanmoins, ils permettent de retrouver des entits lies pour
nous, soit au moment o lon charge lentit utilisateur, soit de manire retarde
par la suite. Maintenant imaginons que notre utilisateur a des amis qui lui sont
lis. Il peut y avoir une table damis, o est stocke la cle primaire de la table
utilisateur. LORM nous permet dutiliser une mthode telle que $user>getFriends() pour rcuprer les objets de la table damis. Si cela ne suffit pas,
lORM se charge galement de la persistance, ce qui nous permet de crer des
objets PHP, dappeler une mthode de sauvegarde (du genre save()), et de
laisser lORM soccuper des dtails pour la sauvegarde dans la base de donnes.
Comme nous allons utiliser Doctrine 2 comme librairie dORM, vous allez devenir
plus laise avec cette notion au cours de ce tutoriel.

Note
Bien que dans ce tutorial nous utilisions Doctrine 2 comme librairie dORM, vous pourriez
opter pour la libraire Doctrine 2 ODM. il y a un certain nombre de variations de cette librairie,
ce qui inclut des implmentations pour MongoDB et CouchDB. Regardez la page de projets
Doctrine pour plus dinformations.
Il y a galement un article dans le cookbook qui explique comment mettre en place ODM
avec Symfony2.
Lentit darticle

Nous allons commencer par crer la classe entit Blog. NDT: cest le nom qua choisi lauteur
original de larticle pour la classe qui sert modliser un article. Nous avons dj parl des
entits dans le chapitre prcdent lorsque nous avons cr lentit Enquiry. Comme le but
dune entit est de stocker des donnes, il est logique den utiliser une pour reprsenter le
contenu dun article. En crant une entit, nous allons automatiquement lier ces donnes avec
la base de donnes. Avec lentit Enquiry, nous nous sommes content dutiliser les
informations par email au webmaster.
Crez un fichier dans src/Blogger/BlogBundle/Entity/Blog.php et collez-y le contenu
suivant.
<?php
// src/Blogger/BlogBundle/Entity/Blog.php
namespace Blogger\BlogBundle\Entity;
class Blog
{
protected $title;
protected $author;
protected $blog;
protected $image;

protected $tags;
protected $comments;
protected $created;
protected $updated;
}

Comme vous pouvez le voir, il sagit dune simple classe PHP. Elle na ni classe parente, ni
accesseurs. Les membres sont tous dclars en protected, il est donc impossible daccder
eux lorsque lon traite avec une instance de cette classe. Nous pourrions crire nous mme les
accesseurs, mais Doctrine 2 propose une commande capable de sen charger. En mme temps,
crire des accesseurs nest pas laspect le plus passionnant du projet.
Avant de lancer cette commande, il faut expliquer Doctrine 2 comment lentit Blog doit
tre associe la base de donne. Cela se fait via des mtadonnes qui peuvent tre dfinis
dans plusieurs formats: YAML, PHP, XML et Annotations. Nous allons utiliser les annotations
dans ce tutoriel. Il est important de noter que tous les membres de lentit nont pas besoin
dtre persists, nous ne prciserons donc pas de mtadonnes pour ceux qui sont dans cette
situation, ce qui nous donne la flexibilit de choisir les informations envoyer la base de
donnes. Remplacez le contenu de la classe Blog situ dans
src/Blogger/BlogBundle/Entity/Blog.php par le suivant :
<?php
// src/Blogger/BlogBundle/Entity/Blog.php
namespace Blogger\BlogBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="blog")
*/
class Blog
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="string")
*/
protected $title;
/**
* @ORM\Column(type="string", length=100)
*/
protected $author;
/**
* @ORM\Column(type="text")

*/
protected $blog;
/**
* @ORM\Column(type="string", length="20")
*/
protected $image;
/**
* @ORM\Column(type="text")
*/
protected $tags;
protected $comments;
/**
* @ORM\Column(type="datetime")
*/
protected $created;

/**
* @ORM\Column(type="datetime")
*/
protected $updated;

Tout dabord, on importe et cre un alias pour lespace de nom de Doctrine 2. Cela nous
permet dutiliser les annotations pour dcrire les mtadonnes des entits. Les mtadonnes
nous fournissent des informations sur la manire dont les membres sont reprsents dans la
base de donne.
Tip
Nous venons seulement de voir un petit sous ensemble des types dassociation que propose
Doctrine 2. Une liste complte est disponible sur le site web de Doctrine 2. Nous allons
utiliser dautres types dassociation plus tard dans ce tutoriel.
Loeil averti aura srement remarqu que lattribut $comments na pas de mtadonnes
associes. Nous ne souhaitons pas le faire persister dans la base de donnes. En effet, il
fournit seulement une liste des commentaires relatifs un article. Si lon pense en terme
dobjet et non de base de donnes, cela prend tout son sens, comme vous pouvez le voir dans
le bout de code suivant :
// Create a blog object.
$blog = new Blog();
$blog->setTitle("symblog - A Symfony2 Tutorial");
$blog->setAuthor("dsyph3r");
$blog->setBlog("symblog is a fully featured blogging website ...");
// Create a comment and add it to our blog
$comment = new Comment();
$comment->setComment("Symfony2 rocks!");
$blog->addComment($comment);

La portion de code ci-dessus illustre le comporte normal que lon pourrait souhaiter dune
classe darticle et de commentaires. En interne, la mthode $blog->addComment() pourrait
tre implmente comme ceci :
class Blog
{
protected $comments = array();

public function addComment(Comment $comment)


{
$this->comments[] = $comment;
}

La mthode addComment se contente dajouter un objet Comment la variable membre


$comments de larticle. Rcuprer les commentaires est alors trs simple :
class Blog
{
protected $comments = array();
public function getComments()
{
return $this->comments;
}
}

Comme on le voit, le membre $comments est simplement une liste dobjets Comment. Doctrine
2 ne change pas cette manire de fonctionner, mais va tre capable de remplir
automatiquement ce champ partir de lobjet blog.
Maintenant que nous avons dit Doctrine 2 comment associer les entits membres, voyons
comment gnrer les accesseurs :
$ php app/console doctrine:generate:entities Blogger

Aprs avoir lanc la commande prcdente, vous aurez remarqu que lentit Blog a t mise
jour avec lajout des accesseurs. A chaque fois que nous allons faire des changements aux
mtadonnes de lORM, il va falloir relancer cette commande pour mettre jour les
accesseurs. Ceux qui existent dj ne seront pas modifis, donc les accesseurs existants ne
seront pas remplacs par cette commande, cest important si jamais vous souhaitez
personnaliser par les suite les accesseurs.
Tip
Bien que nous ayons utilis les annotations dans notre entit, il est possible de convertir les
informations de mapping dans un autre format en utilisant la commande
doctrine:mapping:convert.
Par exemple, la commande suivante va convertir les associations dans les entits
ci-dessus au format yaml.

$ php app/console doctrine:mapping:convert


--namespace="Blogger\BlogBundle\Entity\Blog" yaml
src/Blogger/BlogBundle/Resources/config/doctrine

Cela va crer un fichier dans


src/Blogger/BlogBundle/Resources/config/doctrine/Blogger.BlogBundle.Entity.
Blog.orm.yml

qui va contenir les mappings en yaml de lentit blog.

La base de donnes
Cration de la base de donnes

Si vous avez suivi le chapitre 1 de ce tutoriel, vous avez d utiliser loutil de configuration
web pour rentrer les paramtres de la base de donne. Si vous ne lavez pas fait, mettez jour
les options database_* dans le fichier de configuration app/parameters.ini.
Il est maintenant temps de crer la base de donne en utilisant une autre commande Doctrine
2. Cette commande cre seulement la base de donnes, mais pas les tables lintrieur. Si une
base de donne du mme nom existe dj, une erreur sera affiche et la base de donne
existante restera intacte.
$ php app/console doctrine:database:create

Nous sommes maintenant prts pour crer la reprsentation de lentit Blog dans la base de
donnes. Il y a 2 moyens pour faire cela. Nous pouvons utiliser la commande schema de
Doctrine 2 pour mettre jour la base de donne, ou bien les nettement plus puissantes
migrations de Doctrine 2. Pour le moment, contentons nous de la commande schema, les
migrations seront abordes dans un chapitre ultrieur.
Cration de la table darticle

Pour crer la table blog dans notre base de donnes, on peut lancer la commande doctrine
suivante :
$ php app/console doctrine:schema:create

Cela excute le code SQL ncessaire la gnration du schma de la base de donne pour
lentit blog. Vous pouvez galement ajouter largument --dump-sql optionnellement afin
dafficher le code SQL gnr. Si vous regardez le contenu de votre base de donnes, vous
pourrez voir que la table blog a t cre, avec des champs qui correspondent ce que nous
avions spcifi.
Tip
Nous avons utilis un certain nombre de lignes de commandes Symfony2 jusqu prsent, et
dans une vrai console le format de commande permet toujours dobtenir de laide en ajoutant
loption --help. Symfony2 nchappe pas cette rgle: pour voir laide relative la
commande doctrine:schema:create, excutez la ligne suivante :
$ php app/console doctrine:schema:create --help

Les informations daide vont alors afficher lusage et les options disponible. La plupart des
commandes proposent un grand nombre doptions permettant de personnaliser lexcution
dune commande.
Intgration du Modle avec la Vue : affichage dun article

Maintenant que lentit Blog a t cre et que la base de donne le reflte, nous pouvons
commencer intgrer le modle dans la vue. Nous allons commencer par construire la page
daffichage des articles de notre blog.
La route daffichage dun article

Nous allons commencer par crer une route pour laction show. Un article va tre caractris
par un identifiant unique, cet identifiant se doit donc dtre prsent dans lURL. Mettez jour
les rgles de routage du BloggerBlogBundle dans
src/Blogger/BlogBundle/Resources/config/routing.yml en y ajoutant ce qui suit: with
the following
# src/Blogger/BlogBundle/Resources/config/routing.yml
BloggerBlogBundle_blog_show:
pattern: /{id}
defaults: { _controller: BloggerBlogBundle:Blog:show }
requirements:
_method: GET
id: \d+

Comme lidentifiant de larticle sera prsent dans lURL, nous avons ajout un lment id
dans la route. Sans plus de dtail, cela signifie que les adresses http://symblog.co.uk/1 et
http://symblog.co.uk/my-blog valident toutes les deux la route. Comme nous savons que
lidentifiant est un entier (cest ce que lon a dfini dans le mapping), on peut ajouter une
contrainte qui ne valide la route que si le paramtre id est un entier. Cest ralis grce la
ligne id: \d+ dans la section requirements, qui dfinit les conditions valider. Maintenant,
seule la premire adresse serait valide. Vous pouvez galement voir que lorsque ladresse
valide cette route, cest la mthode show du contrleur Blog du BloggerBlogBundle qui est
execute. Il ne reste plus qu crer le contrleur Blog, cest ce que nous allons faire tout de
suite.
Laction Show du Contrleur

Le lien entre le Modle et la Vue, cest le Contrleur, cest donc l que nous allons
commencer crer la page daffichage. Nous pourrions ajouter laction show notre
contrleur Page dj existant, mais comme cette page se contente dafficher les entits blog,
cela a plus de sens de le mettre dans un contrleur part.
Crez un nouveau fichier dans
src/Blogger/BlogBundle/Controller/BlogController.php

et collez-y le code suivant :

<?php
// src/Blogger/BlogBundle/Controller/BlogController.php

namespace Blogger\BlogBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
/**
* Blog controller.
*/
class BlogController extends Controller
{
/**
* Show a blog entry
*/
public function showAction($id)
{
$em = $this->getDoctrine()->getEntityManager();
$blog = $em->getRepository('BloggerBlogBundle:Blog')->find($id);
if (!$blog) {
throw $this->createNotFoundException('Unable to find Blog
post.');

}
return $this->render('BloggerBlogBundle:Blog:show.html.twig',

array(
}

'blog'

=> $blog,

));

Nous avons cr un nouveau contrleur pour lentit Blog, et y avons dfini une action show.
Comme nous avons spcifi un paramtre id pour la route BloggerBlogBundle_blog_show,
ce paramtre sera pass en argument la mthode showAction. Si nous avions pass
plusieurs paramtres dans la rgle de routage, ils auraient galement t fournis sous la forme
darguments spars.
Tip
Les actions du contrleur fournissent galement un objet de type
Symfony\Component\HttpFoundation\Request si vous le spcifiez parmi les paramtres.
Cela peut tre utile lorsque lon traite avec les formulaires. Nous en avons dj utilis dans le
chapitre 2, mais nous ne nous sommes pas servis de cette mthode car nous avons utilis une
des mthodes daide du contrleur de base
Symfony\Bundle\FrameworkBundle\Controller\Controller comme suit :
// src/Blogger/BlogBundle/Controller/PageController.php
public function contactAction()
{
// ..
$request = $this->getRequest();
}

Nous aurions trs bien pu crire ce code de la manire suivante :


// src/Blogger/BlogBundle/Controller/PageController.php

use Symfony\Component\HttpFoundation\Request;
public function contactAction(Request $request)
{
// ..
}

Les deux mthodes ralisent la mme tche, mais si votre controlleur ntendait pas la classe
de base Symfony\Bundle\FrameworkBundle\Controller\Controller, vous ne pourriez
pas utiliser la premire mthode.
Il nous faut ensuite rcuprer les entits Blog dans la base de donnes. Nous utilisons pour
cela une seconde mthode de la classe
Symfony\Bundle\FrameworkBundle\Controller\Controller pour obtenir le gestionnaire
dentits de Doctrine 2. Le but du gestionnaire dentits est de rcuprer les objets venant de
la base de donnes, et de les y faire persister. Nous utilisons ensuite lobjet EntityManger
pour obtenir le Repository de Doctrine2 pour lentit BloggerBlogBundle:Blog. La
syntaxe spcifie ici est simplement un raccourci qui peut tre utilis avec Doctrine 2 au lieu
de prciser le nom entier, cest dire Blogger\BlogBundle\Entity\Blog. Avec le dpot
dobjets (le repository), nous appelons la mthode find() avec pour argument la variable
$id. Cette mthode se charge de retrouver tous les objets partir de leur cl primaire.
Enfin, nous vrifions quune entit a t trouv, et fournissons cette entit la vue. Si aucune
entit nest trouve, une exception est lance, qui va se charger de gnrer une erreur 404.
Tip
Lobjet repository (le dpot dobjet) nous donne accs un certain nombre de mthodes
auxilliaires utiles, telles que :
// Renvoit les entit dont l'attribut 'author' vaut 'dsyph3r'
$em->getRepository('BloggerBlogBundle:Blog')->findBy(array('author' =>
'dsyph3r'));
// Renvoit une entit dont l'attribut 'slug' vaut 'symblog-tutorial'
$em->getRepository('BloggerBlogBundle:Blog')->findOneBySlug('symblogtutorial');

Nous allons par la suite crer nos propres classes de repository dans le chapitre suivant,
lorsque nous aurons besoin deffectuer des requtes plus complexes.
La vue

Maintenant que nous avons construit laction show pour le contrleur Blog, nous pouvons
nous concenter sur laffichage des entits Blog. Comme prcis dans laction show, le
template BloggerBlogBundle:Blog:show.html.twig sera affich. Commenons par crer
ce fichier, dans src/Blogger/BlogBundle/Resouces/views/Blog/show.html.twig, et
ajoutons y le code qui suit :
{# src/Blogger/BlogBundle/Resouces/views/Blog/show.html.twig #}
{% extends 'BloggerBlogBundle::layout.html.twig' %}

{% block title %}{{ blog.title }}{% endblock %}


{% block body %}
<article class="blog">
<header>
<div class="date"><time datetime="{{ blog.created|
date('c') }}">{{ blog.created|date('l, F j, Y') }}</time></div>
<h2>{{ blog.title }}</h2>
</header>
<img src="{{ asset(['images/', blog.image]|join) }}"
alt="{{ blog.title }} image not found" class="large" />
<div>
<p>{{ blog.blog }}</p>
</div>
</article>
{% endblock %}

Comme vous lattendiez, nous commenons par tendre le template principale du


BloggerBlogBundle. Ensuite, on remplace le titre de la page pour avoir la place celui de
larticle. Cest utile pour le SEO (Search Engine Optimization: lensemble de techniques qui
ont pour but damliorer les rsultats dans les moteurs de recherche), car la page de titre de
larticle dcrit plus spcifiquement le contenu de cette page que le titre gnrique que nous
avons mis par dfaut. Enfin, on remplace le corps de la page pour afficher le contenu de
lentit Blog. Nous utilisons la fonction asset nouveau pour afficher limage de larticle.
Les images devraient tre places dans le rpertoire web/images.
CSS

Afin que la page daffichage des articles soit visuellement agrable, il faut lui ajouter du style.
Mettez jour la feuille de style dans
src/Blogger/BlogBundle/Resouces/public/css/blog.css avec le contenu suivant :
.date
24px;
.blog
.blog
#ccc;
.blog
.blog
.blog
.blog
.blog

{ margin-bottom: 20px; border-bottom: 1px solid #ccc; font-size:


color: #666; line-height: 30px }
{ margin-bottom: 20px; }
img { width: 190px; float: left; padding: 5px; border: 1px solid
margin: 0 10px 10px 0; }
.meta { clear: left; margin-bottom: 20px; }
.snippet p.continue { margin-bottom: 0; text-align: right; }
.meta { font-style: italic; font-size: 12px; color: #666; }
.meta p { margin-bottom: 5px; line-height: 1.2em; }
img.large { width: 300px; min-height: 165px; }

Note
Si vous nutilisez pas la mthode symlink pour rfrencer les ressources utilises dans le
dossier web, vous devez la commande dinstallation des ressource pour mettre jour les
changements qui ont eu lieu dans la feuille de style.
$ php app/console assets:install web

Comme nous avons maintenant construit le contrleur et la vue pour laction show, allons
jeter un oeil la page que nous venons de crer. Rendez vous avec votre navigateur
ladresse http://symblog.dev/app_dev.php/1. Ce nest probablement pas la page que
vous attendiez...

Symfony2 a gnr une erreur 404. Comme il ny a rien dans la base de donnes, il ny a pas
dentit ayant pour id la valeur 1.
Vous pourriez simplement ajouter un lment dans la table blog de votre base de donnes,
mais nous allons faire mieux. Nous servir de donnes factices, galement appeles les data
fixtures.
Donnes factices

On peut utiliser des fixtures pour remplir la base de donne avec des donnes de test. Pour
cela, nous allons utiliser lextension Doctrine Fixtures extension and bundle. Cette
extension nest pas disponible de base avec ldition standard de Symfony2, nous allons
devoir linstaller manuellement. Heureusement, cest facile faire. Ouvrez le fichier deps la

racine du projet, et ajoutez y la nouvelle extension la suite de celles dj prsentes et


ajoutant ceci:
[doctrine-fixtures]
git=http://github.com/doctrine/data-fixtures.git
[DoctrineFixturesBundle]
git=http://github.com/symfony/DoctrineFixturesBundle.git
target=/bundles/Symfony/Bundle/DoctrineFixturesBundle

Maintenant, mettez jour les vendors pour que les changements soient pris en compte.
$ php bin/vendors install

Cela va tlcharger les dernires versions disponible sur Github de chacun des bundles, et les
installer au bon endroit.
Note
Si vous tes sur une machine o Git nest pas install, vous devrez tlcharger et installer
manuellement lextension.
Pour lextension doctrine-fixtures: Tlchargez la version actuelle disponible sur Github, et
dcompressez son contenu dans vendor/doctrine-fixtures.
Pour le DoctrineFixturesBundle: Tlchargez la version actuelle disponible sur Github, et
dcompressez son contenu dans
vendor/bundles/Symfony/Bundle/DoctrineFixturesBundle.
Mettez ensuite jour le fichier app/autoloader.php pour enregistrer les nouveaux espaces
de noms. Comme les DataFixtures sont galement dans lespace de nom Doctrine\Common,
ils doivent tre plac avant la directive Doctrine\Common dj existante, puisquelle prcisent
un nouveau chemin. Les espaces de noms sont vrifis de haut en bas, donc les espaces de
noms les plus prcis doivent tre enregistrs avant ceux qui le sont moins.
// app/autoloader.php
// ...
$loader->registerNamespaces(array(
// ...
'Doctrine\\Common\\DataFixtures'
=> __DIR__.'/../vendor/doctrinefixtures/lib',
'Doctrine\\Common'
=> __DIR__.'/../vendor/doctrinecommon/lib',
// ...
));
Maintenant enregistrons le DoctrineFixturesBundle dans le noyeau situ dans
app/AppKernel.php
// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
// ...

new Symfony\Bundle\DoctrineFixturesBundle\DoctrineFixturesBundle(),
// ...

);
// ...

Articles factices

Nous sommes maintenant prts dfinir du contenu factice pour nos articles. Crez un fichier
de fixtures dans src/Blogger/BlogBundle/DataFixtures/ORM/BlogFixtures.php et
ajoutez-y le contenu suivant :
<?php
// src/Blogger/BlogBundle/DataFixtures/ORM/BlogFixtures.php
namespace Blogger\BlogBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Blogger\BlogBundle\Entity\Blog;
class BlogFixtures implements FixtureInterface
{
public function load($manager)
{
$blog1 = new Blog();
$blog1->setTitle('A day with Symfony2');
$blog1->setBlog('Lorem ipsum dolor sit amet, consectetur adipiscing
eletra electrify denim vel ports.\nLorem ipsum dolor sit amet, consectetur
adipiscing elit. Morbi ut velocity magna. Etiam vehicula nunc non leo
hendrerit commodo. Vestibulum vulputate mauris eget erat congue dapibus
imperdiet justo scelerisque. Nulla consectetur tempus nisl vitae viverra.
Cras el mauris eget erat congue dapibus imperdiet justo scelerisque. Nulla
consectetur tempus nisl vitae viverra. Cras elementum molestie vestibulum.
Morbi id quam nisl. Praesent hendrerit, orci sed elementum lobortis, justo
mauris lacinia libero, non facilisis purus ipsum non mi. Aliquam
sollicitudin, augue id vestibulum iaculis, sem lectus convallis nunc, vel
scelerisque lorem tortor ac nunc. Donec pharetra eleifend enim vel
porta.');
$blog1->setImage('beach.jpg');
$blog1->setAuthor('dsyph3r');
$blog1->setTags('symfony2, php, paradise, symblog');
$blog1->setCreated(new \DateTime());
$blog1->setUpdated($blog1->getCreated());
$manager->persist($blog1);
$blog2 = new Blog();
$blog2->setTitle('The pool on the roof must have a leak');
$blog2->setBlog('Vestibulum vulputate mauris eget erat congue
dapibus imperdiet justo scelerisque. Na. Cras elementum molestie
vestibulum. Morbi id quam nisl. Praesent hendrerit, orci sed elementum
lobortis.');
$blog2->setImage('pool_leak.jpg');
$blog2->setAuthor('Zero Cool');
$blog2->setTags('pool, leaky, hacked, movie, hacking, symblog');
$blog2->setCreated(new \DateTime("2011-07-23 06:12:33"));
$blog2->setUpdated($blog2->getCreated());
$manager->persist($blog2);
$blog3 = new Blog();
$blog3->setTitle('Misdirection. What the eyes see and the ears
hear, the mind believes');

$blog3->setBlog('Lorem ipsumvehicula nunc non leo hendrerit


commodo. Vestibulum vulputate mauris eget erat congue dapibus imperdiet
justo scelerisque.');
$blog3->setImage('misdirection.jpg');
$blog3->setAuthor('Gabriel');
$blog3->setTags('misdirection, magic, movie, hacking, symblog');
$blog3->setCreated(new \DateTime("2011-07-16 16:14:06"));
$blog3->setUpdated($blog3->getCreated());
$manager->persist($blog3);
$blog4 = new Blog();
$blog4->setTitle('The grid - A digital frontier');
$blog4->setBlog('Lorem commodo. Vestibulum vulputate mauris eget
erat congue dapibus imperdiet justo scelerisque. Nulla consectetur tempus
nisl vitae viverra.');
$blog4->setImage('the_grid.jpg');
$blog4->setAuthor('Kevin Flynn');
$blog4->setTags('grid, daftpunk, movie, symblog');
$blog4->setCreated(new \DateTime("2011-06-02 18:54:12"));
$blog4->setUpdated($blog4->getCreated());
$manager->persist($blog4);
$blog5 = new Blog();
$blog5->setTitle('You\'re either a one or a zero. Alive or dead');
$blog5->setBlog('Lorem ipsum dolor sit amet, consectetur adipiscing
elittibulum vulputate mauris eget erat congue dapibus imperdiet justo
scelerisque.');
$blog5->setImage('one_or_zero.jpg');
$blog5->setAuthor('Gary Winston');
$blog5->setTags('binary, one, zero, alive, dead, !trusting, movie,
symblog');
$blog5->setCreated(new \DateTime("2011-04-25 15:34:18"));
$blog5->setUpdated($blog5->getCreated());
$manager->persist($blog5);
}

$manager->flush();

Ce fichier contient un certain nombre de choses importantes savoir lorsque lon utilise
Doctrine 2, en particulier sur comment faire persister les entits dans la base de donnes.
Regardons comment on cre un article :
$blog1 = new Blog();
$blog1->setTitle('A day in paradise - A day with Symfony2');
$blog1->setBlog('Lorem ipsum dolor sit d us imperdiet justo scelerisque.
Nulla consectetur...');
$blog1->setImage('beach.jpg');
$blog1->setAuthor('dsyph3r');
$blog1->setTags('symfony2, php, paradise, symblog');
$blog1->setCreated(new \DateTime());
$blog1->setUpdated($this->getCreated());
$manager->persist($blog1);
// ..
$manager->flush();

On commence par crer une instance de la classe Blog, et spcifie les valeurs pour ses
attributs. A cet instant, Doctrine 2 ne connait rien de lobjet Entity. Cest seulement lors de

lappel de $manager->persist($blog1) que Doctrine 2 prend en charge les objets entit.


Lobjet $manager est ici une instance de EntityManager que nous avons vu plus tt, lorsque
nous allons chercher des objets entit dans la base de donnes. Il est important de noter que
bien que Doctrine 2 soit dsormais en charge de lobjet entit, cet objet nest toujours pas
persist dans la base de donne. Un appel la mthode $manager->flush() est ncessaire
pour cela. La mthode flush oblige Doctrine 2 intragir avec la base de donne pour toute
les entits dont il soccupe. Par souci de performance, il est ncessaire de regrouper les appels
Doctrine 2 et raliser un unique flush, cest comme a que nous avons fait avec nos donnes
factices. On cre chaque entit, on dit Doctrine 2 quil en a la charge, et finalement on
sauvegarde toutes les entits en une fois la fin via flush.
Charger les donnes factices

Nous sommes maintenant prt pour charger les donnes factices dans la base de donnes.
$ php app/console doctrine:fixtures:load
Si vous regardez la page http://symblog.dev/app_dev.php/1,

voir un article.

vous devriez maintenant y

Essayez de changer la valeur du paramtre id dans lURL pour la valeur 2. Vous devriez alors
voir larticle suivant.
Si toutefois vous vous rendez ladresse http://symblog.dev/app_dev.php/100 vous
devriez avoir une erreur 404, car il nexiste pas dentit ayant pour id la valeur 100.
Maintenant, essayez lURL http://symblog.dev/app_dev.php/symfony2-blog. Pourquoi
navons nous pas droit une erreur 404 ? Car laction show nest jamais execute. Lurl
narrive pas faire correspondre cette adresse une rgle de routage ( cause de la ncessit
pour lidentifiant des articles dtre un entier), cest pourquoi on a la place une exception qui
dit: il nexiste pas de route pour cette adresse - No route found for "GET /symfony2blog".
Les Timestamps

Le terme le plus proche de Timestamp en franais tant linfame Horodatage, je vais


continuer dutiliser le terme Timestamp. En gros, un timestamp, cest un attribut qui sert
stocker une information sur une date. Pour finir ce chapitre, nous allons regarder les 2
timestamps de lentit Blog; created et updated. Les fonctionnalits de ces 2 attributs sont
communment dfinies comme Timestampable. Promis, je vais quand mme continuer de
faire un effort pour les traductions, mais ne mobligez pas dire horodatables. Ces attributs
stockent les informations sur la date et lheure auxquelles un article a t cr, puis mis jour
pour la dernire fois. Comme nous ne souhaitons pas mettre jour ce champ manuellement
chaque cration ou mise jour darticle, nous allons nous reposer sur Doctrine.
Doctrine 2 propose un systme dvnements qui fournit des callback de cycle de vie.
On peut utiliser ces callback pour prciser que nos entits doivent tre averties de certains
vnements. Il est par exemple possible dtre prvenu avant la mise jour dune entit, aprs
une sauvegarde ou avant la suppression dune entit. Afin dutiliser ces callback, il est
ncessaire de marquer les entits, ce que lon fait dans les mtadonnes. Mettez jour lentit
Blog dans src/Blogger/BlogBundle/Entity/Blog.php avec le contenu suivant :
<?php
// src/Blogger/BlogBundle/Entity/Blog.php
// ..
/**
* @ORM\Entity
* @ORM\Table(name="blog")
* @ORM\HasLifecycleCallbacks()
*/
class Blog
{
// ..
}

Maintenant ajoutons une mthode dans lentit Blog qui enregistre lvnement preUpdate.
Nous ajoutons galement un constructeur pour dfinir les valeurs par dfaut des attributs
created et updated.
<?php
// src/Blogger/BlogBundle/Entity/Blog.php
// ..
/**
* @ORM\Entity
* @ORM\Table(name="blog")
* @ORM\HasLifecycleCallbacks()
*/
class Blog
{
// ..
public function __construct()
{
$this->setCreated(new \DateTime());

$this->setUpdated(new \DateTime());
}
/**
* @ORM\preUpdate
*/
public function setUpdatedValue()
{
$this->setUpdated(new \DateTime());
}
}

// ..

On enregistre lentit Blog afin dtre notifi de lvnement preUpdate, utilis pour mettre
jour la valeur de updated. Maintenant, en relanant la commande de chargement des donnes
factices, vous allez voir que les valeurs des 2 attributs ont t affectes automatiquement.
Tip
Comme les attributs timestampables sont un besoin rcurrent dans les entits, un bundle est
apparu pour ajouter son support. Il sagit du StofDoctrineExtensionsBundle, qui fournit
plusieurs extensions pour Doctrine 2 intressante comme Timestampable, Sluggable, and
Sortable (triable).
Nous verrons comment intgrer ce bundle plus loin dans le tutoriel. Les plus presss peuvent
dj regarder la page du cookbook ce sujet.
Conclusion

Nous avons couvert un certain nombre de concepts qui traitent du Modle avec Doctrine 2.
Nous avons galement regard comment gnrer des donnes factices, qui propose une
solution simple pour avoir des donnes de test pour la priode de dveloppement et de test.
La prochaine fois, nous regarderons comment tendre le modle pour y ajouter le support des
commentaires. Nous allons galement commencer construire la page daccueil, et construire
un dpt personnalis pour cela. Nous parlerons galement des migrations avec Doctrine,
ainsi que des intractions entre les formulaires et cette librairie pour permettre lajout de
commentaires aux articles.

[Partie 4] - Le modle de commentaires : ajouter des commentaires, dpts


Doctrine 2 et migrations.

Je propose galement des formations en petits groupes sur 2 3 jours, plus dinfos sur la page
ddie. Nhsitez pas me contacter (06.62.28.01.87 ou clement [@] keiruaprod.fr) pour en
discuter !
Introduction

Dans ce chapitre, nous allons amliorer les articles que nous avons crs au chapitre prcdent
en ajoutant la possibilit dy mettre des commentaires. Nous allons pour cela crer le modle
de commentaires, qui va stocker les commentaires de chaque article. Nous allons galement
parler des relations entre les modles, car un article peut en effet contenir plusieurs
commentaires. Nous utiliserons les dpts ainsi que le moteur de construction de requtes de
Doctrine 2 pour rcuprer les entits depuis la base de donnes. Nous allons galement
voquer le thme des migrations Doctrine 2, qui permettent, par la programmation, de
dployer des changements dans une base de donne. A la fin de ce chapitre, nous aurons cr
le modle de commentaires, que nous aurons li celui des articles. Nous aurons galement
mis jour la page daccueil, et aurons fourni aux utilisateurs la possibilit de commenter les
articles.
La page daccueil

Commenons par construire la page daccueil. Comme tout blog qui se respecte, il faut
afficher un bout de chaque article, du plus rcent au plus ancien. Larticle complet sera
disponible par un lien vers une page cet effet. Comme nous avons dj construit une route
pour laffichage dun article, et que nous disposons dun contrleur et dune vue pour la page
daccueil, il suffit de les mettre jour.
Rcuprer un article : requte sur le modle

Afin dafficher les articles, nous devons les rcuprer depuis la base de donne. Doctrine 2
utilise le langage langage de requtes Doctrine (pour Doctrine Query Language, ou DQL)
ainsi quun systme de construction de requtes (QueryBuilder) pour cel. Vous pouvez bien
videmment utiliser du SQL pr avec Doctrine 2, mais cest fortement dcourag, car cel
retire labstraction que Doctrine nous fournit. Nous allons utiliser le QueryBuilder, car il nous
fournit une manire objet sympathique pour effectuer nos requtes sur la base de donne.
Nous allons mettre jour laction index du contrleur Page dans
src/Blogger/BlogBundle/Controller/PageController.php pour rcuprer les articles de la base
de donne.
// src/Blogger/BlogBundle/Controller/PageController.php
class PageController extends Controller
{
public function indexAction()
{
$em = $this->getDoctrine()
->getEntityManager();

$blogs = $em->createQueryBuilder()
->select('b')
->from('BloggerBlogBundle:Blog', 'b')
->addOrderBy('b.created', 'DESC')
->getQuery()
->getResult();
return $this->render('BloggerBlogBundle:Page:index.html.twig',
array(
}

'blogs' => $blogs


));

// ..
}

On commence par obtenir une instance du QueryBuilder partir de EntityManager. Cel nous
permet de commencer construire la requte partir des nombreuses mthodes que le
QueryBuilder propose. Une liste complte de ces mthodes est disponible dans la
documentation du QueryBuilder. Un bon point de dpart, cest regarder les mthodes
dassistance mthodes dassistance. Il sagit des mthodes que nous allons utiliser, tel que
select(), from() et addOrderBy(). Comme avec les intractions prcdentes avec Doctrine 2,
nous pouvons utiliser la notation raccourcie pour faire rfrence lentit Blog via
BloggerBlogBundle:Blog (souvenez vous que cest la mme chose que mettre
Blogger\BlogBundle\Entity\Blog). Une fois quon a fini de spcifier les critres de la requte,
on appelle getQuery() qui renvoit une instance de DQL. Nous ne pouvons pas obtenir de
rsultats depuis lobjet QueryBuilder: il faut passer par une instance de DQL dabord, qui
propose une mthode getResult() en charge de nous renvoyer une liste dentits de Blog. Nous
verrons par la suite que cette instance DQL propose plusieurs mthodes pour renvoyer les
rsultats tels que getSingleResult() et getArrayResult().
La vue

Maintenant que nous avons une liste dentit Blog, il faut les afficher. Remplacez le contenu
du template de la page daccueil situ dans
src/Blogger/BlogBundle/Resources/views/Page/index.html.twig par ce qui suit :
{# src/Blogger/BlogBundle/Resources/views/Page/index.html.twig #}
{% extends 'BloggerBlogBundle::layout.html.twig' %}
{% block body %}
{% for blog in blogs %}
<article class="blog">
<div class="date"><time datetime="{{ blog.created|
date('c') }}">{{ blog.created|date('l, F j, Y') }}</time></div>
<header>
<h2><a href="{{ path('BloggerBlogBundle_blog_show', { 'id':
blog.id }) }}">{{ blog.title }}</a></h2>
</header>
<img src="{{ asset(['images/', blog.image]|join) }}" />
<div class="snippet">
<p>{{ blog.blog(500) }}</p>

<p class="continue"><a
href="{{ path('BloggerBlogBundle_blog_show', { 'id':
blog.id }) }}">Continue reading...</a></p>
</div>
<footer class="meta">
<p>Comments: -</p>
<p>Posted by <span class="highlight">{{blog.author}}</span>
at {{ blog.created|date('h:iA') }}</p>
<p>Tags: <span class="highlight">{{ blog.tags }}</span></p>
</footer>
</article>
{% else %}
<p>There are no blog entries for symblog</p>
{% endfor %}
{% endblock %}

Nous utilisons ici une des structures de contrle de Twig, la structure for..else..endfor. Si vous
navez pas encore utilis de moteur de template, vous reconnaitrez peut tre ce genre de bout
de code :
<?php if (count($blogs)): ?>
<?php foreach ($blogs as $blog): ?>
<h1><?php echo $blog->getTitle() ?><?h1>
<!-- rest of content -->
<?php endforeach ?>
<?php else: ?>
<p>There are no blog entries</p>
<?php endif ?>

La structure de contrle for..else..endfor de Twig est une manire bien plus propre de raliser
ceci. La plupart du code dans le template de la page daccueil se charge dafficher les
informations sur larticle en HTML. Nanmoins, il y a plusieurs points noter. Tout dabord,
nous utilisons la fonction Twig path pour gnrer ladresse vers la page daffichage des
articles. Comme la route a besoin dun id dans lURL pour tre gnre, nous le passons en
argument, comme dans lexemple suivant :
<h2><a href="{{ path('BloggerBlogBundle_blog_show', { 'id':
blog.id }) }}">{{ blog.title }}</a></h2>

Ensuite, nous affichons le contenu de larticle avec <p>{{ blog.blog(500) }}</p>. La valeur
500 que nous fournissons en argument est la longueur maximum de larticle que nous voulons
afficher. Afin que cela fonctionne, nous devons mettre jour la mthode getBlog que Doctrine
2 a gnr pour nous. Mettez jour la mthode getBlog de lentit Blog dans
src/Blogger/BlogBundle/Entity/Blog.php avec ce qui suit :
// src/Blogger/BlogBundle/Entity/Blog.php
public function getBlog($length = null)
{
if (false === is_null($length) && $length > 0)
return substr($this->blog, 0, $length);
else
return $this->blog;
}

Comme le comportement habituel de la mthode getBlog est de renvoyer le contenu complet


de larticle, on dfinit une valeur par dfaut pour le paramtre $length null. Si le paramtre
null est pass en paramtres, le contenu complet de larticle est affich.

Si vous vous rendez maintenant ladresse http://symblog.dev/app_dev.php/, vous devriez


voir que la page daccueil affiche les derniers articles du blog. Vous devriez galement
pouvoir naviguer vers les articles complets en cliquant sur leur titre ou sur le lien continue
reading....

Bien que nous pouvons effectuer nos requtes dentits dans le contrleur, ce nest pas le
meilleur endroit pour faire cel. Les requtes seraient bien mieux en dehors du contrleur
pour plusieurs raisons :
1. Nous serions dans limpossibilit de rutiliser des requtes ailleurs dans
lapplication sans duppliquer du code utilisant le QueryBuilder.
2. En dupliquant du code du QueryBuilder, si une requte change, il y a
plusieurs modifications faire pour rpercuter le changement, ce qui est
source derreurs.
3. En sparant la requte et le contrleur, on devient capable de tester les
requtes indpendamment du contrleur.

Doctrine 2 nous propose des classes de dpt (repository) pour cela.


Les dpts Doctrine 2

Nous avons dj parl des dpts dans le chapitre prcdent lorsquil tait question de la page
daffichage des articles. Nous avons utilis limplmentation par dfaut de la
classe``DoctrineORMEntityRepository`` pour rcuprer une entit du blog via ma mthode
find(). Comme nous voulons crer une requte particulire, nous devons personnaliser un
dpt. Doctrine 2 va nous aider dans cette tche. Mettez jour les mtadonnes de lentit
Blog dans le fichier src/Blogger/BlogBundle/Entity/Blog.php.
// src/Blogger/BlogBundle/Entity/Blog.php
/**
*
@ORM\Entity(repositoryClass="Blogger\BlogBundle\Repository\BlogRepository")
* @ORM\Table(name="blog")
* @ORM\HasLifecycleCallbacks()
*/
class Blog
{
// ..
}

Vous pouvez voir que nous avons prcis lespace de nom pour la classe BlogRepository
associe cette entit. Comme nous venons de mettre jour les mtadonnes de lentit Blog,
il faut relancer la commande doctrine:generate:entities comme suit :
$ php app/console doctrine:generate:entities Blogger

Doctrine 2 va alors crer une classe vide pour notre BlogRepository dans
src/Blogger/BlogBundle/Repository/BlogRepository.php.

<?php
// src/Blogger/BlogBundle/Repository/BlogRepository.php
namespace Blogger\BlogBundle\Repository;
use Doctrine\ORM\EntityRepository;
/**
* BlogRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class BlogRepository extends EntityRepository
{
}

La classe BlogRepository tends la classe EntityRepository qui propose la mthode find()


dont nous parlions plus tt. Mettons jour la classe BlogRepository, en dplacant le code du
QueryBuilder du contrleur de Page dedans.
<?php
// src/Blogger/BlogBundle/Repository/BlogRepository.php
namespace Blogger\BlogBundle\Repository;
use Doctrine\ORM\EntityRepository;
/**
* BlogRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class BlogRepository extends EntityRepository
{
public function getLatestBlogs($limit = null)
{
$qb = $this->createQueryBuilder('b')
->select('b')
->addOrderBy('b.created', 'DESC');
if (false === is_null($limit))
$qb->setMaxResults($limit);

return $qb->getQuery()
->getResult();

Nous avons cr la mthode getLatestBlogs qui va nous renvoyer les derniers articles du blog,
de la mme manire que le faisait le code du QueryBuilder. Dans la classe du repository nous
avons un accs direct au QueryBuilder via la mthode createQueryBuilder(). Nous avons
galement ajout un paramtre par dfaut $limit afin de pouvoir limiter le nombre de rsultats
renvoyer. Le reste ressemble beaucoup ce quil y avait dans le contrleur. Vous avez peut
tre remarqu que nous navons pas besoin de prciser quelle entit utiliser dans la mthode
from(). Cest parce que nous sommes dans le BlogRepository, qui est associt lentit Blog.

Si lon regarde limplmentation de la mthode createQueryBuilder de la classe


EntityRepository, on peut voir que la mthode from() est appele pour nous.
// Doctrine\ORM\EntityRepository
public function createQueryBuilder($alias)
{
return $this->_em->createQueryBuilder()
->select($alias)
->from($this->_entityName, $alias);
}

Mettons enfin jour laction index du contrleur de Page afin de nous servir du
BlogRepository.
// src/Blogger/BlogBundle/Controller/PageController.php
class PageController extends Controller
{
public function indexAction()
{
$em = $this->getDoctrine()
->getEntityManager();
$blogs = $em->getRepository('BloggerBlogBundle:Blog')
->getLatestBlogs();
return $this->render('BloggerBlogBundle:Page:index.html.twig',
array(
}

'blogs' => $blogs


));

// ..
}

Si vous rafraichissez la page daccueil, rien naura chang : nous venons simplement de
refactorer notre code, cest dire que nous lavons rorganis afin que chaque classe fasse ce
quelle est cense faire.
Plus sur le modle : cration de lentit de commentaire

Les articles, cest seulement la moiti du travail quand il est question de blogguer. Nous
devons galement permettre aux lecteurs de les commenter. Ces commentaires doivent
galement tre sauvegards et lis lentit Blog car un article peut contenir plusieurs
commentaires.
Nous allons commencer par poser les bases de la classe de lentit de commentaire Comment.
Crez un fichier dans src/Blogger/BlogBundle/Entity/Comment.php et collez-y le code
suivant :
<?php
// src/Blogger/BlogBundle/Entity/Comment.php
namespace Blogger\BlogBundle\Entity;
use Doctrine\ORM\Mapping as ORM;

/**
*
@ORM\Entity(repositoryClass="Blogger\BlogBundle\Repository\CommentRepositor
y")
* @ORM\Table(name="comment")
* @ORM\HasLifecycleCallbacks()
*/
class Comment
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="string")
*/
protected $user;
/**
* @ORM\Column(type="text")
*/
protected $comment;
/**
* @ORM\Column(type="boolean")
*/
protected $approved;
/**
* @ORM\ManyToOne(targetEntity="Blog", inversedBy="comments")
* @ORM\JoinColumn(name="blog_id", referencedColumnName="id")
*/
protected $blog;
/**
* @ORM\Column(type="datetime")
*/
protected $created;
/**
* @ORM\Column(type="datetime")
*/
protected $updated;
public function __construct()
{
$this->setCreated(new \DateTime());
$this->setUpdated(new \DateTime());
$this->setApproved(true);
}
/**
* @ORM\preUpdate
*/
public function setUpdatedValue()
{
$this->setUpdated(new \DateTime());

}
}

La plupart des choses que vous voyez ici ont dj t abordes dans le chapitre prcdent,
part que nous avons utilis les mtadonnes pour faire un lien vers lentit Blog. Comme un
commentaire est associ un article, nous avons cr un lien dans lentit Comment vers
lentit Blog qui lui est associe. On fait cel en crant un lien ManyToOne qui cible lentit
Blog. On spcifie galement que linverse de ce lien est comments. Pour crer cet inverse, il
faut mettre jour lentit Blog afin que Doctrine 2 sache quun article peut contenir plusieurs
commentaires. Mettez jour lentit Blog dans src/Blogger/BlogBundle/Entity/Blog.php pour
ajouter cette association. De ce fait, nous allons pouvoir connaitre, depuis un article, quels
sont les commentaires associs directement, de manire objet, et pareil pour les
commentaires: nous pourrons savoir quel article ils sont associs.
<?php
// src/Blogger/BlogBundle/Entity/Blog.php
namespace Blogger\BlogBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
*
@ORM\Entity(repositoryClass="Blogger\BlogBundle\Repository\BlogRepository")
* @ORM\Table(name="blog")
* @ORM\HasLifecycleCallbacks()
*/
class Blog
{
// ..
/**
* @ORM\OneToMany(targetEntity="Comment", mappedBy="blog")
*/
protected $comments;
// ..
public function __construct()
{
$this->comments = new ArrayCollection();
$this->setCreated(new \DateTime());
$this->setUpdated(new \DateTime());
}
}

// ..

Il y a plusieurs changements noter ici. Tout dabord, on ajoute des mtadonnes au membre
$comments. Souvenez vous que dans le chapitre prcdent nous navons pas ajout de
mtadonnes cet attribut, car nous ne voulions pas que Doctrine 2 le fasse persister. Cest
toujours vrai, mais nous voulons maintenant que Doctrine 2 remplisse ce champ avec les
entits Comment adaptes. Cest ce que font ces mtadonnes. Ensuite, Doctrine 2 a besoin
que le membre $comments soit cr par dfaut en tant quobjet ArrayCollection. On fait cela

dans le constructeur. Vous pouvez galement noter le use charg dimporter la classe
ArrayCollection.
Comme nous venons de crer lentit Comment et mis jour lentit Blog, laissons Doctrine 2
gnrer pour nous les accesseurs. Lancez la commande suivante :
$ php app/console doctrine:generate:entities Blogger

Les deux entits devraient maintenant tre jour avec des accesseurs corrects. Vous allez
galement remarquer quune classe de dpt CommentReposity a t cre dans
src/Blogger/BlogBundle/Repository/CommentRepository.php comme nous lavons prcis
dans les mtadonnes.
Il faut galement mettre jour la base de donne pour rpercuter les changements nos
entits. Nous pourrions utiliser doctrine:schema:update de la manire suivante pour cela, mais
nous allons plutt utiliser un migration Doctrine 2.
$ php app/console doctrine:schema:update --force

Les migrations Doctrine 2

Lextension et le bundle de migration Doctrine 2 nest pas disponible de base avec la


distribution standard de Symfony2, nous devons linstaller nous mme comme nous lavons
fait pour les donnes factices. Ouvrez le fichier deps la racine du projet et ajoutez
lextension comme suit :
[doctrine-migrations]
git=http://github.com/doctrine/migrations.git
[DoctrineMigrationsBundle]
git=http://github.com/symfony/DoctrineMigrationsBundle.git
target=/bundles/Symfony/Bundle/DoctrineMigrationsBundle

Mettez ensuite jour les vendors pour reflter ce changement.


$ php bin/vendors install

Cela va tlcharger les dernires versions de chaque dpt sur Github et les installer au bon
endroit.
Note
Si vous navez pas une machine sur laquelle Git est installe, vous allez devoir tlcharger et
installer vous mme lextension et le bundle.
doctrine-migrations extension: Tlchargez la version actuelle depuis Github et dcompressez
l dans vendor/doctrine-migrations.
DoctrineMigrationsBundle: Tlchargez la version actuelle depuis Github et dcompressez l
dans vendor/bundles/Symfony/Bundle/DoctrineMigrationsBundle.

Mettez ensuite jour le fichier app/autoloader.php pour enregistrer le nouvel espace de nom.
Comme ce plugin est galement dans lespace de nom Doctrine\DBAL, les nouveaux ajouts
doivent tre placs au dessus de celui dj existant. Les espace de noms sont vrifis de haut
en bas, il faut donc les enregistrer du plus spcifique au moins spcifique.
// app/autoloader.php
// ...
$loader->registerNamespaces(array(
// ...
'Doctrine\\DBAL\\Migrations' => __DIR__.'/../vendor/doctrinemigrations/lib',
'Doctrine\\DBAL'
=> __DIR__.'/../vendor/doctrine-dbal/lib',
// ...
));

Il faut maintenant enregistrer le bundle dans le noyau, situ dans app/AppKernel.php.


// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
// ...
new
Symfony\Bundle\DoctrineMigrationsBundle\DoctrineMigrationsBundle(),
// ...
);
// ...
}

Warning
La librairie de migrations Doctrine 2 est encore actuellement en alpha. Son utilisation sur les
serveurs de production est donc dcourage lheure actuelle.
Nous sommes maintenant prts mettre jour notre base de donne pour raliser les
changements dans les entits. Cest un processus qui comprend 2 tapes: il faut tout dabord
faire dcouvrir lextension de migrations quelles ont t les changements, travers la
commande doctrine:migrations:diff. Il faut ensuite raliser la migration, partir de ces
diffrences, laide de la commande doctrine:migrations:migrate.
Lancez les 2 commandes qui suivent pour mettre jour le schma de base de donne.
$ php app/console doctrine:migrations:diff
$ php app/console doctrine:migrations:migrate

Votre base de donne va maintenant reflter les changements dans les entits et contenir la
nouvelle table de commentaires.
Note
Vous pouvez galement remarquer une nouvelle table appelle migration_versions dans votre
base de donnes. Elle stocke les numros de version de migrations afin que les migrations
puissent savoir quel est la version actuelle de la base de donne.

Tip
Doctrine 2 Migrations est un bon moyen de mettre jour la base de donne car les
changements peuvent tre faits par la programmation. Cela signifie que nous pouvons intgrer
cette tche dans un script de dploiement afin que la base de donne soit automatiquement
mise jour lorsque lon dploie une nouvelle version de lapplication. Doctrine 2 Migrations
permet galement de revenir une version prcdent car charque migration propose une
mthode up et down. Pour revenir une version antrieure, il faut prciser le numro de
version vers laquelle vous souhaitez revenir en utilisant la commande suivante :
$ php app/console doctrine:migrations:migrate 20110806183439

Les donnes factices revisites

Maintenant que nous avons cr lentit Comment, ajoutons lui quelques donnes factices.
Cest toujours une bonne ide de crer des donnes factices lorsque lon cre une nouvelle
entit. On sait quun commentaire doit avoir une entit Blog associe comme nous lavons
prcis dans les mtadonnes, de ce fait lorsque lon cre une entit Comment il faut lui
spcifier une entit Blog entity. Nous avons dj cr les donnes factices pour lentit Blog,
donc nous pourrions simplement mettre jour le fichier qui contient ces dfinitions et ajouter
la cration des entits Comment. Cest peut-tre OK pour le moment, mais que va-t-il se
passer quand nous allons ensuite ajouter des utilisateurs, des catgories darticles et dautres
entits notre bundle ? Une meilleure manire de fonctionner, cest de crer les donnes
factices pour lentit Comment dans un nouveau fichier. Un nouveau problme apparait avec
cette approche : comment accder aux entits factices de la classe Blog ?
Heureusement, ce problme peut aisment tre rsolu en crant dans rfrences aux objets
dans un des fichiers de donnes, rfrence laquelle les autres donnes factices auront accs.
Mettez jour les donnes factices de lentit Blog dans
src/Blogger/BlogBundle/DataFixtures/ORM/BlogFixtures.php avec ce qui suit. Les
changements noter ici sont lextension de la classe AbstractFixture et limplmentation de
OrderedFixtureInterface. Notez galement les deux use pour importer ces classes.
<?php
// src/Blogger/BlogBundle/DataFixtures/ORM/BlogFixtures.php
namespace Blogger\BlogBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Blogger\BlogBundle\Entity\Blog;
class BlogFixtures extends AbstractFixture implements
OrderedFixtureInterface
{
public function load($manager)
{
// ..
$manager->flush();

$this->addReference('blog-1',
$this->addReference('blog-2',
$this->addReference('blog-3',
$this->addReference('blog-4',
$this->addReference('blog-5',

$blog1);
$blog2);
$blog3);
$blog4);
$blog5);

}
public function getOrder()
{
return 1;
}
}

On ajoute des rfrences aux articles via la mthode addReference(). Le premier paramtre est
un identifiant de rfrence que nous pouvons utiliser pour retrouver cet objet par la suite.
Nous devons galement implmenter la mthode getOrder() pour prciser lordre de
chargement des donnes factices. Les articles doivent tre charg avant les commentaires,
donc on renvoit 1.
Commentaires factices

Nous sommes maintenant prts pour crer des donnes factices pour notre entit Comment.
Crez un fichier de donnes factices dans
src/Blogger/BlogBundle/DataFixtures/ORM/CommentFixtures.php et ajoutez-y le contenu
suivant :
<?php
// src/Blogger/BlogBundle/DataFixtures/ORM/CommentFixtures.php
namespace Blogger\BlogBundle\DataFixtures\ORM;
use
use
use
use

Doctrine\Common\DataFixtures\AbstractFixture;
Doctrine\Common\DataFixtures\OrderedFixtureInterface;
Blogger\BlogBundle\Entity\Comment;
Blogger\BlogBundle\Entity\Blog;

class CommentFixtures extends AbstractFixture implements


OrderedFixtureInterface
{
public function load($manager)
{
$comment = new Comment();
$comment->setUser('symfony');
$comment->setComment('To make a long story short. You can\'t go
wrong by choosing Symfony! And no one has ever been fired for using
Symfony.');
$comment->setBlog($manager->merge($this->getReference('blog-1')));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('David');
$comment->setComment('To make a long story short. Choosing a
framework must not be taken lightly; it is a long-term commitment. Make
sure that you make the right selection!');
$comment->setBlog($manager->merge($this->getReference('blog-1')));
$manager->persist($comment);
$comment = new Comment();

$comment->setUser('Dade');
$comment->setComment('Anything else, mom? You want me to mow the
lawn? Oops! I forgot, New York, No grass.');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Kate');
$comment->setComment('Are you challenging me? ');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-23 06:15:20"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Dade');
$comment->setComment('Name your stakes.');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-23 06:18:35"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Kate');
$comment->setComment('If I win, you become my slave.');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-23 06:22:53"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Dade');
$comment->setComment('Your SLAVE?');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-23 06:25:15"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Kate');
$comment->setComment('You wish! You\'ll do shitwork, scan, crack
copyrights...');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-23 06:46:08"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Dade');
$comment->setComment('And if I win?');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-23 10:22:46"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Kate');
$comment->setComment('Make it my first-born!');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-23 11:08:08"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Dade');
$comment->setComment('Make it our first-date!');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-24 18:56:01"));

$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Kate');
$comment->setComment('I don\'t DO dates. But I don\'t lose either,
so you\'re on!');
$comment->setBlog($manager->merge($this->getReference('blog-2')));
$comment->setCreated(new \DateTime("2011-07-25 22:28:42"));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Stanley');
$comment->setComment('It\'s not gonna end like this.');
$comment->setBlog($manager->merge($this->getReference('blog-3')));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Gabriel');
$comment->setComment('Oh, come on, Stan. Not everything ends the
way you think it should. Besides, audiences love happy endings.');
$comment->setBlog($manager->merge($this->getReference('blog-3')));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Mile');
$comment->setComment('Doesn\'t Bill Gates have something like

that?');

$comment->setBlog($manager->merge($this->getReference('blog-5')));
$manager->persist($comment);
$comment = new Comment();
$comment->setUser('Gary');
$comment->setComment('Bill Who?');
$comment->setBlog($manager->merge($this->getReference('blog-5')));
$manager->persist($comment);
$manager->flush();
}
public function getOrder()
{
return 2;
}
}

Comme nous lavons fait dans la classe BlogFixtures, la classe CommentFixtures tend elle
aussi la classe AbstractFixture et implmente OrderedFixtureInterface. Cela signifie que nous
devons galement implmenter la mthode getOrder(). Cette fois-ci, la valeur de retour est 2,
ce qui nous assure que ces informations seront charges aprs celles des articles.
On peut galement voir comment les rfrences aux entits Blog, que nous avions cres
prcdemment, ont t utilises.
$comment->setBlog($manager->merge($this->getReference('blog-2')));

Nous sommes maintenant prt charger ces donnes dans la base de donnes :
$ php app/console doctrine:fixtures:load

Affichage des commentaires :

On peut maintenant afficher les commentaires associs chaque article du blog. Commenons
par mettre jour le CommentReposity avec une mthode pour charger les derniers
commentaires valids dun article.
Dpt de commentaires

Ouvrez la classe CommentRepository dans


src/Blogger/BlogBundle/Repository/CommentRepository.php et remplacez son contenu par ce
qui suit :
<?php
// src/Blogger/BlogBundle/Repository/CommentRepository.php
namespace Blogger\BlogBundle\Repository;
use Doctrine\ORM\EntityRepository;
/**
* CommentRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class CommentRepository extends EntityRepository
{
public function getCommentsForBlog($blogId, $approved = true)
{
$qb = $this->createQueryBuilder('c')
->select('c')
->where('c.blog = :blog_id')
->addOrderBy('c.created')
->setParameter('blog_id', $blogId);
if (false === is_null($approved))
$qb->andWhere('c.approved = :approved')
->setParameter('approved', $approved);
return $qb->getQuery()
->getResult();
}

La mthode que nous venons dajouter rcupre les commentaires associs un article. Pour
cela, on ajoute une clause where notre requte, qui utilise un paramtre nomm, paramtre
qui est associ une variable grce la mthode setParameter(). Vous devriez toujours utiliser
des paramtres plutt que de spcifier les valeurs directement comme ceci :
->where('c.blog = ' . blogId)

En effet dans cet exemple, la valeur de $blogId na pas t assainie, ce qui pourrait mener
des failles de scurit en laissant la porte ouverte des attaques par injection SQL.
Contrleur des articles

Il faut maintenant mettre jour laction show du contrleur Blog pour rcuprer les
commentaires de larticle. Mettez jour le contrleur de Blog dans
src/Blogger/BlogBundle/Controller/BlogController.php avec le contenu suivant :
// src/Blogger/BlogBundle/Controller/BlogController.php
public function showAction($id)
{
// ..
if (!$blog) {
throw $this->createNotFoundException('Unable to find Blog post.');
}
$comments = $em->getRepository('BloggerBlogBundle:Comment')
->getCommentsForBlog($blog->getId());

return $this->render('BloggerBlogBundle:Blog:show.html.twig', array(


'blog'
=> $blog,
'comments' => $comments
));

Nous utilisons la nouvelle mthode du CommentReposity pour rcuprer les commentaires


valids de larticle. La collection $comments est galement passe en paramtre du template.
Template de laffichage des articles

Maintenant que nous avons une liste des commentaires de larticle, nous pouvons mettre
jour la page daffichage des articles afin de les y afficher. Nous pourrions simplement placer
laffichage des commentaires directement dans le template daffichage des articles, mais
comme les commentaires sont une entit propre, cest mieux de sparer leur affichage dans un
template spar, que lon inclut dans un autre. Cela nous permet de rutiliser laffichage des
commentaires ailleurs dans lapplication. Mettez jour le template daffichage des articles
dans src/Blogger/BlogBundle/Resources/public/views/Blog/show.html.twig avec ce qui suit :
{# src/Blogger/BlogBundle/Resources/public/views/Blog/show.html.twig #}
{# .. #}
{% block body %}
{# .. #}
<section class="comments" id="comments">
<section class="previous-comments">
<h3>Comments</h3>
{% include 'BloggerBlogBundle:Comment:index.html.twig' with
{ 'comments': comments } %}
</section>
</section>
{% endblock %}

Vous pouvez remarquer lutilisation dun nouveau tag Twig, le tag include. Comme son nom
lindique, il inclut le contenu du template fourni en paramtres, ici
BloggerBlogBundle:Comment:index.html.twig. On peut galement lui passer des arguments.
Dans le cas prsent, on lui fournit une collection dentits Comment afficher.

Template daffichage des commentaires

Le template BloggerBlogBundle:Comment:index.html.twig que lon inclut plus haut nexiste


pas pour le moment et il faut le crer. Comme il sagit simplement dun template qui sera
inclut dans un autre, pas besoin de crer une route ou un contrleur pour cela : il suffit de
crer un fichier. Crez un nouveau fichier dans
src/Blogger/BlogBundle/Resources/public/views/Comment/index.html.twig et collez-y ce qui
suit :
{# src/Blogger/BlogBundle/Resources/public/views/Comment/index.html.twig #}
{% for comment in comments %}
<article class="comment {{ cycle(['odd', 'even'], loop.index0) }}"
id="comment-{{ comment.id }}">
<header>
<p><span class="highlight">{{ comment.user }}</span> commented
<time datetime="{{ comment.created|date('c') }}">{{ comment.created|
date('l, F j, Y') }}</time></p>
</header>
<p>{{ comment.comment }}</p>
</article>
{% else %}
<p>There are no comments for this post. Be the first to comment...</p>
{% endfor %}

Comme vous pouvez le voir, on traverse la collection dentit Comment et affiche les
commentaires. On peut galement voir une des fonctions sympa de Twig, la fonction cycle.
Cette fonction avance en boucle dune case travers les valeurs du tableau chaque itration.
Lindice de boucle courant est obtenu grce la variable spciale loop.index0, qui garde le
compte des itrations dans la boucle, en partant de 0. Il y a plusieurs autres variables
particulires disponibles lorsque lon est lintrieur dune boucle. Vous pouvez galement
remarquer la prsence dun identifiant HTML dans llment article. Cela nous permettra par
la suite de crer des permalinks (liens permanents) vers les commentaires.
CSS daffichage des commentaires

Ajoutons galement un peu de CSS pour que les commentaires soient agrables regarder.
Mettez jour la feuille de style dans src/Blogger/BlogBundle/Resorces/public/css/blog.css en
y ajoutant ce qui suit :
/** src/Blogger/BlogBundle/Resorces/public/css/blog.css **/
.comments { clear: both; }
.comments .odd { background: #eee; }
.comments .comment { padding: 20px; }
.comments .comment p { margin-bottom: 0; }
.comments h3 { background: #eee; padding: 10px; font-size: 20px; marginbottom: 20px; clear: both; }
.comments .previous-comments { margin-bottom: 20px; }

Si vous jetez maintenant un oeil la page daffichage des articles, par exemple
http://symblog.dev/app_dev.php/2, vous pouvez voir laffichage des commentaires darticles.

Ajouter des commentaires

La dernire partie de ce chapitre va nous faire ajouter aux utilisateurs la possibilit de


commenter les articles. Cela va tre possible grce un formulaire sur la page daffichage des
articles. Nous avons dj parl de la cration de formulaires dans Symfony2 lorsque nous
avons cr la page de contacts. Plutt que crer le formulaire nous mme, nous allons utiliser
Symfony2 pour faire cela pour nous.
Lancez la commande suivante pour gnrer la classe CommentType pour lentit Comment.
$ php app/console generate:doctrine:form BloggerBlogBundle:Comment

Vous remarquerez nouveau ici lutilisation ici de la version raccourcie pour spcifier lentit
Comment.
Tip
Vous avez peut tre remarqu que la commande doctrine:generate:form est galement
disponible. Elle fait la mme chose, mais lespace de nom utilis est diffrent.
Cette commande a gnr pour nous la classe CommentType dans
src/Blogger/BlogBundle/Form/CommentType.php.
<?php
// src/Blogger/BlogBundle/Form/CommentType.php
namespace Blogger\BlogBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class CommentType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('user')
->add('comment')
->add('approved')
->add('created')
->add('updated')
->add('blog')
;
}

public function getName()


{
return 'blogger_blogbundle_commenttype';
}

Nous avons dj explor ce qui se passe ici pour la classe EnquiryType. Nous pourrions
commencer par personnaliser cette classe tout de suite, mais nous allons dabord afficher le
formulaire.

Afficher le formulaire de commentaires.

Comme nous voulons que lutilisateur puisse ajouter des commentaires depuis la page
daffichage des articles, nous pourrions crer le formulaire dans laction show du contrleur
Blog et afficher le formulaire directement dans le template show. Il est toutefois mieux de
sparer ce code, comme nous lavions fait pour laffichage des commentaires. La diffrence
entre afficher les commentaires et afficher le formulaire de commentaires, cest que cette
seconde tche doit tre traite, elle ncessite donc un contrleur. Cela nous amnera donc
procder lgrement diffrement de ce que nous venons de faire, o il tait simplement
question dinclure un template.
Routage

Nous devons crer une nouvelle route pour grer le traitement du formulaire soumis. Ajoutez
une nouvelle route dans le fichier src/Blogger/BlogBundle/Resources/config/routing.yml.
BloggerBlogBundle_comment_create:
pattern: /comment/{blog_id}
defaults: { _controller: BloggerBlogBundle:Comment:create }
requirements:
_method: POST
blog_id: \d+

Le contrleur

Ensuite, il faut crer le nouveau contrleur Comment auquel nous faisons rfrence juste au
dessus. Crez un fichier dans src/Blogger/BlogBundle/Controller/CommentController.php et
collez-y le code qui suit :
<?php
// src/Blogger/BlogBundle/Controller/CommentController.php
namespace Blogger\BlogBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Blogger\BlogBundle\Entity\Comment;
use Blogger\BlogBundle\Form\CommentType;
/**
* Comment controller.
*/
class CommentController extends Controller
{
public function newAction($blog_id)
{
$blog = $this->getBlog($blog_id);
$comment = new Comment();
$comment->setBlog($blog);
$form
= $this->createForm(new CommentType(), $comment);
array(

return $this->render('BloggerBlogBundle:Comment:form.html.twig',
'comment' => $comment,
'form'
=> $form->createView()
));

}
public function createAction($blog_id)
{
$blog = $this->getBlog($blog_id);
$comment = new Comment();
$comment->setBlog($blog);
$request = $this->getRequest();
$form
= $this->createForm(new CommentType(), $comment);
$form->bindRequest($request);
if ($form->isValid()) {
// TODO: Persist the comment entity
return $this->redirect($this>generateUrl('BloggerBlogBundle_blog_show', array(
'id' => $comment->getBlog()->getId())) .
'#comment-' . $comment->getId()
);
}
return $this->render('BloggerBlogBundle:Comment:create.html.twig',
array(
));

'comment' => $comment,


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

}
protected function getBlog($blog_id)
{
$em = $this->getDoctrine()
->getEntityManager();
$blog = $em->getRepository('BloggerBlogBundle:Blog')>find($blog_id);
if (!$blog) {
throw $this->createNotFoundException('Unable to find Blog
post.');

}
return $blog;

}
}

On cre 2 actions dans le contrleur Comment, une pour laction new et une pour laction
create. Laction new est charge dafficher le formulaire de commentaires, alors que laction
create a pour mission de traiter le formulaire de commentaire soumis. Le bout de code a lair
imposant, mais il ny a rien de nouveau ici, tout a dj t abord dans le chapitre 2 lorsque
lon a cr le formulaire de contact. Avant davancer, assurez vous toutefois davoir bien
compris ce qui se passe dans ce contrleur.
Validation du formulaire

Nous ne voulons pas que les utilisateurs puissent proposer des commentaires avec des valeurs
vides pour le nom dutilisateur ou le contenu. Pour cela, nous allons retourner dans les

validateurs dont nous avions dj parl au chapitre 2, lors de la soumission du formulaire de


contact. Mettez jour lentit Comment dans src/Blogger/BlogBundle/Entity/Comment.php
avec ce qui suit.
<?php
// src/Blogger/BlogBundle/Entity/Comment.php
// ..
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\NotBlank;
// ..
class Comment
{
// ..
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('user', new NotBlank(array(
'message' => 'You must enter your name'
)));
$metadata->addPropertyConstraint('comment', new NotBlank(array(
'message' => 'You must enter a comment'
)));
}
// ..
}

Ces contraintes vrifient que le nom dutilisateur et le contenu du commentaire ne sont pas
vides. Nous avons galement ajout loption message aux deux contraintes pour remplacer le
message par dfaut. Noubliez pas dajouter lespace de nom pour ClassMetadata et NotBlank
comme cest le cas ici.
La vue

Il faut ensuite crer les 2 templates pour les actions new et create. Commencez par crer un
nouveau fichier dans
src/Blogger/BlogBundle/Resources/public/views/Comment/form.html.twig et collez-y le code
qui suit :
{# src/Blogger/BlogBundle/Resources/public/views/Comment/form.html.twig #}
<form action="{{ path('BloggerBlogBundle_comment_create', { 'blog_id' :
comment.blog.id } ) }}" method="post" {{ form_enctype(form) }}
class="blogger">
{{ form_widget(form) }}
<p>
<input type="submit" value="Submit">
</p>
</form>

Le but de ce template est simple, il affiche simplement le formulaire de commentaires. Vous


pourrez galement remarquer que laction du formulaire est POST vers la route que nous
venons de crer, BloggerBlogBundle_comment_create.

Maintenant, ajoutons le template pour la vue create. Crez un nouveau fichier dans
src/Blogger/BlogBundle/Resources/public/views/Comment/create.html.twig et collez-y le
code suivant :
{% extends 'BloggerBlogBundle::layout.html.twig' %}
{% block title %}Add Comment{% endblock%}
{% block body %}
<h1>Add comment for blog post "{{ comment.blog.title }}"</h1>
{% include 'BloggerBlogBundle:Comment:form.html.twig' with { 'form':
form } %}
{% endblock %}

Comme laction create du contrleur Comment soccupe de traiter le formulaire, il doit


galement permettre de lafficher, car il pourrait y avoir des erreurs dans le formulaire. Nous
utilisons nouveau BloggerBlogBundle:Comment:form.html.twig pour afficher le formulaire
et ainsi viter la duplication de code.
Maintenant, mettons jour le template daffichage des articles pour afficher le formulaire
dajout de commentaires. Mettez jour le template dans
src/Blogger/BlogBundle/Resources/public/views/Blog/show.html.twig avec ce qui suit :
{# src/Blogger/BlogBundle/Resources/public/views/Blog/show.html.twig #}
{# .. #}
{% block body %}
{# .. #}
<section class="comments" id="comments">
{# .. #}

} %}

<h3>Add Comment</h3>
{% render 'BloggerBlogBundle:Comment:new' with { 'blog_id': blog.id

</section>
{% endblock %}

Nous utilisons un nouveau tag Twig ici, le tag render. Ce tag permet dafficher le contenu
dun contrleur dans un template. Dans le cas prsent, il affiche le contenu de laction
BloggerBlogBundle:Comment:new, en lui fournissant le paramtre ncessaires, lidentifiant
de larticle.
Si vous regardez maintenant une des pages daffichage des articles, tel que
http://symblog.dev/app_dev.php/2, vous allez voir quune exception Symfony2 est lance.

Cette exception est lance par le template BloggerBlogBundle:Blog:show.html.twig. Si lon


regarde la ligne 25 du fichier BloggerBlogBundle:Blog:show.html.twig, on peut voir que

cette ligne cre une erreur au moment dembarquer le contrleur


BloggerBlogBundle:Comment:create.
{% render 'BloggerBlogBundle:Comment:create' with { 'blog_id': blog.id } %}

En regardant un peu plus attentivement cette exception, on peut voir quelle nous donne plus
dinformations sur la raison pour laquelle lexception a t lance.
Entities passed to the choice field must have a __toString() method defined

Cela nous dit quun champ de choix que lon essaye dafficher na pas de mthode
__toString() dans lentit laquelle il est associ. Un champ de choix est un lment de
formulaire qui fournit lutilisateur plusieurs choix, tel quun lment select (une liste
droulante). Vous vous demandez srement o est-ce que lon affiche un champ de choix dans
le formulaire de commentaires... Si vous regardez nouveau le template du formulaire de
commentaires, vous pouvez voir que lon affiche le formulaire grce la fonction Twig {{
form_widget(form) }}. Cette fonction affiche le formulaire entit de manire basique.
Retournons donc dans la classe qui cre ce formulaire, la classe CommentType. On peut voir
que plusieurs champs sont ajouts au formulaire via lobjet FormBuilder. On ajoute en
particulier un champs blog. Si vous vous souvenez du chapitre 2, nous avions parl de
comment le FormBuilder essaye de deviner le type de champ afficher partir des
mtadonnes qui lui sont associes. Comme nous avons tabli un lien entre les entits
Comment et Blog, le FormBuilder a devin que le commentaire devait avoir un champ de
choix, afin de permettre lutilisateur de prciser larticle auquel il est associ. Cest pourquoi
nous avons un champ de choix dans le formulaire, et pourquoi Symfony2 lance une exception.
On peut rsoudre le problme en ajoutant la mthode __toString() dans lentit Blog.
// src/Blogger/BlogBundle/Entity/Blog.php
public function __toString()
{
return $this->getTitle();
}

Tip
Les messages derreur de Symfony2 fournissent beaucoup dinformations pour dcrire les
problmes qui viennent dapparaitre. Lisez toujours les messages derreur car ils facilitent
grandement le processus de dbug. Les messages derreur fournissent galement une trace
complte de la pile dappel pour que vous voyiez les tapes qui ont men cette erreur.
Maintenant, lorsque vous rafraichissez la page vous devriez voir laffichage du formulaire de
commentaires. Vous pourrez galement remarquer laffichage indsirable de certains champs,
tel que approved, created, updated et blog. Cest parce que nous navons pas personnalis
prcdemment la classe CommentType, gnre automatiquement.
Tip

Les champs affichs semblent tous tre affichs avec un type de champ adapt : le champ user
est de type text, le champ comment``est une ``textarea, les 2 champs DateTime proposent
plusieurs champs select qui permettent de slectionner une date, etc.
Cest parce que le FormBuilder est capable de deviner le type dlment de formulaire associ
un lment de lentit. Il est capable de faire cela partir des mtadonnes que lon lui
fournit. Comme nous avons prcis des mtadonnes assez spcifiques pour lentit
Comment, le FormBuilder est capable de deviner de manire prcise les bons types de champ
afficher.
Mettons maintenant jour le fichier src/Blogger/BlogBundle/Form/CommentType.php afin de
nafficher que les champ dont nous avons besoin.
<?php
// src/Blogger/BlogBundle/Form/CommentType.php
// ..
class CommentType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('user')
->add('comment')
;
}
// ..
}

Si vous rafraichissez maintenant la page, seul les champs pour le nom dutilisateur et pour le
corps du commentaire sont affichs. Si vous vouliez soumettre le formulaire maintenant, le
commentaire ne serait pas sauvegard dans la base de donnes, car le contrleur ne fait rien de
lentit Comment si elle passe la validation. Nous avons dj vu comment persister des
lments dans la base de donnes lors de la cration des donnes factices, nous allons faire la
mme chose ici avec les commentaires. Mettez jour laction create du contrleur Comment
afin de persister les entits Comment dans la base de donnes.
<?php
// src/Blogger/BlogBundle/Controller/CommentController.php
// ..
class CommentController extends Controller
{
public function createAction($blog_id)
{
// ..
if ($form->isValid()) {
$em = $this->getDoctrine()
->getEntityManager();
$em->persist($comment);
$em->flush();

return $this->redirect($this>generateUrl('BloggerBlogBundle_blog_show', array(


'id' => $comment->getBlog()->getId())) .
'#comment-' . $comment->getId()
);
}
}

// ..

Persister les entits Comment ncessite simplement dappeler persist() et flush(). Souvenez
vous que les formulaires traitent seulement avec des objets PHP, et que Doctrine 2 gre et
sauve ces objets. Il ny a pas de lien direct entre la soumission dun formulaire et les donnes
soumises qui sont envoyes dans la base de donnes.
Vous devriez maintenant pouvoir ajouter des commentaires aux articles.

Conclusion

Nous avons bien progress dans ce chapitre. Notre site de blogging commence avoir les
fonctionnalits que lon est en droit dattendre de lui. Nous avons maintenant les bases de la
page daccueil et de lentit commentaire. Les utilisateurs peuvent poster des commentaires
dans les articles et lire ceux des autres utilisateurs. Nous avons vu comment crer des donnes
factices qui sont rfrences entre plusieurs fichiers de donnes factices, et utilis les
migrations Doctrine 2 pour conserver une trace des changements dans le schma de base de
donne.
Dans la prochaine partie, nous allons construire la barre latrale pour inclure le nuage de tags
et les commentaires rcents. Nous allons galement tendre Twig en crant nos propres filtres.
Nous regarderons enfin comment utiliser la librairie Assetic pour nous aider dans la gestion de
fichiers externes.

[Partie 5] - Personnalisation de la vue :


extensions Twig, barre latrale et Assetic
Je propose galement des formations en petits groupes sur 2 3 jours, plus dinfos sur la page
ddie. Nhsitez pas me contacter (06.62.28.01.87 ou clement [@] keiruaprod.fr) pour en
discuter !

Introduction
Dans ce chapitre, nous allons continuer construire la partie utilisateur de Symblog. Nous
allons amliore la page daccueil pour aficher des informations sur les commentaires associs
aux articles, ainsi quamliorer les rsultats potentiels de recherche via SEO (Search Engine
Optimization : optimisation pour les moteurs de recherches) en ajoutant le titre des articles
dans lURL. Nous allons galement commencer travailler sur la barre latrale, en lui
ajoutant 2 composants classiques; un nuage de tags et une section Derniers commentaires.
Nous allons galement explorer les diffrents environnements dans Symfony2 et apprendre
comment lancer Symblog dans lenvironnement de production. Le moteur de template Twig
va tre tendu afin de proposer un nouveau filtre, et nous allons prsenter Assetic pour la
gestion des ressources externes.

La page daccueil - Articles et commentaires


Pour le moment, la page daccueil se contente dafficher les articles mais ne fournit pas
dinformations concernant les commentaires qui leurs sont associs. Maintenant que nous
avons construit une entit Comment, nous pouvons mettre jour la page daccueil afin
dajouter ces informations. Comme nous avons dj tabli un lien entre les entits Blog et
Comment, nous savons que Doctrine 2 est capable de retrouver les commentaires associs un
article (souvenez vous que nous avons ajout un membre $comments dans lentit Blog).
Mettons jour le template de la page daccueil dans
src/Blogger/BlogBundle/Resources/views/Page/index.html.twig avec ce qui suit.
{# src/Blogger/BlogBundle/Resources/views/Page/index.html.twig #}
{# .. #}
<footer class="meta">
<p>Comments: {{ blog.comments|length }}</p>
<p>Posted by <span class="highlight">{{ blog.author }}</span> at
{{ blog.created|date('h:iA') }}</p>
<p>Tags: <span class="highlight">{{ blog.tags }}</span></p>
</footer>
{# .. #}

Nous avons utilis le getter comments afin de rcuprer les commentaires de larticle, et avons
ensuite pass la liste dans le filtre Twig length. Si vous regardez maintenant la page daccueil
via http://symblog.dev/app_dev.php/, vous pourrez voir que le nombre de commentaires
de chaque article est affich.

Comme expliqu plus haut, nous avons dj inform Doctrine 2 que le membre $comments de
lentit Blog estn associ lentit Comment. Nous avons ralis cela dans le chapitre
prcdent avec la mtadonne suivante dans lentit Blog, dans
src/Blogger/BlogBundle/Entity/Blog.php.
// src/Blogger/BlogBundle/Entity/Blog.php
/**
* @ORM\OneToMany(targetEntity="Comment", mappedBy="blog")
*/
protected $comments;

Nous savons ainsi que Doctrine 2 est conscient de la relation entre articles et commentaires,
mais comment a-t-il remplit le membre $comments avec les entits Comment
correspondantes ? Si vous vous souvenez de la mthode que nous avons cr pour le
BlogRepository (voir ci-dessous), vous pourrez voir que nous navons fait aucune slection
des commentaires pour rcuprer les articles de la page daccueil.
// src/Blogger/BlogBundle/Repository/BlogRepository.php
public function getLatestBlogs($limit = null)
{
$qb = $this->createQueryBuilder('b')
->select('b')
->addOrderBy('b.created', 'DESC');
if (false === is_null($limit))
$qb->setMaxResults($limit);
return $qb->getQuery()
->getResult();
}

Nanmoins, Doctrine 2 utilise un processus appel chargement feignant (lazy loading) o les
entits Comment sont cherches dans la base de donne lorsque cest ncessaire, dans le cas
prsent lors de lappel {{ blog.comments|length }}. Nous pouvons dmontrer ce
processus laide de la abrre doutils pour dveloppeurs. Nous avons dj commenc parler
de cet outil, et il est maintenant temps daborder lune des ses fonctionnalits les plus
puissantes, le profiler pour Doctrine 2. On se rend dans le profiler Doctrine 2 en cliquant sur
le dernier icone de la barre doutils. Le chiffre ct indique le nombre de requtes excutes
sur la base de donnes pour lactuelle requte HTTP.

Si vous cliquez sur licne Doctrine 2, des informations sur les requtes qui ont t excutes
par Doctrine 2 sur la base de donnes vous seront prsentes.

Comme vous pouvez le voir dans la capture dcran ci-dessus, il y a plusieurs requtes vers la
base de donne qui sont executes lorsque la page daccueil est charge. La seconde requte
rcupre les articles dans la base de donne, et est excute en rponse lappel de la

mthode getLatestBlogs() de la classe BlogRepository. Aprs cette requte, vous pouvez


trouver plusieurs requtes qui extraient les commentaires depuis la base de donne, un article
la fois. On peut le voir grce WHERE t0.blog_id = ? dans chacune des requtes, o le ?
est remplat par la valeur du paramtre (lidentifiant de larticle). Chacune de ces requtes est
lie un appel de {{ blog.comments }} dans le template de la page daccueil. Chaque fois
que cette fonction est effectue, Doctrine 2 va charger, parce que cest ncessaire ici et pas
avant, et donc de manire feignante, les entits Comment associes une entit Blog.
Bien que le lazy loading soit trs efficace pour rcuprer des entits depuis la base de
donnes, ce nest pas toujours la manire la plus efficace de procder. Doctrine 2 fournit la
possibilit de joindre des entits relies entre elles lorsquune requte a lieu sur la base de
donnes. De cette manire, on peut extraire les entit Blog et leurs entits Comment associes
en une seule requte. Mettez jour le code du QueryBuilder de la classe BlogRepository
dans src/Blogger/BlogBundle/Repository/BlogRepositoy.php pour joindre les
commentaires.
// src/Blogger/BlogBundle/Repository/BlogRepositoy.php
public function getLatestBlogs($limit = null)
{
$qb = $this->createQueryBuilder('b')
->select('b, c')
->leftJoin('b.comments', 'c')
->addOrderBy('b.created', 'DESC');
if (false === is_null($limit))
$qb->setMaxResults($limit);

return $qb->getQuery()
->getResult();

Si maintenant vous raffraichissez la page daccueil et allez examiner la sortie de Doctrine 2


dans la barre doutils, vous allez remarquer que le nombre de requtes a chut de manire
drastique. Vous pouvez galement voir que la table de commentaires a t jointe la table
darticles.
Le lazy loading et la jonction dentits qui sont lies sont deux concepts trs puissants, mais
qui doivent tre utiliss correctement. Lquilibre entre les deux doit tre trouv afin de
permettre aux applications de fonctionner aussi efficacement que possible. Au premier abord,
il simble attrayant de joindre toutes les entits lies afin de ne jamais avoir faire du lazy
loading et conserver un nombre faible de requtes vers la base de donnes. Il est nanmoins
important de se souvenir que plus il y a dinformations aller chercher dans la base de
donnes, plus les traitements effectuer par Doctrine 2 pour crer les objets associs aux
entits sont lourds. Plus de donnes signifie galement plus dutilisation mmoire par le
serveur pour stocker les objets.
Avant davancer, faisant un ajout mineur au template de la page daccueil. Mettez jour le
template de la page daccueil dans
src/Blogger/BlogBundle/Resources/views/Page/index.html.twig pour ajouter un lien
vers laffichage des commentaires de larticle.
{# src/Blogger/BlogBundle/Resources/views/Page/index.html.twig #}

{# .. #}
<footer class="meta">
<p>Comments: <a href="{{ path('BloggerBlogBundle_blog_show', { 'id':
blog.id }) }}#comments">{{ blog.comments|length }}</a></p>
<p>Posted by <span class="highlight">{{ blog.author }}</span> at
{{ blog.created|date('h:iA') }}</p>
<p>Tags: <span class="highlight">{{ blog.tags }}</span></p>
</footer>
{# .. #}

La barre latrale.
Actuellement, la barre latrale de Symblog est un peu vide. Nous allons la mettre jour en lui
ajoutant 2 composants, un nuage de tags et une liste des derniers commentaires.

Le nuage de tags
Le nuage de tags montre les tags des articles, les plus populaires ayant plus dimportance
visuelle travers un affichage plus gros. Pour cel, il nous faut un moyen de rcuprer tous
les articles de tous les articles. Crons de nouvelles mthodes dans la classe BlogRepository
pour cela. Mettez jour la classe BlogRepository dans
src/Blogger/BlogBundle/Repository/BlogRepository.php avec ce qui suit.
// src/Blogger/BlogBundle/Repository/BlogRepository.php
public function getTags()
{
$blogTags = $this->createQueryBuilder('b')
->select('b.tags')
->getQuery()
->getResult();
$tags = array();
foreach ($blogTags as $blogTag)
{
$tags = array_merge(explode(",", $blogTag['tags']), $tags);
}
foreach ($tags as &$tag)
{
$tag = trim($tag);
}
return $tags;
}
public function getTagWeights($tags)
{
$tagWeights = array();
if (empty($tags))
return $tagWeights;
foreach ($tags as $tag)
{

$tagWeights[$tag] = (isset($tagWeights[$tag])) ? $tagWeights[$tag]


+ 1 : 1;
}
// Shuffle the tags
uksort($tagWeights, function() {
return rand() > rand();
});
$max = max($tagWeights);
// Max of 5 weights
$multiplier = ($max > 5) ? 5 / $max : 1;
foreach ($tagWeights as &$tag)
{
$tag = ceil($tag * $multiplier);
}
}

return $tagWeights;

Comme les tags sont stocks dans la base de donne au format CSV (comma separated
values, cest dire que chaque valeur est spare de la prcdente par une virgule), il nous
faut un moyen de sparer et de renvoyer le rsultat sous la forme dun tableau. Cest le rle de
getTags(). La mthode getTagWeights() se sert ensuite du tableau de tafs pour calculer le
poids (weight) de chaque tag partir de son nombre doccurences dans le tableau. Les tags
sont galement mlangs afin dajouter un peu dalatoire leur affichage.
Maintenant que nous sommes capable de gnrer un nuage de tags, il faut lafficher. Crez
une nouvelle action dans le PageController dans le fichier
src/Blogger/BlogBundle/Controller/PageController.php pour grer la barre latrale.
// src/Blogger/BlogBundle/Controller/PageController.php
public function sidebarAction()
{
$em = $this->getDoctrine()
->getEntityManager();
$tags = $em->getRepository('BloggerBlogBundle:Blog')
->getTags();
$tagWeights = $em->getRepository('BloggerBlogBundle:Blog')
->getTagWeights($tags);
return $this->render('BloggerBlogBundle:Page:sidebar.html.twig', array(
'tags' => $tagWeights
));
}

Cette action est trs simple, elle utilise les 2 nouvelles mthodes du BlogRepository pour
gnrer le nuage de tags, quelle passe ensuite en paramtres la vue. Il nous faut maintenant
crer cette vue, dans
src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig.
{# src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig #}
<section class="section">

<header>
<h3>Tag Cloud</h3>
</header>
<p class="tags">
{% for tag, weight in tags %}
<span class="weight-{{ weight }}">{{ tag }}</span>
{% else %}
<p>There are no tags</p>
{% endfor %}
</p>
</section>

Le template est galement trs simple. Il traverse les diffrents tags, en leur associant une
classe CSS en fonction de leur poids. Dans cette boucle for un peu particulire, on accde
aux couples cl/valeur du tableau avec tag pour la cl et weight comme valeur. Il existe
plusieurs variations de comment utiliser une boucle `for avec Twig disponible dans la
``documentation <http://twig.sensiolabs.org/doc/templates.html#for>`_.
Si vous regardez le principal template du BloggerBlogBundle dans
src/Blogger/BlogBundle/Resources/views/layout.html.twig,

vous pourrez remarquer


que nous avions plac un lment temporaire pour le bloc de la barre latrale. On peut
maintenant le remplacer, en affichant la nouvelle action de la barre latrale. Souvenez vous
que la fonction Twig render permet dafficher le contenu dune action dun controlleur, dans
le cas prsent laction sidebar du controlleur Page.
{# src/Blogger/BlogBundle/Resources/views/layout.html.twig #}
{# .. #}
{% block sidebar %}
{% render "BloggerBlogBundle:Page:sidebar" %}
{% endblock %}

Enfin, ajoutons de la CSS au nuage de tags. Crez la nouvelle feuille de style dans
src/Blogger/BlogBundle/Resources/public/css/sidebar.css.
.sidebar .section { margin-bottom: 20px; }
.sidebar h3 { line-height: 1.2em; font-size: 20px; margin-bottom: 10px;
font-weight: normal; background: #eee; padding: 5px; }
.sidebar p { line-height: 1.5em; margin-bottom: 20px; }
.sidebar ul { list-style: none }
.sidebar ul li { line-height: 1.5em }
.sidebar .small { font-size: 12px; }
.sidebar .comment p { margin-bottom: 5px; }
.sidebar .comment { margin-bottom: 10px; padding-bottom: 10px; }
.sidebar .tags { font-weight: bold; }
.sidebar .tags span { color: #000; font-size: 12px; }
.sidebar .tags .weight-1 { font-size: 12px; }
.sidebar .tags .weight-2 { font-size: 15px; }
.sidebar .tags .weight-3 { font-size: 18px; }
.sidebar .tags .weight-4 { font-size: 21px; }
.sidebar .tags .weight-5 { font-size: 24px; }

Comme nous avons ajout une nouvelle feuille de style, il faut linclure. Mettez jour le
template principale du BloggerBlogBundle dans
src/Blogger/BlogBundle/Resources/views/layout.html.twig avec ce qui suit.

{# src/Blogger/BlogBundle/Resources/views/layout.html.twig #}
{# .. #}
{% block stylesheets %}
{{ parent() }}
<link href="{{ asset('bundles/bloggerblog/css/blog.css') }}"
type="text/css" rel="stylesheet" />
<link href="{{ asset('bundles/bloggerblog/css/sidebar.css') }}"
type="text/css" rel="stylesheet" />
{% endblock %}
{# .. #}

Note
Si vous nutilisez pas les liens symboliques pour rfrencer les fichiers externes dans le
rpertoire web, vous devez relancer la commande suivante afin de copier les nouveaux fichiers
CSS.
$ php app/console assets:install web

Si vous mettez maintenant jour la page daccueil de Symblog, vous verrez que le nuage de
tags est affich dans la barre latrale. Afin que les tags soient affichs avec diffrents poids,
vous devrez modifier les tags factices afin que certains soient plus utiliss que dautres.

Commentaires rcents.
Maintenant que le nuage de tags est en place, ajoutons un composant pour les derniers
commentaires la barre latrale.
Il nous faut tout dabord un moyen de rcuprer les derniers commentaires des articles. Nous
allons pour cela ajouter une mthode dans le CommentRepository situ dans
src/Blogger/BlogBundle/Repository/CommentRepository.php.
<?php
// src/Blogger/BlogBundle/Repository/CommentRepository.php
public function getLatestComments($limit = 10)
{
$qb = $this->createQueryBuilder('c')
->select('c')
->addOrderBy('c.id', 'DESC');
if (false === is_null($limit))
$qb->setMaxResults($limit);

return $qb->getQuery()
->getResult();

Maintenant, mettez jour laction de la barre latrale dans


src/Blogger/BlogBundle/Controller/PageController.php

derniers commentaires et les fournir la vue.

afin de rcuprer les

// src/Blogger/BlogBundle/Controller/PageController.php
public function sidebarAction()
{
// ..
$commentLimit

= $this->container

->getParameter('blogger_blog.comments.latest_comment_limit');
$latestComments = $em->getRepository('BloggerBlogBundle:Comment')
->getLatestComments($commentLimit);
return $this->render('BloggerBlogBundle:Page:sidebar.html.twig', array(
'latestComments'
=> $latestComments,
'tags'
=> $tagWeights
));
}

Vous remarquerez galement que nous avons utilis un nouveau paramtre appel
blogger_blog.comments.latest_comment_limit afin de limiter le nombre de
commentaires afficher. Pour crer ce paramtre, mettez jour le fichier de configuration
dans src/Blogger/BlogBundle/Resources/config/config.yml avec ce qui suit.
# src/Blogger/BlogBundle/Resources/config/config.yml
parameters:
# ..
# Blogger max latest comments
blogger_blog.comments.latest_comment_limit: 10

Il faut enfin afficher les derniers commentaires dans le template de la barre latrale. Mettez
jour le template dans
src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig en y ajoutant ce
qui suit.
{# src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig #}
{# .. #}
<section class="section">
<header>
<h3>Latest Comments</h3>
</header>
{% for comment in latestComments %}
<article class="comment">
<header>
<p class="small"><span
class="highlight">{{ comment.user }}</span> commented on
<a href="{{ path('BloggerBlogBundle_blog_show', { 'id':
comment.blog.id }) }}#comment-{{ comment.id }}">
{{ comment.blog.title }}
</a>
[<em><time datetime="{{ comment.created|
date('c') }}">{{ comment.created|date('Y-m-d h:iA') }}</time></em>]
</p>
</header>
<p>{{ comment.comment }}</p>

</p>
</article>
{% else %}
<p>There are no recent comments</p>
{% endfor %}
</section>

Si vous mettez maintenant jour le site, vous verrez que les derniers commentaires sont
affichs dans la barre latrale, juste en dessous du nuage de tags.

Extensions Twig
Pour le moment nous avons affich les dates dans un format de date standard tel que 2011-0421. Une approche bien plus sympa serait dafficher depuis combien de temps les
commentaires ont t ajouts, tel que post il y a 3 heures. Nous pourrions ajouter une
mthode dans lentit Comment afin de raliser cela et changer les templates pour utiliser cette
mthode au lieu de {{ comment.created|date('Y-m-d h:iA') }}.
Comme il est possible que lon veuille utiliser cette fonctionnalit dautres endroits, il est
logique de sortir le code de lentit Comment. Comme transformer la date est une tche
spcifique la vue, nous devrions limplmenter en utilisant le moteur de template Twig.
Twig nous permet en effet cela grce ses possibilits dextensions.
Nous pouvons utiliser linterface dextension de Twig pour tendre les fonctionnalits par
dfaut quil propose. Nous allons crer une extension qui nous fournira un nouveau filtre qui
sutilisera de la manire suivante :
{{ comment.created|created_ago }}

Cela affichera une date de cration du commentaire de type posted 2 days ago pour Post il y
a 2 jours.

Lextension
Crez un fichier pour lextension Twig dans
src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogExtension.php

et mettez le

jour avec le contenu suivant.


<?php
// src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogExtension.php
namespace Blogger\BlogBundle\Twig\Extensions;
class BloggerBlogExtension extends \Twig_Extension
{
public function getFilters()
{
return array(
'created_ago' => new \Twig_Filter_Method($this, 'createdAgo'),
);

}
public function createdAgo(\DateTime $dateTime)
{
$delta = time() - $dateTime->getTimestamp();
if ($delta < 0)
throw new \Exception("createdAgo is unable to handle dates in
the future");
$duration = "";
if ($delta < 60)
{
// Seconds
$time = $delta;
$duration = $time . " second" . (($time > 1) ? "s" : "") . "
ago";

ago";

}
else if ($delta <= 3600)
{
// Mins
$time = floor($delta / 60);
$duration = $time . " minute" . (($time > 1) ? "s" : "") . "
}
else if ($delta <= 86400)
{
// Hours
$time = floor($delta / 3600);
$duration = $time . " hour" . (($time > 1) ? "s" : "") . "

ago";

}
else
{

// Days
$time = floor($delta / 86400);
$duration = $time . " day" . (($time > 1) ? "s" : "") . " ago";

return $duration;
}
public function getName()
{
return 'blogger_blog_extension';
}
}

Crer lextension est assez simple. On surcharge la mthode getFilters() pour renvoyer
autant de filtres que lon souhaite. Dans le cas prsent, on a cr le filtre created_ago. Ce
filtre est ensuite enregistr de manire appeler la mthode createdAgo, qui se charge
simplement de transformer un objet DateTime en une chaine de caractres qui reprsente la
dure coule depuis la valeur stocke dans lobjet DateTime.

Enregistrer lextension
Pour rendre lextension Twig disponible, il faut mettre jour le fichier de services dans
src/Blogger/BlogBundle/Resources/config/services.yml avec ce qui suit.

services:
blogger_blog.twig.extension:
class: Blogger\BlogBundle\Twig\Extensions\BloggerBlogExtension
tags:
- { name: twig.extension }

Vous pouvez voir que cel enregistre un nouveau service en utilisant la classe dextension
BloggerBlogExtension que nous venons de crer.

Mettre jour la vue


Le nouveau filtre Twig est dsormais prt tre utilis. Mettons jour la section des derniers
commentaires de la barre latrale pour nous en servir. Mettez jour le contenu du template de
la barre latrale dans
src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig avec ce qui suit :
{# src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig #}
{# .. #}
<section class="section">
<header>
<h3>Latest Comments</h3>
</header>
{% for comment in latestComments %}
{# .. #}
<em><time datetime="{{ comment.created|
date('c') }}">{{ comment.created|created_ago }}</time></em>
{# .. #}
{% endfor %}
</section>

Si vous vous rendez maintenant sur la page daccueil http://symblog.dev/app_dev.php/,


vous allez voir que les dates des derniers commentaires utilisent le filtre Twig pour afficher
les dures depuis lesquelles ils ont t posts.
Nous allons galement mettre jour les commentaires de la page daffichage des articles afin
dutiliser l aussi le nouveau filtre. Remplacez le contenu du template dans
src/Blogger/BlogBundle/Resources/views/Comment/index.html.twig avec ce qui suit.
{# src/Blogger/BlogBundle/Resources/views/Comment/index.html.twig #}
{% for comment in comments %}
<article class="comment {{ cycle(['odd', 'even'], loop.index0) }}"
id="comment-{{ comment.id }}">
<header>
<p><span class="highlight">{{ comment.user }}</span> commented
<time datetime="{{ comment.created|date('c') }}">{{ comment.created|
created_ago }}</time></p>
</header>
<p>{{ comment.comment }}</p>
</article>
{% else %}
<p>There are no comments for this post. Be the first to comment...</p>
{% endfor %}

Tip
Il y a plusieurs extensions Twig utiles disponibles via la librarie Twig-Extensions sur GitHub.
Si vous crez une extension utile, proposez une proposition dajout (pull request) dans ce
dpt et il est possible quelle soit incluse afin que dautres puissent sen servir.

Slugification de lURL
Actuellement, lURL de chaque article montre seulement lidentifiant de larticle. Bien que ce
soit parfaitement acceptable dun point de vue fonctionnel, cest pas terrible dun point de vue
SEO (Search Engine Optimization: optimisation pour les moteurs de recherche). Par exemple,
lURL http://symblog.dev/1 ne donne aucune information sur le contenu de larticle, alors
que quelquechose comme http://symblog.dev/1/a-day-with-symfony2 est beaucoup
mieux de ce point de vue. Pour raliser cel, il nous faut slugifier le titre des articles et nous
en servir comme lment de ladresse. Slugifier le titre revient enlever tous les caractres
non ASCII et les remplacer par un -.

Mise jour de la route


Pour commencer, modifions les rgles de routage pour la page daffichage des articles afin
dajouter sa nouvelle composante slug. Mettez jour les rgles de rouatge dans
src/Blogger/BlogBundle/Resources/config/routing.yml

# src/Blogger/BlogBundle/Resources/config/routing.yml
BloggerBlogBundle_blog_show:
pattern: /{id}/{slug}
defaults: { _controller: BloggerBlogBundle:Blog:show }
requirements:
_method: GET
id: \d+

Le controlleur
Comme avec le composant dj existant id, le nouvel lment slug va tre pass laction du
controlleur en argument. Il faut donc mettre jour le controlleur dans
src/Blogger/BlogBundle/Controller/BlogController.php afin de rpercuter ce
changement.
// src/Blogger/BlogBundle/Controller/BlogController.php
public function showAction($id, $slug)
{
// ..
}

Tip
Lordre dans lequel les arguments sont passs laction du controlleur na pas dimportance,
seul leur nom compte. Symfony2 est capable dassocier les paramtres de routage avec la liste
de paramtres pour nous. Bien que nous nayons pas utilis pour le moment de valeurs par
dfaut, cela vaut le coup de les mentionner ici. Si nous ajoutions un nouveau composant la

rgle de routage, nous pourrions trs bien lui spcifier galement une valeur par dfaut,
laide de loption defauts.
BloggerBlogBundle_blog_show:
pattern: /{id}/{slug}/{comments}
defaults: { _controller: BloggerBlogBundle:Blog:show, comments: true }
requirements:
_method: GET
id: \d+
public function showAction($id, $slug, $comments)
{
// ..
}

En utilisant cette mthode, les requtes ladresse http://symblog.dev/1/symfony2-blog


mneraient avoir $comments true dans showAction.

Slugification du titre
Comme on veut gnrer le slug partir du titre de larticle, nous allons gnrer
automatiquement cette valeur. Nous pourrions raliser cel automatiquement lexcution sur
le titre de larticle, mais la place nous allons plutt stocker le slug dans lentit Blog et le
stocker dans la base de donnes.

Mise jour de lentit Blog


Ajoutons un nouveau membre lentit Blog pour stocker le slug. Mettez jour lentit Blog
dans src/Blogger/BlogBundle/Entity/Blog.php
// src/Blogger/BlogBundle/Entity/Blog.php
class Blog
{
// ..
/**
* @ORM\Column(type="string")
*/
protected $slug;
}

// ..

Gnrez maintenant les accesseurs pour le nouveau membre $slug. Comme avant, lancez la
tche :
$ php app/console doctrine:generate:entities Blogger

Il est ensuite temps de mettre jour le schma de base de donne :


$ php app/console doctrine:migrations:diff
$ php app/console doctrine:migrations:migrate

Pour gnrer la valeur du slug, nous allons utiliser la mthode slugify du Tutorial Symfony 1
Jobeet. Ajoutez la mthode slugify dans lentit Blog situ dans
src/Blogger/BlogBundle/Entity/Blog.php

// src/Blogger/BlogBundle/Entity/Blog.php
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;
}

Comme nous voulons gnrer automatiquement le slug partir du titre, on peut gnrer le
slug lorsque la valeur du titre est affecte. Pour cel, on peut mettre jour laccesseur
setTitle pour mettre galement jour la valeur du slug. Mettez jour lentit Blog dans
src/Blogger/BlogBundle/Entity/Blog.php avec ce qui suit.
// src/Blogger/BlogBundle/Entity/Blog.php
public function setTitle($title)
{
$this->title = $title;
$this->setSlug($this->title);
}

Maintenant mettez jour la mthode setSlug afin daffecter une valeur slugifie lattribut
slug.
// src/Blogger/BlogBundle/Entity/Blog.php
public function setSlug($slug)
{
$this->slug = $this->slugify($slug);
}

Maintenant rechargez les donnes factices pour gnrer les slugs des articles.
$ php app/console doctrine:fixtures:load

Mise jour des routes gnres


Il faut enfin mettre jour les appels dj existants la gnration de route vers la page
daffichage des articles. Il y a plusieurs endroits o cel doit tre mis jour.
Ouvrez le template de la page daccueil dans
src/Blogger/BlogBundle/Resources/views/Page/index.html.twig et remplacez son
contenu avec ce qui suit. Il y a 3 modifications de la route BloggerBlogBundle_blog_show

dans ce template. Les modifications ajoutent simplement le slug des titres des articles en
paramtre de la fonction path.
{# src/Blogger/BlogBundle/Resources/views/Page/index.html.twig #}
{% extends 'BloggerBlogBundle::layout.html.twig' %}
{% block body %}
{% for blog in blogs %}
<article class="blog">
<div class="date"><time datetime="{{ blog.created|
date('c') }}">{{ blog.created|date('l, F j, Y') }}</time></div>
<header>
<h2><a href="{{ path('BloggerBlogBundle_blog_show', { 'id':
blog.id, 'slug': blog.slug }) }}">{{ blog.title }}</a></h2>
</header>
<img src="{{ asset(['images/', blog.image]|join) }}" />
<div class="snippet">
<p>{{ blog.blog(500) }}</p>
<p class="continue"><a
href="{{ path('BloggerBlogBundle_blog_show', { 'id': blog.id, 'slug':
blog.slug }) }}">Continue reading...</a></p>
</div>
<footer class="meta">
<p>Comments: <a
href="{{ path('BloggerBlogBundle_blog_show', { 'id': blog.id, 'slug':
blog.slug }) }}#comments">{{ blog.comments|length }}</a></p>
<p>Posted by <span
class="highlight">{{ blog.author }}</span> at {{ blog.created|
date('h:iA') }}</p>
<p>Tags: <span class="highlight">{{ blog.tags }}</span></p>
</footer>
</article>
{% else %}
<p>There are no blog entries for symblog</p>
{% endfor %}
{% endblock %}

De plus, une mise jour doit tre faite la section Derniers commentaires de la barre latrale
dans le template src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig.
{# src/Blogger/BlogBundle/Resources/views/Page/sidebar.html.twig #}

{# .. #}
<a href="{{ path('BloggerBlogBundle_blog_show', { 'id': comment.blog.id,
'slug': comment.blog.slug }) }}#comment-{{ comment.id }}">
{{ comment.blog.title }}
</a>
{# .. #}

Enfin, laction createAction du CommentController doit tre mise jour lorsquelle


redirige vers la page daffichage dun article lorsquun commentaire a t post. Mettez jour
le CommentController situ dans
src/Blogger/BlogBundle/Controller/CommentController.php avec ce qui suit.
// src/Blogger/BlogBundle/Controller/CommentController.php
public function createAction($blog_id)
{
// ..
if ($form->isValid()) {
// ..
return $this->redirect($this>generateUrl('BloggerBlogBundle_blog_show', array(
'id'
=> $comment->getBlog()->getId(),
'slug' => $comment->getBlog()->getSlug())) .
'#comment-' . $comment->getId()
);
}
}

// ..

Maintenant si vous allez sur la page daccueil http://symblog.dev/app_dev.php/ et


cliquez sur un des titres des articles, vous verrez que le slug des titres des articles est
maintenant prsent la fin de lURL.

Environnements
Les environnements sont la fois une fonctionnalit trs simple et trs puissante de
Symfony2. Vous nen tes peut tre pas conscient, mais vous vous en servez depuis le tout
premier chapitre de ce tutoriel. Avec les environnements, on peut configurer diffrents aspects
de Symfony2 et de lapplication pour quelle tourne diffremment selon des besoins
spcifiques au cours du cycle de vie de lapplication. Par dfaut, Symfony2 est configur avec
3 environnement :
1. dev - Developpement
2. test - Test
3. prod - Production

Le rle de ces environnements est inclus dans leur nom. Lorsque lon dveloppe une
application, il est utile davoir la barre de dbug lacran afin davoir des erreurs et des
exceptions dtailles, alors quen production on ne veut rien de tout cela. En fait, afficher ces
informations serait mme une faille de scurit car de nombreux dtails relatifs au
comportement interne de lapplication et du serveur seraient disponibles. En production, il
serait plus judicieux dafficher des pages derreur personnalises avec des messages simples,
tout en stockant discrtement les messages derreurs dans un fichier log. Il peut galement
tre utile dactiver le cache afin que lapplication tourne au maximum de ses capacits. En
dbug, lactiver serait un vritable cauchemar car il faudrait vider le cache chaque
modification ou presque, ce qui fait au final perdre plus de temps quil nen fait gagner et peut
tre source derreurs.
Le dernier environnement, cest lenvironnement de test. Il est utilis pour effectuer des tests
sur lapplication, tel que des tests unitaires ou fonctionnels. Nous navons pas parl des tests
pour le moment, mais ils seront abords en dtails dans le chapitre suivant.

Controlleur de facade
Pour le moment dans ce tutoriel, nous avons uniquement utilis lenvironnement de
dveloppement, ce qui nous avons prcis en utilisant le controlleur de facade app_dev.php
lorsque nous avons fait des requtes vers symblog, par exemple
http://symblog.dev/app_dev.php/about. Si vous regardez le contenu du controlleur de
facade de lenvironnement de dveloppement dans web/app_dev.php, vous y verrez la ligne
suivante :
$kernel = new AppKernel('dev', true);

Cette ligne est celle qui fait dmarrer Symfony2. Elle cre une nouvelle instance de
lAppKernel de Symfony2, et opte pour lenvironnement dev.
En comparaison, si vous regardez le controlleur de faade de lenvironnement de production
dans web/app.php, vous y verrez :
$kernel = new AppKernel('prod', false);

Vous pouvez voir que lenvironnement prod est fourni en paramtre lAppKernel dans cette
instance.
Lenvironnement de test na pas de controlleur de faade, car il nest pas cens tre utilis
dans un navigateur. Cest pourquoi il ny a pas de fichier app_test.php.

Paramtres de configuration
Nous avons vu plus haut comment les controlleurs de faade sont utiliss pour changer
lenvironnement dans lequel lapplication tourne. Nous allons maintenant regarder comment
les diffrents paramtres sont modifis lorsque lon utilise tel ou tel environnement. Si vous
regardez les fichiers dans app/config, vous y verrez plusieurs fichiers config.yml. Plus
prcismment, il y a un fichier de configuration principal, config.yml, et 3 autres qui sont
suffixs du nom de lenvironnement; config_dev.yml, config_test.yml et

config_prod.yml. Chacun de ces fichiers est charg selon lenvironnement courant.


ouvrons le fichier config_dev.yml, nous y verrons les lignes suivantes en entte :

Si nous

imports:
- { resource: config.yml }

La directive imports va permettre dimporter le contenu du fichier config.yml lintrieur


de celui l. La mme directive import peut tre trouve au dbut des 2 autres fichiers de
configuration config_test.yml et config_prod.yml. Linclusion dun ensemble commun
de paramtres de configuration dfinis dans config.yml permet davoir des valeurs
spcifiques pour ces paramtres selon les environnements. On peut voir dans le fichier de
configuration de lenvironnement de dveloppement app/config/config_dev.yml les lignes
suivantes, qui configurent lutilisation de la barre de dbug :
# app/config/config_dev.yml
web_profiler:
toolbar: true

Ce paramtre est absent dans le fichier de configuration de lenvironnement de production car


nous ne voulons pas que la barre doutils soit affiche.

Fonctionner en production
Nous allons maintenant voir notre site tourner dans lenvironnement de production. Pour cela,
il faut tout dabord vider le cache, laide dune commande Symfony2 :
$ php app/console cache:clear --env=prod

Maintenant rendez vous ladresse http://symblog.dev/. Remarquez quil manque le


controlleur de faade app_dev.php.
Note
Pour ceux qui utilisent les hotes dynamiques virtuels comme dans le lien de la partie 1, il
faudra ajouter ce qui suit dans le fichier .htaccess dans web/.htaccess.
<IfModule mod_rewrite.c>
RewriteBase /
# ..
</IfModule>

Vous allez remarquer que le site est presque identique, mais un certain nombre dlments
sont diffrents. La barre de dbug a disparue et les messages derreur dtaills ne sont plus
affichs : essayez de vous rendre ladresse http://symblog.dev/999 pour vous en assurer.

Les messages dexceptions dtaills ont t remplaces par un message plus simple, qui
informe lutilisateur quun problme a eu lieu. Ces crans dexceptions peuvent tre

configurs pour saccorder avec le thme visuel de votre application. Nous reviendrons sur ce
sujet dans un futur chapitre.
Vous pouvez galement remarquer que le fichier app/logs/prod.log se remplit avec des
informations sur lexcution de lapplication. Cest un aspect intressant lorsque vous aurez
des problmes en production mais quil ny aura plus les erreurs et exceptions de
lenvironnement de dveloppement.
Tip
Comment la requte depuis http://symblog.dev/ a russi emmener jusquau fichier
app.php? Je suis sr que vous avez tous dj cr des fichiers tels que index.html et
index.php comme index de sites, mais app.php est moins courant; cest grce une des
rgles du fichier web/.htaccess :
RewriteRule ^(.*)$ app.php [QSA,L]

On peut voir que cette line contient une expression rgulire qui associe nimporte quel texte
via ^(.*)$ et le fournit app.php.
Vous tes peut tre sur un serveur Apache qui ne dispose pas de mod_rewrite.c activ. Dans
ce cas, vous pouvez simplement ajouter app.php lURL, tel que
http://symblog.dev/app.php/.
Bien que nous ayions couvert les bases de lenvironnement de production, nous navons pas
parl de plusieurs lments lis lenvironnement de production, tel que la personnalisation
des pages derreurs et le dploiement vers un serveur de production laide doutils tel que
capifony. Nous reviendrons plus tard sur ces sujets dans un chapitre ultrieur.

Cration de nouveaux environnements


Il est enfin intressant de savoir que vous pouvez crer vos propres environnements
facilement dans Symfony2. Par exemple, vous pouvez avoir envie davoir un environnement
qui tourne sur le serveur de production mais affiche certaines informations de dbug tel que
les exceptions. Cela permettrait la plateforme dtre teste manuellement sur le serveur de
production, car les configurations des serveurs de dveloppement et de production peuvent (et
cest souvent le cas) tre diffrentes.
Bien que la cration dun nouvel environnement soit une tche simple, elle va au del du
cadre de ce tutoriel. Il y a un excellent article article dans le livre de recettes de Symfony2 qui
couvre ce sujet.

Assetic
La distribution standard de Symfony2 est accompagne dune librairie de gestion des fichiers
externes (les assets) appele Assetic. Cette librairie a t dveloppe par Kris Wallsmith et a
t inspire par la librairie Python webassets.

Assetic se charge de 2 aspects de la gestion des fichiers externes, les assets tels que images,
feuilles de style ou fichiers JavaScript, et les filtres qui peuvent tre appliqus sur ces assets.
Ces filtre permettent de raliser des tches utiles tel que la minification des fichiers CSS ou
JavaScript, ou bien passer les fichiers CoffeeScript travers un compilateur, et comibiner les
assets ensemble afin de rduire le nombre de requtes HTTP faites vers le serveur.
Nous avons jusqu prsent utilis la fonction Twig asset afin dinclure les fichiers externes,
de la manire suivante :
<link href="{{ asset('bundles/bloggerblog/css/blog.css') }}"
type="text/css" rel="stylesheet" />

Ces appels la fonction asset vont tre remplacs par Assetic.

Assets
La librairie Assetic dcrit un asset de la manire suivante :
Un asset Assetic est quelquechose avec un contenu filtrable qui peut tre charg et dcharg.
Cela inclus galement les mtadonnes, certaines pouvant tre manipules et certaines tant
fixes.
Plus simplement, les assets sont des ressources que lapplication utilise tel que les feuilles de
style et les images.
Feuilles de style
Commenons par remplacer les appels actuels la fonction asset pour les feuilles de styles
dans le template principal du BloggerBlogBundle. Mettez jour le contenu du template situ
dans src/Blogger/BlogBundle/Resources/views/layout.html.twig avec ce qui suit :
{# src/Blogger/BlogBundle/Resources/views/layout.html.twig #}
{# .. #}
{% block stylesheets %}
{{ parent () }}
{% stylesheets
'@BloggerBlogBundle/Resources/public/css/*'
%}
<link href="{{ asset_url }}" rel="stylesheet" media="screen" />
{% endstylesheets %}
{% endblock %}
{# .. #}

Nous avons remplac les 2 prcdents liens vers les fichiers CSS avec des fonctionnalits
Assetic. En utilisant stylesheets depuis Assetic, nous avons prcis que toutes les feuilles
de style dans src/Blogger/BlogBundle/Resources/public/css doivent tre combines en
un fichier avant dtre incluses. Combiner plusieurs fichiers est une manire simple mais
efficace dopitmiser le nombre de fichiers ncessaires votre site web. Moins de fichiers
signifie moins de requtes HTTP vers le serveur. Bien que nous ayons utilis * pour prciser

tous les fichiers du rpertoire css, nous aurions galement pu lister chaque fichier
individuellement :
{# src/Blogger/BlogBundle/Resources/views/layout.html.twig #}
{# .. #}
{% block stylesheets %}
{{ parent () }}
{% stylesheets
'@BloggerBlogBundle/Resources/public/css/blog.css'
'@BloggerBlogBundle/Resources/public/css/sidebar.css'
%}
<link href="{{ asset_url }}" rel="stylesheet" media="screen" />
{% endstylesheets %}
{% endblock %}
{# .. #}

Le rsultat final dans les 2 cas est le mme. La premire option qui utilise * assure que les
nouveaux fichiers CSS ajouts dans le rpertoire seront ajouts et combins dans le fichier
CSS dAssetic. Cela nest toutefois pas forcment le comportement que lon souhaite avoir,
donc utilisez lune ou lautre des mthodes selon vos besoins.
Si vous regardez la sortie HTML via http://symblog.dev/app_dev.php/, vous verrez que
les fichiers CSS ont t inclus de la manire suivante (remarquez que nous sommes retourn
dans lenvironnement de dveloppement).
<link href="/app_dev.php/css/d8f44a4_part_1_blog_1.css" rel="stylesheet"
media="screen" />
<link href="/app_dev.php/css/d8f44a4_part_1_sidebar_2.css" rel="stylesheet"
media="screen" />

Au premier abord, vous vous demandez peut tre quels sont ces 2 fichiers, car nous avons dit
plus haut quAssetic combinerait les fichiers en 1 fichier. Cest parce que nous sommes dans
lenvironnement de developpement. On peut demander Assetic de fonctionner en mode non
dbug We can ask Assetic to run in non-debug mode en mettant le paramtre debug false de
la manire suivante :
{# src/Blogger/BlogBundle/Resources/views/layout.html.twig #}
{# .. #}
{% stylesheets
'@BloggerBlogBundle/Resources/public/css/*'
debug=false
%}
<link href="{{ asset_url }}" rel="stylesheet" media="screen" />
{% endstylesheets %}
{# .. #}

Si vous regardez maintenant le HTML, vous y verrez ceci :

<link href="/app_dev.php/css/3c7da45.css" rel="stylesheet"


media="screen" />

Si vous regardez le contenu de ce fichier, vous verrez que les 2 fichiers CSS blog.css et
sidebar.css ont t combins en 1 fichier. Le nom de fichier utilis pour le fichier gnr est
produit alatoirement par Assetic. Si vous voulez controller le nom du fichier gnr, utilisez
loption output comme suit :
{% stylesheets
'@BloggerBlogBundle/Resources/public/css/*'
output='css/blogger.css'
%}
<link href="{{ asset_url }}" rel="stylesheet" media="screen" />
{% endstylesheets %}

Avant de continuer, supprimez le paramtre debug de lexemple prcdent, car nous voulons
revenir au comportement par dfaut sur les assets.
Nous devons galement mettre jour le template de base de lapplication, dans
app/Resources/views/base.html.twig.
{# app/Resources/views/base.html.twig #}
{# .. #}
{% block stylesheets %}
<link href='http://fonts.googleapis.com/css?family=Irish+Grover'
rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=La+Belle+Aurore'
rel='stylesheet' type='text/css'>
{% stylesheets
'css/*'
%}
<link href="{{ asset_url }}" rel="stylesheet" media="screen" />
{% endstylesheets %}
{% endblock %}
{# .. #}

JavaScripts
Bien que nous nayions pas actuellement de fichiers JavaScript dans notre application, leur
utilisation via Assetic est trs semblable celle des feuilles de style :
{% javascripts
'@BloggerBlogBundle/Resources/public/js/*'
%}
<script type="text/javascript" src="{{ asset_url }}"></script>
{% endjavascripts %}

Filtres
La vrai puissance dAssetic vient de ses filtres. Les filtres peuvent tre appliqus des assets
ou un ensemble dassets. Il y a un grand nombre de filtres lintrieur de la librairie de base,
qui ralisent les taches courantes suivantes :

1. CssMinFilter: minifaction de la CSS


2. JpegoptimFilter: optimisation des fichiers JPEGs
3. Yui\CssCompressorFilter: compression de fichiers CSS laide de loutil YUI
compressor
4. Yui\JsCompressorFilter: compression de fichiers JavaScript laide de loutil YUI
compressor
5. CoffeeScriptFilter: compile CoffeeScript en JavaScript
Une liste complte des filtres disponible se trouve dans le Readme Assetic.
Plusieurs de ces filtres passent en fait la main un autre programme ou une autre librairie,
tel que YUI Compressor, donc il est possible que vous ayiez installer ou configurer les
librairies ncessaires pour utiliser certains filtres.
Tlchargez YUI Compressor, dcompressez larchive et copiez les fichiers du rpertoire
build dans app/Resources/java/yuicompressor-2.4.6.jar. Cela suppose que vous
ayiez tlcharg la version 2.4.6, sinon changez le numro de version en consquences.
Nous allons ensuite configurer un filtre Assetic pour minifier la CSS laide de YUI
Compressor. Mettez jour la configuration de lapplication dans app/config/config.yml
avec le contenu suivant :
# app/config/config.yml
# ..
assetic:
filters:
yui_css:
jar: %kernel.root_dir%/Resources/java/yuicompressor-2.4.6.jar
# ..

Nous venons de configurer un filtre yui_css qui va utiliser lexcutable Java de loutil YUI
Compressor, que nous allons placer dans le rpertoire des ressources de lapplication. Afin
dutiliser ce filtre, il faut lui prciser avec quels assets sen servir. Mettez jour le template
dans src/Blogger/BlogBundle/Resources/views/layout.html.twig pour utiliser le
filtre yui_css.
{# src/Blogger/BlogBundle/Resources/views/layout.html.twig #}
{# .. #}
{% stylesheets
'@BloggerBlogBundle/Resources/public/css/*'
output='css/blogger.css'
filter='yui_css'
%}
<link href="{{ asset_url }}" rel="stylesheet" media="screen" />

{% endstylesheets %}
{# .. #}

Si vous rafraichissez la page daccueil du site Symblog et regardez les fichiers gnrs par
Assetic, vous verrez quils ont t minifis. Bien que la minification soit une bonne ide sur
un serveur de production, elle peut rendre le dbuggage difficile, en particulier lorsque le
Javascript est minifi. On peut la dsactiver pour lenvironnement development en prfixant
le filtre avec un ? de la manire suivante.
{% stylesheets
'@BloggerBlogBundle/Resources/public/css/*'
output='css/blogger.css'
filter='?yui_css'
%}
<link href="{{ asset_url }}" rel="stylesheet" media="screen" />
{% endstylesheets %}

Gnration des assets pour la production


En production, on peut gnrer les fichiers dassets grce Assetic afin quils deviennent de
vrais fichiers prts tre utiliss sur le serveur web. Le processus de cration des assets avec
Assetic pour chaque adresse peut tre assez long, en particulier si des filtres sont appliqus
aux assets. Le sauvegarder de manire dfinitive pour la production assure quAssetic ne sera
pas utilis pour manipuler les assets, mais seulement pour fournir les assets pr-traits. Lancez
la commande suivante pour conserver les fichiers assets traits sur le disque :
$ app/console --env=prod assetic:dump

Vous pouvez remarquer que plusieurs fichiers CSS ont t gnrs dans le rpertoire web/css.
Si vous lancez Symblog dans lenvironnement de production, vous verrez que les fichiers
proviennent directement de ce rpertoire.
Note
Si vous stockez les fichiers assets sur le disque mais souhaitez retourner dans lenvironnement
de dveloppement, vous devrez supprimer les fichiers cr dans le rpertoire web/ pour
permettre Assetic de les recrer.

Lecture additionnelle
Nous avons seulement abord une fraction des possibilits offertes par Assetic. Il y a plus de
ressources en ligne, en particulier dans le livre de recettes de Symfony2, en particulier (mais
en anglais) :
How to Use Assetic for Asset Management
How to Minify JavaScripts and Stylesheets with YUI Compressor
How to Use Assetic For Image Optimization with Twig Functions
How to Apply an Assetic Filter to a Specific File Extension

Il y a galement plusieurs bons articles de Richard Miller tel que :


Symfony2: Using CoffeeScript with Assetic
Symfony2: A Few Assetic Notes
Symfony2: Assetic Twig Functions
Tip
Il est noter galement que Richard Miller a galement de nombreux articles trs intressant
dans de nombreux dommaines de Symfony2, tel que linjection de dpendances, les services
ainsi que les dj mentionns guides sur Assetic. Cherchez les articles taggs avec symfony2

Conclusion
Nous avons couvert plusieurs nouveaux dommaines de Symfony2, tel que les environnements
et comment utiliser la librairie Assetic. Nous avons galement amlior la page daccueil, et
ajout plusieurs composants la barre latrale.
Dans le prochain chapitre, nous aller passer aux tests. Nous parlerons la fois des tests
unitaires et des tests fonctionnels avec PHPUnit. Nous verrons comment Symfony2 aide
grandement lcriture des tests avec plusieurs classes pour faciliter lcriture des tests
fonctionnels qui simulent des requtes, permettre de remplir les formulaires, cliquent sur les
liens et nous permettent dinspecter les rponses obtenues.

[Partie 6] - Les tests unitaires et


fonctionnels avec PHPUnit
Je propose galement des formations en petits groupes sur 2 3 jours, plus dinfos sur la page
ddie. Nhsitez pas me contacter (06.62.28.01.87 ou clement [@] keiruaprod.fr) pour en
discuter !

Introduction
Jusqu prsent, nous avons explor une grande quantit dlments de base essentiels de
Symfony2. Avant de continuer ajouter des fonctionnalits Symblog, il est temps de
commencer parler des tests. Nous allons regarder comment tester les fonctions
individuellement grce aux tests unitaires, puis regarder comment nous assurer que plusieurs
composants fonctionnent bien ensemble grce aux tests unitaires. Nous allons utiliser la
librairie de tests PHPUnit car elle est au centre des tests dans Symfony2. Comme le sujet des
tests est trs large, il sera abord nouveau dans dautres chapitres par la suite. A la fin de ce
chapitre nous aurons crit plusieurs tests, la fois unitaires et fonctionnels. Nous aurons
simul des requtes du navigateur, rempli des formulaires, et vrifi la rponse pour nous
assurer que les pages saffichent correctement. Nous allons galement regarder quel
pourcentage du code de lapplication couvrent ces tests.

Les tests dans Symfony2


PHPUnit est devenu le standard pour lcriture des tests en PHP, donc si vous ne connaissez
pas cette librairie, lapprendre vous sera galement utile pour vos autres projets PHP.
Noubliez galement pas que la plupart des concepts abords dans ce chapitre sont
indpendant du langage, et pourront ainsi tre transfrs dans les autres langages que vous
pourrez tre amens utiliser.
Tip
Si vous comptez crire vos propres bundles open source pour Symfony2, vous avez plus de
chances de gagner en popularit si votre bundle est bien test (et document). Regardez les
bundles dj existant pour Symfony2 chez KnpBundles.

Tests unitaires
Les tests unitaires ont pour mission dassurer que des units individuelles de code
fonctionnent correctement lorsquelles sont utilises de manire isole. Dans un cadre objet
tel que celui de Symfony2, une unit serait une classe et ses mthodes. Par exemple, nous
pourrions crire les test pour les classes des entits Blog et Comment. Lorsque lon crit des
tests unitaires, les tests devraient tre crits indpendemment des autres tests, cest dire que
le rsultat du test B ne devrait pas dpendre du rsultat du test A. Il est utile, lorsque lon fait
des tests unitaires, de pouvoir crer des faux objets (des objets mock) qui facilitent les tests
unitaires lorsquil y a des dpendances. Lutilisation des mocks permet galement de simuler
un appel de fonction plutt que lexcuter; cest par exemple utile pour simuler une classe qui

fait appel une librairie extrieure. La classe de lAPI peut utiliser une couche de transport
pour communiquer avec la librairie externe. On peut mocker la mthode de requte de la
couche de transport pour simuler les rsultats que lon veut, plutt que de faire appel la
librairie extrieur (dont lutilisateur dans un cadre isol peut tre pnible mettre en place, en
plus dtre hors sujet, et peut mener des erreurs lors des mises jour de librairies, et ainsi de
suite). Les tests unitaires ne testent pas que les composants fonctionnent bien ensemble, cest
un domaine couvert par le thme suivant, les tests fonctionnels.

Tests fonctionnels
Les tests fonctionnels vrifient lintgration des diffrents composants lintrieur de
lapplication, tel que le routage, les controlleurs et les vues. Les tests fonctionnels sont
similaires aux tests manuels que vous pouvez effectuer vous mme en lanant le navigateur
sur la page daccueil du site, en cliquant sur le lien dun article et en vrifiant que cest le bon
article qui saffiche. Les tests fonctionnels fournissent la possibilit dautomatiser ce
processus. Symfony2 propose plusieurs classes trs utiles pour les tests fonctionnels, tel quun
Client capable deffectuer des requtes vers les pages et soumettre des formulaire, et un
navigateur de DOM que lon peut utiliser pour analyser la rponse du client.
Tip
Il y a plusieurs processus de dveloppement logiciel dirigs par les tests. On peut citer le TDD
(Test Driven Development, dveloppement orient tests) et BDD (Behavioral Driven
Development, dveloppement orient comportement). Bien que ces thmes aillent au dela du
but de ce tutoriel, il est bon que vous ayiez connaissance de la librairie crit par everzet,
Behat, qui facilite le BDD. Il y a galement le BehatBundle disponible pour Symfony2, qui
permet dintgrer facilement Behat dans vos projets Symfony2.

PHPUnit
Comme dit plus haut, les tests sont crits dans Symfony2 avec PHPUnit. Vous devrez
linstaller afin de pouvoir lancer ces tests et les tests de ce chapitre. Pour des instructions
dinstallation dtaille rendez vous sur la documentation officielle du site de PHPUnit. Pour
lancer les tests dans Symfony2, il vaut faut PHPUnit 3.5.11 ou une version plus rcente.
PHPUnit est une librairie de tests trs complte, donc des rfrences la documentation
officielle auront lieu lorsque des dtails supplmentaires peuvent tre apports.

Assertions
Lcriture de tests soccupe de vrifier que le rsultat test du test est bien gal celui attendu.
Il y a plusieurs mthodes dassertion disponibles dans PHPUnit pour nous assister dans cette
tche. Quelques unes des assertions les plus courantes sont listes ci-dessous.
// Check 1 === 1 is true
$this->assertTrue(1 === 1);
// Check 1 === 2 is false
$this->assertFalse(1 === 2);
// Check 'Hello' equals 'Hello'
$this->assertEquals('Hello', 'Hello');

// Check array has key 'language'


$this->assertArrayHasKey('language', array('language' => 'php', 'size' =>
'1024'));
// Check array contains value 'php'
$this->assertContains('php', array('php', 'ruby', 'c++', 'JavaScript'));

Une liste complte des assertions est disponible dans la documentation de PHPUnit.

Lancer les tests de Symfony2


Avant de commencer crire des tests, regardons comment lancer des tests dans Symfony2.
On peut spcifier PHPUnit de se lancer avec un fichier de configuration particulier. Dans
notre projet Symfony2, ce fichier est app/phpunit.xml.dist. Comme le nom du fichier est
suffix avec .dist, vous devez copier son contenu dans un fichier app/phpunit.xml.
Tip
Si vous utilisez un gestionnaire de version tel que Git, vous devriez ajouter le nouveau fichier
app/phpunit.xml dans la liste des fichiers ignorer.
Si vous regardez maintenant le contenu du fichier de configuration PHPUnit, vous y verrez ce
qui suit :
<!-- app/phpunit.xml -->
<testsuites>
<testsuite name="Project Test Suite">
<directory>../src/*/*Bundle/Tests</directory>
<directory>../src/*/Bundle/*Bundle/Tests</directory>
</testsuite>
</testsuites>

Ces paramtres configurent des rpertoires qui font partie de notre ensemble de test.
Lorsquon lance PHPUnit, il va chercher dans ces rpertoire sil existe des tests lancer. On
peut galement lui fournir des paramtres de ligne de commande additionnels pour lancer les
tests seulement sur un rpertoire particulier, plutt que sur une suite de tests complte. Nous
verrons comment faire cel un peu plus tard.
Vous pouvez galement remarquer que ce fichier spcifie le fichier de dmarrage
app/bootstrap.php.cache. Ce fichier est utilis par PHPUnit pour obtenir des paramtres
de lenvironnement de test.
<!-- app/phpunit.xml -->
<phpunit
bootstrap

= "bootstrap.php.cache" >

Tip
Pour plus dinformations concernant la configuration de PHPUnit laide dun fichier XML,
reportez vous la documentation de PHPUnit.

Lancer les tests


Comme nous avons utilis le gnrateur de Symfony2 pour crer le BloggerBlogBundle dans
le chapitre 1, une classe de test pour le controlleur par dfaut DefaultController a
galement t cre. On peut excuter ce test en lanant la commande suivante depuis le
rpertoire racine du projet. Loption -c prcise que PHPUnit doit charger sa configuration
depuis le rpertoire app.
$ phpunit -c app

Une fois que le test est termin, vous devriez tre averti que les tests ont chou. Si vous
regardez dans la classe DefaultControllerTest dans
src/Blogger/BlogBundle/Tests/Controller/DefaultControllerTest.php, vous y
trouverez le contenu suivant :
<?php
// src/Blogger/BlogBundle/Tests/Controller/DefaultControllerTest.php
namespace Blogger\BlogBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class DefaultControllerTest extends WebTestCase
{
public function testIndex()
{
$client = static::createClient();
$crawler = $client->request('GET', '/hello/Fabien');
$this->assertTrue($crawler->filter('html:contains("Hello
Fabien")')->count() > 0);
}
}

Cest un test fonctionnel pour la classe DefaultController que Symfony2 a gnr. Si vous
vous souvenez du chapitre 1, ce controlleur avait une action qui grait les requtes vers
/hello/{name}. Puisque nous avons enlev cette classe, le test choue. Essayez de vous
rendre ladresse http://symblog.dev/app_dev.php/hello/Fabien avec un navigateur.
Symfony2 devrait vous informer que la route na pas pu tre trouve. Comme ce test fait
appel la mme adresse, il obtient la mme rponse, do lchec du test. Les tests
fonctionnels vont couvrir une grosse partie de ce chapitre et seront abords en dtails par la
suite.
COmme nous avons supprim la classe DefaultController, vous pouvez galement
supprimer cette classe de test. Supprimez la classe DefaultControllerTest situe dans
src/Blogger/BlogBundle/Tests/Controller/DefaultControllerTest.php.

Tests unitaires
Comme expliqu prcdemment, les tests unitaires ont pour mission de tester des units de
lapplication, individuellement, en isolation. Lorsque vous crivez des tests unitaires, il est

conseill de reproduire la structure du Bundle dans le rpertoire Tests. Si par exemple vous
voulez tester la classe de lentit Blog, situe dans
src/Blogger/BlogBundle/Entity/Blog.php, le fichier de test devrait se trouver dans
src/Blogger/BlogBundle/Tests/Entity/BlogTest.php. Une structure de rpertoire
pourrait tre la suivante :
src/Blogger/BlogBundle/
Entity/
Blog.php
Comment.php
Controller/
PageController.php
Twig/
Extensions/
BloggerBlogExtension.php
Tests/
Entity/
BlogTest.php
CommentTest.php
Controller/
PageControllerTest.php
Twig/
Extensions/
BloggerBlogExtensionTest.php

Vous pouvez remarquer que tous les fichiers de tests sont suffixs par Test.

Tests dans lentit Blog - Mthode Slugify


Nous allons commencer par tester la mthode slugify de lentit Blog. Ecrivons quelques tests
afin de nous assurer quelle fonctionne correctement. Crez un nouveau fichier dans
src/Blogger/BlogBundle/Tests/Entity/BlogTest.php et ajoutez-y le contenu suivant :
<?php
// src/Blogger/BlogBundle/Tests/Entity/BlogTest.php
namespace Blogger\BlogBundle\Tests\Entity;
use Blogger\BlogBundle\Entity\Blog;
class BlogTest extends \PHPUnit_Framework_TestCase
{
}

Nous avons cr une classe de test pour lentit Blog. Remarquez que la localisation de se
fichier est conforme la structure de rpertoire voque plus haut. La classe BlogTest
extends la classe de base de PHPUnit PHPUnit_Framework_TestCase. Tous les tests que
vous crirez pour PHPUnit hriteront de cette classe. Vous vous souvenez peut tre depuis un
chapitre prcdent que le \ doit tre plac devant le nom de la classe
PHPUnit_Framework_TestCase car elle est dclare dans lespace de nom public de PHP.
Nous avons un squelette de la classe de test pour lentit Blog, crivons donc maintenant un
scnario de test. Les scnarios de test sont, avec PHPUnit, des mthodes de la classe de test

prfixes par test, tel que testSlugify(). Mettez jour la classe BlogTest dans
src/Blogger/BlogBundle/Tests/Entity/BlogTest.php avec ce qui suit.
// src/Blogger/BlogBundle/Tests/Entity/BlogTest.php
// ..
class BlogTest extends \PHPUnit_Framework_TestCase
{
public function testSlugify()
{
$blog = new Blog();
$this->assertEquals('hello-world', $blog->slugify('Hello World'));
}

Cest un scnario trs simple. On instancie une nouvelle entit Blog et lance un
assertEquals() sur le rsultat de la mthode slugify. La mthode assertEquals() prend
au moins 2 arguments en paramtres, le rsultat attendu et celui obtenu. Un 3eme argument
permet de choisir le message afficher en cas dchec.
Lanons notre nouveau test unitaire. Lancez la commande suivante.
$ phpunit -c app

Vous devriez voir la sortie suivante :


PHPUnit 3.5.11 by Sebastian Bergmann.
.
Time: 1 second, Memory: 4.25Mb
OK (1 test, 1 assertion)

La sortie de lexcution de PHPUnit est trs simple comprendre. Le programme commence


par afficher certaines informations propos de PHPUnit, et affiche un . pour chaque test
lanc. Dans notre cas il y a seulement 1 test, donc seulement 1 . est affich. La dernire ligne
nous informe du rsultat du tests. Pour notre BlogTest nous avons seulement lanc 1 test
avec 1 assertion. Si vous avez laffichage en couleur sur votre ligne de commande, vous
verrez galement la dernire ligne en vert, ce qui montre que tout sest bien excut.
Modifions la mthode testSlugify() pour voir ce qui se passe lorsque le test choue.
// src/Blogger/BlogBundle/Tests/Entity/BlogTest.php
// ..
public function testSlugify()
{
$blog = new Blog();
$this->assertEquals('hello-world', $blog->slugify('Hello World'));
$this->assertEquals('a day with symfony2', $blog->slugify('A Day With
Symfony2'));
}

Relancez le test unitaire comme prcdement. Vous aurez alors la sortie suivante :
PHPUnit 3.5.11 by Sebastian Bergmann.
F
Time: 0 seconds, Memory: 4.25Mb
There was 1 failure:
1) Blogger\BlogBundle\Tests\Entity\BlogTest::testSlugify
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-a day with symfony2
+a-day-with-symfony2
/
var/www/html/symblog/symblog/src/Blogger/BlogBundle/Tests/Entity/BlogTest.p
hp:15
FAILURES!
Tests: 1, Assertions: 2, Failures: 1.

La sortie est un peu plus volue cette fois ci. On peut voir que les . ont laiss place un F,
qui nous indique quun des tests a chou. Vous verrez galement E lorsque le test contient
des erreurs. PHPUnit nous informe ensuite des dtails des checs, donc dans le cas prsent, de
lchec. On peut voir que la mthode
Blogger\BlogBundle\Tests\Entity\BlogTest::testSlugify a chou car les valeurs
attendues et obtenues sont diffrentes. Si vous avez laffichage en couleur dans la console,
vous verrez la dernire ligne en rouge, indiquant des checs dans les tests. Corrigez la
mthode testSlugify() afin de russir le test.
// src/Blogger/BlogBundle/Tests/Entity/BlogTest.php
// ..
public function testSlugify()
{
$blog = new Blog();
$this->assertEquals('hello-world', $blog->slugify('Hello World'));
$this->assertEquals('a-day-with-symfony2', $blog->slugify('A Day With
Symfony2'));
}

Avant davancer, ajoutons quelques autres tests pour la mthode slugify().


// src/Blogger/BlogBundle/Tests/Entity/BlogTest.php
// ..
public function testSlugify()
{
$blog = new Blog();
$this->assertEquals('hello-world', $blog->slugify('Hello World'));

$this->assertEquals('a-day-with-symfony2', $blog->slugify('A Day With


Symfony2'));
$this->assertEquals('hello-world', $blog->slugify('Hello
world'));
$this->assertEquals('symblog', $blog->slugify('symblog '));
$this->assertEquals('symblog', $blog->slugify(' symblog'));
}

Maintenant que nous avons test la mthode slugify de lentit Blog, il faut nous assurer que
le membre $slug est correctement affect lorsque le membre $title de Blog est mis jour.
Ajoutez la mthode suivante dans la classe BlogTest du fichier
src/Blogger/BlogBundle/Tests/Entity/BlogTest.php.
// src/Blogger/BlogBundle/Tests/Entity/BlogTest.php
// ..
public function testSetSlug()
{
$blog = new Blog();

$blog->setSlug('Symfony2 Blog');
$this->assertEquals('symfony2-blog', $blog->getSlug());

public function testSetTitle()


{
$blog = new Blog();

$blog->setTitle('Hello World');
$this->assertEquals('hello-world', $blog->getSlug());

On commence par tester la mthode setSlug pour sassurer que le membre $slug est
correctement slugifi lorsquil est mis jour. Ensuite on vrifie que le membre $slug est
correctement mis jour lorsque la mthode setTitle de lentit Blog est appele.
Lancez ces tests pour vrifier que lentit Blog fonctionne correctement.

Test de lextension Twig


Dans le chapitre prcdent, nous avons cr une extension Twig pour convertir une instance
dun objet \DateTime en une chaine de caractres contenant la dure coule depuis. Nous
allons tester que cette mthode se comporte bien comme nous lattendons. Crez un nouveau
fichier de test dans
src/Blogger/BlogBundle/Tests/Twig/Extensions/BloggerBlogExtensionTest.php et
mettez-y le ccontenu suivant :
<?php
//
src/Blogger/BlogBundle/Tests/Twig/Extensions/BloggerBlogExtensionTest.php
namespace Blogger\BlogBundle\Tests\Twig\Extensions;
use Blogger\BlogBundle\Twig\Extensions\BloggerBlogExtension;
class BloggerBlogExtensionTest extends \PHPUnit_Framework_TestCase

{
public function testCreatedAgo()
{
$blog = new BloggerBlogExtension();
$this->assertEquals("0 seconds ago", $blog->createdAgo(new
\DateTime()));
$this->assertEquals("34 seconds ago", $blog->createdAgo($this>getDateTime(-34)));
$this->assertEquals("1 minute ago", $blog->createdAgo($this>getDateTime(-60)));
$this->assertEquals("2 minutes ago", $blog->createdAgo($this>getDateTime(-120)));
$this->assertEquals("1 hour ago", $blog->createdAgo($this>getDateTime(-3600)));
$this->assertEquals("1 hour ago", $blog->createdAgo($this>getDateTime(-3601)));
$this->assertEquals("2 hours ago", $blog->createdAgo($this>getDateTime(-7200)));
// Cannot create time in the future
$this->setExpectedException('\Exception');
$blog->createdAgo($this->getDateTime(60));
}
protected function getDateTime($delta)
{
return new \DateTime(date("Y-m-d H:i:s", time()+$delta));
}
}

La classe est construite de la mme manire que prcdemment, et une mthode


testCreatedAgo() teste lextension Twig. Nous utilisons une nouvelle mthode de PHPUnit
ici, la mthode setExpectedException(). Cette mthode doit tre appele avant une
mthode dont on attend quelle lance une exception. Nous savons que la mthode
createdAgo ne peut grer des dures dans le futur, et que si cela arrive, elle va lancer une
\Exception. La mthode getDateTime() est simplement une petite fonction auxilliaire pour
crer une instance de \DateTime facilement. Vous pouvez remarquer que cette mthode nest
pas prfixe par test, donc PHPUnit nessayera pas de lexcuter. Ouvrez une ligne de
commande et lancez les tests pour ce fichier. On pourrait simplement lancer le test comme
avant, mais nous pouvons galement prciser PHPUnit de lancer les tests sur tout un
rpertoire (et ses sous-rpertoires) ou bien sur un seul fichier. Lancez la commande suivante :
$ phpunit -c app
src/Blogger/BlogBundle/Tests/Twig/Extensions/BloggerBlogExtensionTest.php

Cela va lancer les tests uniquement pour les fichiers de tests de notre extension. PHPUnit va
alors nous informer que les tests ont chou. Regardons la sortie, et essayons de comprendre
pourquoi :
1)
Blogger\BlogBundle\Tests\Twig\Extension\BloggerBlogExtensionTest::testCreat
edAgo
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@

-0 seconds ago
+0 second ago
/
var/www/html/symblog/symblog/src/Blogger/BlogBundle/Tests/Twig/Extensions/B
loggerBlogExtensionTest.php:14

Nous voulions que la premire assertion renvoie 0 seconds ago mais ce nest pas arriv, le
mot second ntait pas au pluriel. Mettons jour lextension Twig dans
src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogBundle.php pour corriger cela.
<?php
// src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogBundle.php
namespace Blogger\BlogBundle\Twig\Extensions;
class BloggerBlogExtension extends \Twig_Extension
{
// ..
public function createdAgo(\DateTime $dateTime)
{
// ..
if ($delta < 60)
{
// Secondes
$time = $delta;
$duration = $time . " second" . (($time === 0 || $time > 1) ?
"s" : "") . " ago";
}
// ..
}
// ..
}

Relancez les tests. La premire assertion passe dsormais, mais le jeu de test choue plus loin
quand mme. Regardons nouveau pourquoi :
1)
Blogger\BlogBundle\Tests\Twig\Extension\BloggerBlogExtensionTest::testCreat
edAgo
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-1 hour ago
+60 minutes ago
/
var/www/html/symblog/symblog/src/Blogger/BlogBundle/Tests/Twig/Extensions/B
loggerBlogExtensionTest.php:18

Nous pouvons maintenant remarquer que cest la 5me assertion qui choue. Prenez note du
18 la fin de la sortie, qui nous indique la ligne dans le fichie o lassertion a choue. En
regardant le jeu de tests, on peut voir que lextension ne se comporte pas comme attendu : l
o il aurait fallu donner 1 hour ago (il y a 1 heure), il a t rpondu 60 minutes ago (Il y a
60 minutes). Nous pouvons en trouver la raison en examinant le code du

BloggerBlogExtension. On compare les dures non strictement, cest dire avec <= plutt
<. Nous le faisons galement lorsque nous vrifions les heures. Mettez jour le code de
src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogBundle.php pour corriger cel.
<?php
// src/Blogger/BlogBundle/Twig/Extensions/BloggerBlogBundle.php
namespace Blogger\BlogBundle\Twig\Extensions;
class BloggerBlogExtension extends \Twig_Extension
{
// ..
public function createdAgo(\DateTime $dateTime)
{
// ..

ago";

else if ($delta < 3600)


{
// Minutes
$time = floor($delta / 60);
$duration = $time . " minute" . (($time > 1) ? "s" : "") . "
}
else if ($delta < 86400)
{
// Heures
$time = floor($delta / 3600);
$duration = $time . " hour" . (($time > 1) ? "s" : "") . "

ago";

}
// ..

}
}

// ..

Relancez nouveau tous nos tests avec la commande suivante :


$ phpunit -c app

Cela lance tous nos tests, et montre quils passent tous sans erreurs. Bien que nous navons
crit que quelques tests unitaires, vous devriez dj sentir quel point il est important et
puissant de tester unitairement son code. Bien que les tests ci-dessus soient mineurs, il sagit
tout de mme derreurs. Tester permet galement de sassurer que les nouvelles
fonctionnalits ajoutes au projet ne cassent pas ce qui est dj en place. Cela conclut la page
sur les tests unitaires pour cette fois-ci, mais nous y reviendrons par la suite. En attendant,
vous pouvez essayer dajouter vos propres tests unitaires pour tester ce qui ne la pas encore
t.

Tests fonctionnels
Maintenant que nous avons crit quelques tests unitaires, passons aux tests de plusieurs
composants la fois. La premire section des tests fonctionnels va nous faire simuler des
requtes dans un navigateur, afin danalyser la rponse qui est gnre.

Test de la page A propos


Nous commenons par tester la classe PageController pour la page A propos. Cest un bon
point de dpart, car cette page est trs simple. Crez un nouveau fichier dans
src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php et ajoutez-y le
contenu suivant.
<?php
// src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php
namespace Blogger\BlogBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class PageControllerTest extends WebTestCase
{
public function testAbout()
{
$client = static::createClient();
$crawler = $client->request('GET', '/about');
$this->assertEquals(1, $crawler->filter('h1:contains("About
symblog")')->count());
}
}

Nous avons dj vu un test de controlleur trs similaire lorsque nous avons brivement jet un
oeil la classe DefaultControllerTest. Pour tester la page propos, on vrifie que la
chaine About symblog est prsente dans le HTML qui est gnr, plus prcismment
lintrieur dun tag H1. La classe PageControllerTest ntend pas le
\PHPUnit_Framework_TestCase comme nous lavons vu dans les exemples des tests
unitaires, elle tend la place la classe WebTestCase. Cette classe fait partie du
FrameworkBundle, un bundle de Symfony2.
Comme expliqu plus tt, les classes de tests de PHPUnit doivent tendre
\PHPUnit_Framework_TestCase, mais lorsque des fonctionnalits communes ou
supplmentaires sont ncessaires dans plusieurs jeux de tests, il est utile de les encapsuler
dans une classe part et de faire que les classes de test tendent alors cette classe. Cest ce
que fait WebTestCase, qui fournit plusieurs mthode utiles aux tests fonctionnels dans
Symfony2. Ouvrez la dfinition de WebTestCase dans
vendor/symfony/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php, vous
y verrez que cette classe tend en fait la classe \PHPUnit_Framework_TestCase.
// vendor/symfony/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php
abstract class WebTestCase extends \PHPUnit_Framework_TestCase
{
// ..
}

Si vous regardez la mthode createClient() de la classe WebTestCase, vous verrez quelle


instancie un kernel (noyau) de Symfony2. EN suivant les mthodes, vous pouvez galement
voir que l environment est dfini test, moins quil soit surdfini comme argument

createClient().

Il sagit l de lenvironnement de test dont nous parlions dans le chapitre


prcdent. En revenant notre classe de test, on peut voir que la mthode createClient()
est appele pour mettre en marche le test. On appelle en suite request() sur le client pour
simuler une requte HTTP de type GET de la part dun navigateur vers la page /about,
exactement comme si nous nous rendions sur http://symblog.dev/about dans un
navigateur). La requte nous fournit en retour un objet Crawler qui contient la Response. La
classe Crawler est trs utile car elle nous laisse traverser le HTML qui nous est renvoy.
Nous utilisons le linstance du Crawler pour vrifier que le tag H1 de la rponse HTML
contient les mots About symblog. Vous remarquerez que, bien que nous tendons dsormais
la classe WebTestCase, nous continuons utiliser les mthodes dassertions comme avant :
souvenez vous que la classe PageControllerTest hrite de
\PHPUnit_Framework_TestCase .
Lanons le PageControllerTest avec la commande suivante. Lorsque lon crit des tests, il
est utile de ne lancer que les tests sur le fichier sur lequel on est en train de travailler : lorsque
les tests deviennent nombreux, tous les excuter peut alors prendre beaucoup de temps.
$ phpunit -c app/
src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

Vous devriez recevoir le message OK (1 test, 1 assertion), qui nous informe quun test
(testAboutIndex()) a tourn avec une assertion (assertEquals()) et que le rsultat est
celui attendu. Tout va pour le mieux !
Essayez de changer la chaine About symblog pour Contact, et lancez nouveau le test. Vous
verrez alors le test chouter car Contact nest pas trouv, et le assertEquals donne alors
une erreur.
1) Blogger\BlogBundle\Tests\Controller\PageControllerTest::testAboutIndex
Failed asserting that <boolean:false> is true.

Remettez le test About symblog avant davancer.


Linstance du Crawler que nous avons utilis nous permet de traverser les documents HTML
ou XML, ce qui signifie quil ne va marcher que pour des rponses de ce type. Nous pouvons
utiliser le Crawler pour traverser les rponses gnres laide de mthodes telles que
filter(), first(), last(), et parents(). Si vous avez dj utilis jQuery auparavant, vous
ne serez pas perdu avec la classe Crawler. Une liste complte des mthodes de traverse
supportes par le Crawler se trouve dans le chapitre sur les tests du livre Symfony2. Nous
allons en voquer quelques uns en avanant.

Page daccueil
Bien que le test de la page A propos ait t trs simple, il nous a permis de mettre en avant
les principes de base des tests fonctionnels des pages du site :
1. Crer le client
2. Effectuer une requte sur une page

3. Vrifier la rponse
Cest une prsentation simple du processus, car il y a en fait plusieurs autres tapes qui
peuvent sajouter, comme cliquer sur les liens, ou remplir et soumettre des formulaires.
Crons une mthode pour tester la page daccueil. Nous savons que la page daccueil est
disponible via lURL / et quelle doit afficher les derniers articles. Ajoutez une nouvelle
mthode testIndex() la classe PageControllerTest dans le fichier
src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php, avec le contenu
suivant :
// src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php
public function testIndex()
{
$client = static::createClient();
$crawler = $client->request('GET', '/');

// Vrifie qu'il y a des articles dans la page


$this->assertTrue($crawler->filter('article.blog')->count() > 0);

Vous pouvez voir que les mmes taptes ont lieu que pour les tests de la page A propos.
Lancez le test pour vrifier que tout fonctionne comme prvu.
$ phpunit -c app/
src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

Allons maintenant un peu plus loin dans les tests. Une partie des tests fonctionnels consiste
rpliquer ce quun utilisateur pourrait faire sur le site. Les utilisateurs cliquent sur les liens
pour naviguer entre les pages. Simulons maintenant cette action pour tester que les liens vers
la page daffichage des articles fonctionnent amnent bien vers la bonne page lorsque lon
clique sur les titres des articles.
Mettez jour la mthode testIndex() de la classe PageControllerTest avec le contenu
suivant :
// src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php
public function testIndex()
{
// ..
// Rcupre
correspondant
$blogLink
$blogTitle
$crawler

le premier lien, puis vrifie qu'il amene bien l'article


= $crawler->filter('article.blog h2 a')->first();
= $blogLink->text();
= $client->click($blogLink->link());

// Vrifie que h2 contient bien le titre de l'article


$this->assertEquals(1, $crawler->filter('h2:contains("' .
$blogTitle .'")')->count());
}

La premire chose que nous faisons, cest utiliser le Crawler pour extraitre le texte du
premier lien vers un article, laide du filtre article.blog h2 a. Ce filtre est utilis pour
renvoyer un tag a dans un tag H2 de la section article.blog. Pour mieux comprendre cela,
regardez le code HTML utilis pour afficher les articles sur la page daccueil.
<article class="blog">
<div class="date"><time datetime="2011-09-05T21:06:19+01:00">Monday,
September 5, 2011</time></div>
<header>
<h2><a href="/app_dev.php/1/a-day-with-symfony2">A day with
Symfony2</a></h2>
</header>
<!-- .. -->
</article>
<article class="blog">
<div class="date"><time datetime="2011-09-05T21:06:19+01:00">Monday,
September 5, 2011</time></div>
<header>
<h2><a href="/app_dev.php/2/the-pool-on-the-roof-must-have-aleak">The pool on the roof must have a leak</a></h2>
</header>
<!-- .. -->
</article>

Vous pouvez voir le filtre article.blog h2 a en place dans le code de la page daccueil.
Vous pouvez galement remarquer quil y a plus dune section <article class="blog">
dans le code, ce qui signifie que le filtre du Crawler va nous renvoyer une collection. Comme
nous voulons seulement le premier lien, nous utilisons la mthode first() sur la collection.
Nous utilisons enfin la mthode text() pour extraire le texte du lien, dans ce cas prcis il
sagit de A day with Symfony2. On clique ensuite sur le lien du titre de larticle pour
naviguer vers la page daffichage des articles. La mthode click() du client prend un objet
lien en paramtre et renvoit la Response dans une instance de Crawler. Vous devriez
maintenant remarquer que lobjet Crawler est un lment essentiel des tests fonctionnels.
Lobjet Crawler contient maintenant la rponse vers la page daffichage des articles. Nous
devons tester que la page vers laquelle nous avons navigu est bien la bonne. Nous pouvons
utiliser la variable $blogTitle que nous avons rcupr auparavant pour vrifier le titre de la
rponse.
Lancez les tests pour vrifier que la navigation entre la page daccueil et la page daffichage
des articles fonctionne correctement.
$ phpunit -c app/
src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

Vous savez maintenant comment naviguer entre les pages du site travers les tests
fonctionnels. Apprenons maintenant tester les formulaires.

Test de la page de contact

Les utilisateurs de Symblog peuvent envoyer des demandes de contact en utilisant le


formulaire sur la page de contacts http://symblog.dev/contact. Testons que la soumission
de ce formulaire marche comme il faut. Nous devons tout dabord savoir ce qui doit arriver si
le formulaire est correctement soumis, ce qui, dans le cas prsent, doit arriver lorsquil ny a
pas derreurs dans le formulaire. Le processus est le suivant :
1. Se rendre sur la page de contact
2. Remplir les lments du formulaire avec des valeurs
3. Soumettre le formulaire
4. Vrifier que lemail a t envoy symblog
5. Vrifier que la rponse au client contient une notification de russite denvoi
Pour le moment, nous en savons assez pour faire seulement les tapes 1 et 5. Nous allons
maintenant regarder comment faire les 3 tapes intermdiaires.
Ajoutez une nouvelle mthode testContact() la classe PageControllerTest dans
src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php.
// src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php
public function testContact()
{
$client = static::createClient();
$crawler = $client->request('GET', '/contact');
$this->assertEquals(1, $crawler->filter('h1:contains("Contact
symblog")')->count());
// Slection base sur la valeur, l'id ou le nom des boutons
$form = $crawler->selectButton('Submit')->form();
$form['blogger_blogbundle_enquirytype[name]']
= 'name';
$form['blogger_blogbundle_enquirytype[email]']
=
'email@email.com';
$form['blogger_blogbundle_enquirytype[subject]']
= 'Subject';
$form['blogger_blogbundle_enquirytype[body]']
= 'The comment body
must be at least 50 characters long as there is a validation constrain on
the Enquiry entity';
$crawler = $client->submit($form);
$this->assertEquals(1, $crawler->filter('.blogger-notice:contains("Your
contact enquiry was successfully sent. Thank you!")')->count());
}

On commence de la manire habituelle, en effectuant une requte vers lURL /contact, et en


vrifiant que la page contient le bon titre H1. On utilise ensuite le Crawler pour choisir le
bouton de soumission du formulaire. A partir du bouton, il est ensuite possible de retrouver le
formulaire; on remplit ensuite les lments du formulaire en utilisant la notation de tableau
[]. Le formulaire est ensuite fourni la mthode du client submit() pour soumettre le

formulaire. Comme dhbaitude, on reoit en retour une instance de Crawler. On utilise cette
rponse pour vrifier que le message flash est prsent dans la rponse qui nous est renvoye.
Lancez le test pour vrifier que tout fonctionne correctement.
$ phpunit -c app/
src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

Le test choue, PHPUnit nous donne la sortie suivante :


1) Blogger\BlogBundle\Tests\Controller\PageControllerTest::testContact
Failed asserting that <integer:0> matches expected <integer:1>.
/
var/www/html/symblog/symblog/src/Blogger/BlogBundle/Tests/Controller/PageCo
ntrollerTest.php:53
FAILURES!
Tests: 3, Assertions: 5, Failures: 1.

La sortie nous informe que le message flash na pas pu tre trouv dans la rponse obtenue
aprs soumission du formulaire. Cest parce que dans lenvironnement de test, les redirections
ne sont pas suivies. Lorsque le formulaire est effectivement valid dans la classe
PageController, une redirection a lieu, mais nest pas suivie. Nous devons dire
explicitement que la redirection doit tre suivie. La raison en est simple : il est possible que
lon veuille vrifier la rponse actuelle dabord. Cest ce que lon verra bientt pour vrifier
que lenvoi de lemail a bien eu lieu. Mettez jour la classe PageControllerTest pour
forcer le client suivre la redirection.
// src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php
public function testContact()
{
// ..
$crawler = $client->submit($form);
// Il faut suivre la redirection
$crawler = $client->followRedirect();
$this->assertEquals(1, $crawler->filter('.blogger-notice:contains("Your
contact enquiry was successfully sent. Thank you!")')->count());
}

Les tests devraient maintenant passer dans PHPUnit. Regardons maintenant ltape finale du
processus de vrification de soumission des formulaires, qui consiste sassurer quun email
a bien t envoy Symblog. On sait dj que les emails ne seront pas rellement envoys
dans lenvironnement de test, grce la configuration de cet environnement :
# app/config/config_test.yml
swiftmailer:
disable_delivery: true

Il est nanmoins possible de vrifier que les emails sont envoys en utilisant les informations
rapportes par la barre doutils de debug. Cest l o limportance pour le client de ne pas

suivre les redirections rentre en jeu. La vrification de loutil de debug doit tre ralise avant
que la redirection arrive, sinon les informations du profiler seraient perdues. Mettez jour la
mthode de test testContact() avec ce qui suit :
// src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php
public function testContact()
{
// ..
$crawler = $client->submit($form);
// On vrifie que l'email a bien t envoy
if ($profile = $client->getProfile())
{
$swiftMailerProfiler = $profile->getCollector('swiftmailer');
// Seul 1 message doit avoir t envoy
$this->assertEquals(1, $swiftMailerProfiler->getMessageCount());
// On rcupre le premier message
$messages = $swiftMailerProfiler->getMessages();
$message = array_shift($messages);
$symblogEmail = $client->getContainer()>getParameter('blogger_blog.emails.contact_email');
// On vrifie que le message a t envoy la bonne adresse
$this->assertArrayHasKey($symblogEmail, $message->getTo());
}
// On suit la redirection
$crawler = $client->followRedirect();
$this->assertTrue($crawler->filter('.blogger-notice:contains("Your
contact enquiry was successfully sent. Thank you!")')->count() > 0);
}

Aprs la soumission du formulaire, on vrifie que le profiler est disponible, car il aurait pu
tre dsactiv par un paramtre de lenvironnement.
Tip
Souvenez vous que les tests nont pas besoin dtre raliss dans lenvironnement de test. Ils
pourraient trs bien tre lancs dans lenvironnement de production, o des lments tel que le
profiler ne sont pas disponibles.
Si nous arrivons rcuprer le profiler, on effectue une requte pour obtenir lespion de
swiftmailer. Lespion de swiftmailer se charge de surveiller, en arrire plan, comment le
service demails est utilis. On peut lutiliser pour obtenir des informations sur quels emails
ont t envoys.
On utilise ensuite la mthode getMessageCount() pour vrifier quun email a t envoy.
Cest peut-tre suffisant pour sassurer quun email a bien t envoy, mais cela ne vrifie pas
vers o il est envoy. Il peut tre trs embarassant, voire dangereux, que des emails soient
envoys des adresses incorrectes. Afin de nous en assurer, on vrifie que ladresse du
destinataire est bien correcte.

Lancez maintenant le test pour vrifier que tout fonctionne correctement :


$ phpunit -c app/
src/Blogger/BlogBundle/Tests/Controller/PageControllerTest.php

Test de lajout de commentaire darticles


Utilisons maintenant les connaissances que nous avons acquises dans les prcdents tests de la
page de contact pour tester le processus de soumission des commentaires pour un article.
Voici ce qui doit arriver lorsquun commentaire est correctement soumis :
1. Se rendre sur la page dun article
2. Remplir le formulaire de commentaire
3. Soumettre le formulaire
4. Vrifier que le nouveau commentaire est ajout la fin de la liste des
commentaires
5. Vrifier galement dans la barre latrale que le commentaire ajout est au
sommet de la liste des derniers ccommentaires
Crez un nouveau fichier dans
src/Blogger/BlogBundle/Tests/Controller/BlogControllerTest.php

et ajoutez-y le

code suivant :
<?php
// src/Blogger/BlogBundle/Tests/Controller/BlogControllerTest.php
namespace Blogger\BlogBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class BlogControllerTest extends WebTestCase
{
public function testAddBlogComment()
{
$client = static::createClient();
$crawler = $client->request('GET', '/1/a-day-with-symfony');
$this->assertEquals(1, $crawler->filter('h2:contains("A day with
Symfony2")')->count());
// Select based on button value, or id or name for buttons
$form = $crawler->selectButton('Submit')->form();
$crawler = $client->submit($form, array(
'blogger_blogbundle_commenttype[user]'
'blogger_blogbundle_commenttype[comment]'
));
// Il faut suivre la redirection
$crawler = $client->followRedirect();

=> 'name',
=> 'comment',

// On vrifie que le comment s'affiche, et que c'est le dernier.


Cela assure que les commentaires
// vont du plus vieux au plus rcent.
$articleCrawler = $crawler->filter('section .previous-comments
article')->last();
$this->assertEquals('name', $articleCrawler->filter('header
span.highlight')->text());
$this->assertEquals('comment', $articleCrawler->filter('p')>last()->text());
// On vrifie que la barre latrale affiche bien 10 derniers
articles.
>last()

$this->assertEquals(10, $crawler->filter('aside.sidebar section'));

->filter('article')->count()

$this->assertEquals('name', $crawler->filter('aside.sidebar
section')->last()
->filter('article')->first()
->filter('header
span.highlight')->text()
);
}
}

Il sagit cette fois-ci du code entier du test directement. Avant de dissecter ce code, lancez le
test pour vrifier que tout fonctionne :
$ phpunit -c app/
src/Blogger/BlogBundle/Tests/Controller/BlogControllerTest.php

PHPUnit devrait vous dire quun test a t correctement excut. En regardant le code de
testAddBlogComment(), on peut voir que les choses se passent de la manire habituelle : on
cre un client, effectue des requtes sur les pages et on vrifie quelles contiennent bien ce
quil faut. On continue alors par obtenir le formulaire dajout des commentaires et le soumet.
Cette fois-ci, nous peuplons le formulaire dune manire un peu diffrente de la prcdente.
Nous utilisons le 2nd argument de la mthode submit() pour fournir les valeurs du
formulaire.
Tip
Nous pourrions galement utiliser linterface objet pour remplir les champs du formulaire,
comme dans les exemples suivantes :
// Cocher une case cocher
$form['show_emal']->tick();
// Choisir une option ou un lment radio
$form['gender']->select('Male');

Aprs avoir soumis le formulaire, on effectue la redirection du client afin de pouvoir vrifier
la rponse. On utilise nouveau le Crawler afin dobtenir le dernier commentaire de larticle,

qui devrait tre celui qui vient dtre soumis. On vrifie enfin que le premier lment de la
liste des derniers commentaires de la barre latrale est bien le dernier commentaire propos.

Dpt darticles
Dans la dernire partie sur les tests fonctionnels de ce chapitre, nous allons regarder comment
tester un dpt Doctrine 2. Crez un fichier dans
src/Blogger/BlogBundle/Tests/Repository/BlogRepositoryTest.php et ajoutez-y le
contenu suivant.
<?php
// src/Blogger/BlogBundle/Tests/Repository/BlogRepositoryTest.php
namespace Blogger\BlogBundle\Tests\Repository;
use Blogger\BlogBundle\Repository\BlogRepository;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class BlogRepositoryTest extends WebTestCase
{
/**
* @var \Blogger\BlogBundle\Repository\BlogRepository
*/
private $blogRepository;
public function setUp()
{
$kernel = static::createKernel();
$kernel->boot();
$this->blogRepository = $kernel->getContainer()
->get('doctrine.orm.entity_manager')
->getRepository('BloggerBlogBundle:Blog');
}
public function testGetTags()
{
$tags = $this->blogRepository->getTags();

$this->assertTrue(count($tags) > 1);


$this->assertContains('symblog', $tags);

public function testGetTagWeights()


{
$tagsWeight = $this->blogRepository->getTagWeights(
array('php', 'code', 'code', 'symblog', 'blog')
);
$this->assertTrue(count($tagsWeight) > 1);
// Cas ou le poids est suprieur 5
$tagsWeight = $this->blogRepository->getTagWeights(
array_fill(0, 10, 'php')
);
$this->assertTrue(count($tagsWeight) >= 1);
// Cas o il y a plusieurs lments dont le poids est suprieur 5

$tagsWeight = $this->blogRepository->getTagWeights(
array_merge(array_fill(0, 10, 'php'), array_fill(0, 2, 'html'),
array_fill(0, 6, 'js'))
);
$this->assertEquals(5, $tagsWeight['php']);
$this->assertEquals(3, $tagsWeight['js']);
$this->assertEquals(1, $tagsWeight['html']);
// Cas vide
$tagsWeight = $this->blogRepository->getTagWeights(array());
$this->assertEmpty($tagsWeight);
}

Le code parle de lui mme grce aux nombreux commentaires; La mthode setUp, appele
avant chaque mthode de test, cre le lien vers notre base de donnes. On vrifie que le calcul
des poids des tags getTagWeights se fait bien comme espr dans plusieurs cas limites,
comme celui o il ny a pas de tags, car il sagit gnralement des situations lorigine du
plus grand nombre de bugs. Comme on veut effectuer des tests qui ncessitent une connection
effective vers la base de donne, il faut nouveau tendre la classe WebTestCase, car cela
nous permet de dmarrer le noyau Symfony2. Lancez le test sur ce fichier avec la commande
suivante :
$ phpunit -c app/
src/Blogger/BlogBundle/Tests/Repository/BlogRepositoryTest.php

Victoire !

Couverture de code
Avant de terminer, quelques mots sur la couverture de code. Il sagit dun aperu de quelles
parties du code sont executes lorsque les tests sont lancs. Cela permet de savoir quel
volume de code est test, et quelles sont les parties du code qui ne sont pas testes, et de
dterminer sil faut ou non crire des test pour eux. A 0% de couverture, rien nest test, et
avec 100% de couverture de code, les tests passent dans lintgralit des situations traites par
la mthode teste. Attention, il est important de comprendre quun taux de couverture de code
lev nest en aucun cas un indicateur unique de qualit : la pertinence des tests lest tout
autant. En effet si une mthode tester ne traite pas certains cas particuliers mais quil
nexiste pas de test pour ces cas particuliers, la couverture de code sera leve et il ny aura
pas de test qui choue pour dire que la mthode ne se comporte pas comme attendu...
Afin dobtenir lanalyse de couverture de code de votre application, lancez la commande
suivante :
$ phpunit --coverage-html ./phpunit-report -c app/

Cela donnera lanalyse de couverture de code dans le rpertoire phpunit-report. Lancez le


fichier index.html dans votre navigateur pour obtenir les rsultats de lanalyse.
Reportez vous au chapitre sur lanalyse de la couverture de code de PHPUnit pour plus
dinformations.

Conclusion
Nous avons couvert un grand nombre daspects cls de la question des tests. Nous avons
explor la fois les tests unitaires et fonctionnels pour nous assurer que notre site web
fonctionne correctement. Nous avons vu comment manipuler les requtes les requtes du
navigateur et comment utiliser la classe Crawler de Symfony2 pour vrifier les rponses de
ces requtes.
Dans le prochain chapitre, nous parlerons du composant de scurit de Symfony2, et plus
spcifiquement de comment sen servir pour la gestion des utilisateurs. Nous intgrerons
galement FOSUserBundle, qui va nous permettre de travailler directement sur la section
dadministration de Symblog.