Development

Documentation/fr_FR/askeet: 4.txt

You must first sign up to be able to contribute.

Documentation/fr_FR/askeet: 4.txt

File 4.txt, 28.7 kB (added by nicocsgamer, 11 years ago)

Calendrier de laventquatrime jour: refactorisation

Line 
1 ´╗┐Calendrier de laventquatrime jour: refactorisation
2 =====================================================
3
4 Prcdemment dans Symfony
5 -------------------------
6
7 Au cours du [troisime jour] (3.txt), toutes les couches de larchitecture MVC ont t vu et modifi pour obtenir la liste des questions correctement affiche sur la page daccueil. Lapplication commence  tre sympa mais manque toujours de contenu.
8
9 Les objectifs du quatrime jour sont dafficher la liste des rponses concernant une question, de donner une belle URL  la page de dtail dune question,  dajouter une classe personnalise, et de migrer des bouts de code  un meilleur endroit. Cela devrait vous aider  comprendre les concepts de template, modle, politique de routage, et de refactorisation. Vous pouvez penser que cest un peu tt pour rcrire du code qui est vieux de seulement quelques jours, mais nous allons voir ce que vous en penserez  la fin de ce tutorial.
10
11 Pour lire ce tutorial vous devriez tre familiaris avec les concepts lis  [limplmentation dMVC dans symfony] (http://www.symfony-project.com/content/book/page/mvc.html). Ca pourrait aussi vous aider si vous aviez une ide de ce quest le [dveloppement agile][1].
12
13 Afficher les rponses dune question
14 ------------------------------------
15
16 Premirement, continuons ladaptation des template gnrs par le CRUD `Question` lors du [deuxime jour] (2.txt).
17
18 Laction `question/show` est ddie  laffichage des dtails dune question,  condition que vous lui passiez un `id`. Pour le tester, appel juste:
19
20     http://askeet/frontend_dev.php/question/show/id/1
21
22 [question detail](/images/askeet/question_day4.gif)
23
24 Vous avez surement dj vu la page `show` si vous avez dj manipul lapplication. Cest ici que nous allons ajouter les rponses  une question.
25
26 ### Un coup dil rapide  laction
27
28 Premirement, jetons un coup dil  laction `show`, situe dans le fichier `askeet/apps/frontend/modules/question/actions/actions.class.php`:
29
30     [php]
31     public function executeShow()
32      {
33        $this->question = QuestionPeer::retrieveByPk($this->getRequestParameter('id'));
34        $this->forward404Unless($this->question);
35      }
36
37 Si vous tes familiarisez avec Propel, vous reconnaitrez ici une simple requte sur la table `Question`. Elle a pour but de rcuprer lunique enregistrement ayant comme cl primaire la valeur du paramtre `id` de la requte. Dans lexemple donn dans lURL ci-dessus, le paramtre `id`  la valeur `1`, donc la fonction `->retrieveByPk()` de la classe QuestionPeer` va retourn lobjet de la classe `Question` avec `1` comme cl primaire. Si vous ntes pas familiaris avec Propel, revenez aprs avoir lu [un peu de documentation][2] sur leur site web.
38
39 Le rsultat de cette requte est passe au template `showSuccess.php` grce  la variable `$question`.
40
41 La fonction `->getRequestParameter('id')` de lobjet `sfAction` rcupre . . . le paramtre `id`, quil soit pass par la mthode GET ou POST. Par exemple si vous demandez
42    
43     http://askeet/frontend_dev.php/question/show/id/1/myparam/myvalue
44
45 . . . ensuite laction `show` sera capable de rechercher `myvalue` en faisant `$this->getRequestParameter('myparam')`.
46
47 >**Note**: la fonction `forward404Unless()` envoie au navigateur une page 404 si la question nexiste pas dans la base de donnes. Cest toujours bon de traiter les cas inconnus et les erreurs qui peuvent apparatre pendant lexcution. Symfony vous fournit des fonctions simples pour vous aider  faire des choses correctes, facilement.
48
49 ### Modifier le template `showSuccess.php`
50
51 Le template gnr `showSuccess.php` nest pas exactement ce dont nous avons besoin, donc nous allons compltement le rcrire. Ouvrez le fichier `frontend/modules/question/templates/showSuccess.php` et remplacez le contenu par:
52
53     [php]
54     <?php use_helper('Date') ?>
55    
56     <div class="interested_block">
57       <div class="interested_mark">
58         <?php echo count($question->getInterests()) ?>
59       </div>
60     </div>
61        
62     <h2><?php echo $question->getTitle() ?></h2>
63        
64     <div class="question_body">
65       <?php echo $question->getBody() ?>
66     </div>
67    
68     <div id="answers">
69     <?php foreach ($question->getAnswers() as $answer): ?>
70       <div class="answer">
71         posted by <?php echo $answer->getUser()->getFirstName().' '.$answer->getUser()->getLastName() ?>
72         on <?php echo format_date($answer->getCreatedAt(), 'p') ?>
73         <div>
74           <?php echo $answer->getBody() ?>
75         </div>
76       </div>
77     <?php endforeach; ?>
78     </div>
79
80 Vous reconnaissez ici le bloc div `interested_block` qui a dj t ajout hier au template `listSuccess.php`. Il affiche juste le nombre dutilisateur intress par une question. Ensuite, les markup ressemblent beaucoup  ceux de `list`, mit  part quil ny a pas de `link_to` dans le titre. Cest juste une rcriture du code initial pour afficher seulement les informations ncessaires dune question.
81
82 La nouvelle partie est le div `answers`. Il affiche toutes les rponses dune question (utilisant simplement la fonction Propel `$question->getAnswers()`), et pour chaque, affiche le taux de pertinence, le nom de lauteur, et la date de cration en plus du contenu.
83
84 La fonction `format_date()` est un autre exemple dassistants pour les template, qui ncessite une dclaration initiale. Vous pouvez en apprendre plus sur la syntaxe de cet assistant et sur dautres dans [le chapitre sur linternationalisation](http://www.symfony-project.com/content/book/page/templating_i18n_helpers.html) du livre symfony. Ces assistants acclrent les taches pnibles en affichant les dates dans le bon format.
85  
86 >**Note**: Propel cre le nom des fonctions, lies  une table, en ajoutant automatiquement un 's'  la fin du nom de la table. Sil vous plait oubliez laffreuse fonction `->getRelevancys()`, elle vous vitera quelques ligne de code SQL.
87
88 ### Ajouter quelques donnes de test
89
90 Il est temps dajouter quelques donnes aux tables `answer` et `relevancy`  la fin du fichier `data/fixtures/test_data.yml` (vous tes libre dajouter les vtres):
91
92     Answer:
93       a1_q1:
94         question_id: q1
95         user_id:     francois
96         body:        |
97           You can try to read her poetry. Chicks love that kind of things.
98      
99       a2_q1:
100         question_id: q1
101         user_id:     fabien
102         body:        |
103           Don't bring her to a donuts shop. Ever. Girls don't like to be
104           seen eating with their fingers - although it's nice.
105      
106       a3_q2:
107         question_id: q2
108         user_id:     fabien
109         body:        |
110           The answer is in the question: buy her a step, so she can
111           get some exercise and be grateful for the weight she will
112           lose.
113      
114       a4_q3:
115         question_id: q3
116         user_id:     fabien
117         body:        |
118           Build it with symfony - and people will love it.
119
120 Recharger les donnes:
121
122     $ php batch/load_data.php
123
124 Naviguez jusqu laction affichant la premire question pour vrifier si les modifications sont correctes:
125
126     http://askeet/frontend_dev.php/question/show/id/XX
127
128 >**Note**: Remplacez les XX par `lid` de votre premire question.
129
130 ![question answers](/images/askeet/answers_day4.gif)
131
132 La question est maintenant affiche de manire plus fantaisiste, suivit de ses rponses. Cest mieux, non?
133
134 Modifier le modle, partie I
135 ------------------------
136
137 Il est presque sr que le nom complet de lauteur sera ncessaire  un autre endroit de lapplication. Vous pouvez donc considrer que cest un attribut de lobjet `User`. Cela veut dire quil va y avoir une fonction dans le modle de `User` permettant de rcuprer le nom complet, au lieu de le construire dans une action supplmentaire. crivons cette fonction. Ouvrez le fichier `askeet/lib/model/User.php` et ajouter la:
138
139     [php]
140     public function __toString()
141     {
142       return $this->getFirstName().' '.$this->getLastName();
143     }
144
145 Pourquoi la fonctions est nomme `__toString()` au lieu de getFullName()` ou quelque chose de similaire? Parce que la fonction `__toString()` est la fonction par dfaut utilise par PHP5 pour reprsenter un objet sous la forme dun string. Cela signifie que vous pouvez simplement remplacer la ligne     
146
147 [php]
148     posted by <?php echo $answer->getUser()->getFirstName().' '.$answer->getUser()->getLastName() ?>
149  du template `askeet/apps/frontend/modules/question/templates/showSuccess.php`
150
151 par
152
153     [php]
154     posted by <?php echo $answer->getUser() ?>
155
156 pour obtenir le mme rsultat. Chouette, nest ce pas ?
157
158 Ne vous rptez pas
159 -------------------
160
161
162 Un des principes du dveloppement agile est dviter la duplication de code. Il dit "Don't Repeat Yourself" (D.R.Y.). Cest parce que le code dupliqu est deux fois plus long  revoir,  modifier,  tester et  valider plutt quun unique bout de code encapsul. Il rend la maintenance de lapplication plus complexe. Et si vous prter attention  la dernire partie du tutorial daujourdhui, vous noterez probablement, un peu de code dupliqu entre le template `listSuccess.php` dhier et le template `showSuccess.php`:
163
164     [php]
165     <div class="interested_block">
166       <div class="interested_mark">
167         <?php echo count($question->getInterests()) ?>
168       </div>
169     </div>
170
171 Donc notre premire session de [refactorisation](http://en.wikipedia.org/wiki/Refactoring) enlvera ce bout de code des deux template et le mettra dans un **fragment**, ou un bout rutilisable. Crez le fichier `_interested_user.php` dans le rpertoire `askeet/apps/frontend/modules/question/template/` avec le code suivant:
172
173     [php]
174     <div class="interested_mark">
175       <?php echo count($question->getInterests()) ?>
176     </div>
177
178 Ensuite, remplacez le code original des deux template (`listSuccess.php` et `showSuccess.php`) par:
179
180     [php]
181     <div class="interested_block">
182       <?php include_partial('interested_user', array('question' => $question)) ?>
183     </div>
184
185 Un fragment na pas daccs natif aux objets courants. Le fragment utilise la variable `$question`, donc elle doit tre dfinie dans lappel de `include_partial`. Ladditionnel `_` devant le nom du fichier du fragment aide  distinguer ceux des template actuels du dossier `template/`. Si vous souhaitez en savoir plus sur les fragments, lisez le  [chapitre sur la Vue]( http://www.symfony-project.com/content/book/page/view.html) du livre symfony.
186
187 Modifier le modle, part II
188 ---------------------------
189
190 L'appel  `$question->getInterests()` du nouveau fragment fait une requte  la base de donnes et renvoie un tableau d'objets de la classe `Interest`. Cest une requte lourde juste pour le nombre de personne intress, et elle pourrait surcharger la base de donnes. Rappelez-vous que cet appel est aussi fait dans le template `listSuccess.php`, mais cette fois en boucle, pour chaque question de la liste. Ca serait une bonne ide de loptimiser.
191 Une bonne solution est dajouter une colonne  la table `Question` appele `interested_users`, et de mettre  jour cette colonne chaque fois quun intrt est cr pour cette question.
192
193 >**Attention**: Nous sommes sur le point de modifier le modle sans manire apparente de le tester, puisqu'il n'y a actuellement aucune manire d'ajouter des enregistrements `Interest` grce  askeet. Vous ne devriez jamais modifier quelque chose sans pouvoir le tester.
194 >
195 > Heureusement, nous avons une manire de tester cette modification, et vous la dcouvrirez plus tard dans cette partie.
196
197 ### Ajouter un champ dans le modle objet de User
198
199 Allez y sans craintes et modifiez le modle de donnes `askeet/config/schema.xml` en ajoutant  la table `ask_question`:
200
201     [xml]
202     <column name="interested_users" type="integer" default="0" />
203
204 Ensuite reconstruisez le modle:
205
206     $ symfony propel-build-model
207
208 Cest exact, nous reconstruisons dj le modle sans nous inquitez des extensions de celui ci. Cest parce que lextension de la classe User faite dans `askeet/lib/model/User.php`, hrite de la classe gnre par Propel `askeet/lib/model/om/BaseUser.php`. Cest pourquoi nous devrions jamais diter le code du rpertoire `askeet/lib/model/om/`: il est remplac  chaque fois que la commande `build-model` est appele. Symfony aide  soulager le cycle de vie normal des changements de modles des premires tapes de n'importe quel projet Web.
209
210 Vous avez besoin galement de mettre  jour la base de donnes actuelle. Pour viter dcrire quelques dclarations SQL, vous pouvez reconstruire votre schma SQL et recharger vos donnes de test:
211
212     $ symfony propel-build-sql
213     $ mysql -u youruser -p askeet < data/sql/schema.sql
214     $ php batch/load_data.php
215
216 >**Note**: TIMTOWTDI: There is more than one way to do it (il y a plus dune manire de le faire). Au lieu de reconstruire la base de donnes, vous pouvez ajoutez une nouvelle colonne  la main:
217 >
218 >     $ mysql -u youruser -p askeet -e "alter table ask_question add interested_users int default '0'"
219 >
220
221 ### Modifier la fonction save() de lobjet Interest
222
223 La mise  jour de la valeur de ce nouveau champ doit tre faite chaque fois quun utilisateur dclare son intrt pour une question, i.e. que chaque fois quun enregistrement est ajout dans la table `Interest`. Vous pouvez implmenter ceci avec les trigger de MySQL, mais cela serait une solution dpendant de la base de donnes, et nous voulons pouvoir changer de base de donnes facilement.
224 La meilleur solution est de modifier le modle en remplaant la fonction `save()` de la classe `Interest`. Cette fonction est appele  chaque fois quun objet de la classe `Interest` est cr. Donc ouvrez le fichier  `askeet/lib/model/Interest.php` et crivez la fonction suivante:
225
226     [php]
227     public function save($con = null)
228     { 
229         $ret = parent::save($con);
230        
231         // update interested_users in question table
232         $question = $this->getQuestion();
233         $interested_users = $question->getInterestedUsers();
234         $question->setInterestedUsers($interested_users + 1);
235         $question->save($con);
236
237         return $ret;
238     }
239
240 La nouvelle fonction `save()` renvoie la question correspondant  lintrt courant, et incrmente son champ `interested_users`. Puis, il fait lhabituel `save()`. Par contre`$this->save() ferait une boucle infinie, donc nous utilisons la fonction de classe `parent::save()`  la place.
241
242 ### Scuriser la requte de mise  jour avec une transaction
243
244 Que se passe t-il si la base de donnes crash entre la mise  jour de lobjet `Question` et celui de lobjet `Interest` ? Vous finiriez avec des donnes corrompues. Cest le mme problme qui est rencontr dans une banque lors dun transfert dargent. Une premire requte diminue le montant dun compte, et une seconde augmente un autre compte.
245
246 Si  deux requtes sont hautement dpendantes, vous devriez scuriser leur excution avec une **transaction**. Une transaction est une assurance que les deux requtes russirons, ou aucunes delles. Si quelque chose de mauvais arrive  lune des requtes de la transaction, toutes les prcdentes russies sont annules, et la base de donnes retourne dans ltat avant la transaction.
247
248 Notre fonction `save()` est une bonne opportunit pour illustrer limplmentation des transactions dans Symfony. Remplacez le code par:
249
250     [php]
251     public function save($con = null)
252     {
253       $con = Propel::getConnection();
254       try
255       {
256         $con->begin();
257  
258         $ret = parent::save($con);
259  
260         // update interested_users in question table
261         $question = $this->getQuestion();
262         $interested_users = $question->getInterestedUsers();
263         $question->setInterestedUsers($interested_users + 1);
264         $question->save($con);
265  
266         $con->commit();
267  
268         return $ret;
269       }
270       catch (Exception $e)
271       {
272         $con->rollback();
273         throw $e;
274       }
275     }
276
277 Premirement, la fonction ouvre une connexion directe  la base de donnes par Creole. Entre les dclarations de `->begin()` et commit()`,  la transaction sassure que tout sera fait ou rien. Si quelque chose choue, une exception sera envoye, et la base de donnes excutera un retour  ltat prcdent.
278
279 ### Changer le template
280
281 Maintenant que la fonction `->getInterestedUsers()` de lobjet `Question` fonctionne correctement, il est temps de simplifier le fragment _`_interested_user.php` en remplaant:
282
283     [php]
284     <?php echo count($question->getInterests()) ?>
285
286 par
287
288     [php]
289     <?php echo $question->getInterestedUsers() ?>
290
291 >**Note**: Grce  notre brillante ide d'employer un fragment au lieu de laisser le code reproduit dans les template, cette modification  ncessite seulement que nous le fassions quune fois. Sinon, nous aurions du modifier les template `listSuccess.php` et `showSuccess.php` et pour des gens paresseux comme nous, cela aurait t accablant.
292
293 En termes de nombre de requtes et temps dexcution, cela devrait tre meilleur. Vous pouvez le vrifier grce au nombre de requtes  la base de donnes indique dans la barre doutils de dbogage, prt de licne base de donnes. Notez que vous pouvez aussi rcuprer des dtails sur les requtes SQL de la page courante en cliquant sur licne base de donnes:
294
295 ![database queries before refactoring](/images/askeet/debug_before_day4.gif)
296 ![database queries after refactoring](/images/askeet/debug_after_day4.gif)
297
298 ### Tester la validit de la modification
299
300 Nous allons vrifier que rien nest cass en rappelant laction `show`, mais avant ca, relancer limportation batch des donnes que nous avons crites hier:
301
302     $ cd /home/sfprojects/askeet/batch
303     $ php load_data.php
304
305 Quand nous crons les enregistrements de la table `Interest`, lobjet `sfPropelData`utilisera la mthode save()` et devrait correctement mettre  jour les enregistrements `User` connexes. Donc cest une bonne manire de tester les modifications du modle, mme si il ny  encore aucune interface CRUD tablie avec lobjet `Interest`.
306
307 Vrifiez-le en demandant la page d'accueil et le dtail de la premire question:
308
309     http://askeet/frontend_dev.php/
310     http://askeet/frontend_dev.php/question/show/id/XX
311
312 Le nombre dutilisateurs intresss na pas chang. Cest une modification russie!
313
314 La mme chose pour les rponses
315 -------------------------------
316
317 Ce qui a tait fait pour `count($question->getInterests())` peut tre aussi bien fait pour `count($answer->getRelevancys())`. La seule diffrence sera quune rponse peut avoir des votes positifs et ngatifs, alors quune question peut seulement tre note comme intressante.
318 Maintenant que vous avez comprit comment modifier le modle, nous pouvons aller plus vite. Voici les changements, juste pour rappel. Vous navez pas  les copier  la main pour le tutorial de demain, si vous utiliser [lespace de stockage SVN daskeet] (http://svn.askeet.com/tags/release_day_4/).
319
320 * Ajoutez les colonnes suivantes  la table `answer`dans `schema.xml`
321
322         [xml]
323         <column name="relevancy_up" type="integer" default="0" />
324         <column name="relevancy_down" type="integer" default="0" />
325
326 * Reconstruisez le modle et mettez  jour en consquence la base de donnes
327
328         $ symfony propel-build-model
329         $ symfony propel-build-sql
330         $ mysql -u youruser -p askeet < data/sql/schema.sql
331
332 * La fonction `->save()` de la classe `Relevancy` dans `lib/model/Relevancy.php`
333
334         [php]
335         public function save($con = null)
336         {
337           $con = Propel::getConnection();
338           try
339           {
340             $con->begin();
341      
342             $ret = parent::save();
343      
344             // update relevancy in answer table
345             $answer = $this->getAnswer();
346             if ($this->getScore() == 1)
347             {
348               $answer->setRelevancyUp($answer->getRelevancyUp() + 1);
349             }
350             else
351             {
352               $answer->setRelevancyDown($answer->getRelevancyDown() + 1);
353             }
354             $answer->save($con);
355      
356             $con->commit();
357      
358             return $ret;
359           }
360           catch (Exception $e)
361           {
362             $con->rollback();
363             throw $e;
364           }
365         }
366
367 * Ajoutez les deux fonctions suivantes dans la classe `Answer` du modle:
368
369         [php]
370         public function getRelevancyUpPercent()
371         {
372           $total = $this->getRelevancyUp() + $this->getRelevancyDown();
373        
374           return $total ? sprintf('%.0f', $this->getRelevancyUp() * 100 / $total) : 0;
375         }
376        
377         public function getRelevancyDownPercent()
378         {
379           $total = $this->getRelevancyUp() + $this->getRelevancyDown();
380        
381           return $total ? sprintf('%.0f', $this->getRelevancyDown() * 100 / $total) : 0;
382         }
383
384 * Changez la partie concernant la rponse dans `question/templates/showSuccess.php` par:
385
386         [php]
387         <div id="answers">
388         <?php foreach ($question->getAnswers() as $answer): ?>
389           <div class="answer">
390             <?php echo $answer->getRelevancyUpPercent() ?>% UP <?php echo $answer->getRelevancyDownPercent() ?> % DOWN
391             posted by <?php echo $answer->getUser()->getFirstName().' '.$answer->getUser()->getLastName() ?>
392             on <?php echo format_date($answer->getCreatedAt(), 'p') ?>
393             <div>
394               <?php echo $answer->getBody() ?>
395             </div>
396           </div>
397         <?php endforeach; ?>
398         </div>
399
400 * Ajoutez quelques donnes de test
401
402         Relevancy:
403           rel1:
404             answer_id: a1_q1
405             user_id:   fabien
406             score:     1
407    
408           rel2:
409             answer_id: a1_q1
410             user_id:   francois
411             score:     -1
412    
413 * Lancez le batch de popularisation
414
415 * Vrifiez la page `question/show`
416
417 ![relevancies on answers](/images/askeet/home_day4.gif)
418
419 Routage
420 -------
421
422 Depuis le dbut du tutorial, nous appelons lURL:
423
424     http://askeet/frontend_dev.php/question/show/id/XX
425    
426 Les rgles de routage par dfaut de Symfony comprennent cette requte comme si vous aviez rellement demand:
427
428     http://askeet/frontend_dev.php?module=question&action=show&id=XX
429
430 Mais avoir un systme de routage ouvre beaucoup dautres possibilits. Nous pouvons utilisez le titre de la question comme une URL, pour pouvoir demander la mme page avec:
431
432     http://askeet/frontend_dev.php/question/what-shall-i-do-tonight-with-my-girlfriend
433
434 Ceci optimise la manire dont les moteurs de recherche indexent les pages de votre site Web, et rend les urls plus lisibles.
435
436 ### Crons une version alternative au titre
437
438 Premirement, nous avons besoin d'une version convertie du titre - un titre dpouill - pour tre employe comme URL. [There's more than one way to do it](http://en.wikipedia.org/wiki/Perl), et nous allons choisir de stocker la version alternative dans une nouvelle colonne de la table `Question`. Dans le `schema.xml`, ajoutez la ligne suivante dans la table `Question`:
439
440     [xml]
441     <column name="stripped_title" type="varchar" size="255" />
442     <unique name="unique_stripped_title">
443       <unique-column name="stripped_title" />
444     </unique>
445
446 ... et reconstruisez le modle et mettez  jour la base de donnes:
447
448     $ symfony propel-build-model
449     $ symfony propel-build-sql
450     $ mysql -u youruser -p askeet < data/sql/schema.sql
451
452 Nous allons redfinir la fonction `setTitle()` de lobjet `Question` de sorte qu'il mette le titre dpouill en mme temps.
453
454 ### Classe personnalise
455
456 Avant cela, nous allons crer une classe personnalise pour rellement transformer un titre en titre dpouill, puisque cette fonction ne concerne pas vraiment lobjet `Question` (nous lutiliserons probablement aussi pour lobjet `Answer`).
457
458 Crons un nouveau fichier `myTools.class.php` dans le rpertoire`askeet/lib/`:
459
460     [php]
461     <?php
462    
463     class myTools
464     {
465       public static function stripText($text)
466       {
467         $text = strtolower($text);
468    
469         // strip all non word chars
470         $text = preg_replace('/\W/', ' ', $text);
471    
472         // replace all white space sections with a dash
473         $text = preg_replace('/\ +/', '-', $text);
474    
475         // trim dashes
476         $text = preg_replace('/\-$/', '', $text);
477         $text = preg_replace('/^\-/', '', $text);
478    
479         return $text;
480       }
481     }
482
483 Maintenant ouvrez le fichier classe `askeet/lib/model/Question.php` et ajoutez-y:
484
485     [php]
486     public function setTitle($v)
487     {
488       parent::setTitle($v);
489      
490       $this->setStrippedTitle(myTools::stripText($v));
491     }
492
493 Notez que la classe personnalise `myTools` na pas besoin dtre dclare: Symfony la charge automatiquement quand elle est ncessaire,  condition quelle soit situ dans le rpertoire `lib/`.
494
495 Vous pouvez maintenant recharger vos donnes.
496
497     $ symfony cc
498     $ php batch/load_data.php
499
500 Si vous voulez en savoir plus sur les classes personnalises et laide personnalis, lisez le [chapitre sur les extensions] (http://www.symfony-project.com/content/book/page/custom_helper.html) du livre Symfony.
501
502 ### Changer les liens de laction `show`
503
504 Dans le template `listSuccess.php`, changez la ligne
505
506     [php]
507     <h2><?php echo link_to($question->getTitle(), 'question/show?id='.$question->getId()) ?></h2>
508
509 par
510
511     [php]
512     <h2><?php echo link_to($question->getTitle(), 'question/show?stripped_title='.$question->getStrippedTitle()) ?></h2>
513
514 Maintenant ouvrez `actions.class.php` du module `question`, et changez laction `show` en:
515
516     [php]
517     public function executeShow()
518     {
519       $c = new Criteria();
520       $c->add(QuestionPeer::STRIPPED_TITLE, $this->getRequestParameter('stripped_title'));
521       $this->question = QuestionPeer::doSelectOne($c);
522  
523       $this->forward404Unless($this->question);
524     }
525
526 Essayer  nouveau dafficher la liste des questions et daccder  chacune en cliquant sur le titre:
527
528     http://askeet/frontend_dev.php/
529    
530 Les urls affichent correctement les titres dpouills des questions:
531
532     http://askeet/frontend_dev.php/question/show/stripped-title/what-shall-i-do-tonight-with-my-girlfriend
533
534 ### Changer les rgles de routage
535
536 Mais ce nest pas exactement comme nous cherchons  les afficher. Il est maintenant temps dditer les rgles de routage. Ouvrez le fichier de configuration `routing.yml` (situ dans le rpertoire `askeet/apps/frontend/config/` et ajoutez la rgle suivante au dbut du fichier:
537
538     question:
539       url:   /question/:stripped_title
540       param: { module: question, action: show }
541
542 Dans la ligne `url`, le mot `question` est un texte personnalis qui apparait dans lurl finale, alors que `stripped_title` est un paramtre (il est prcd de`:`). Ils forment un **pattern** que le systme de routage de Symfony applique aux liens de laction `question/show`  parce que tous les liens de nos template utilisent lassistant `link_to()`.
543
544 Il est temps du test final: raffichez la page daccueil, cliquez sur le titre de la premire question. Non seulement la premire question saffiche (montrant que rien nest cass) mais la barre dadresse de votre navigateur affiche:
545
546     http://askeet/frontend_dev.php/question/what-shall-i-do-tonight-with-my-girlfriend
547
548 Si vous voulez en apprendre plus  propos des dispositifs de routages, lisez le [chapitre sur la politique de routage] (http://www.symfony-project.com/content/book/page/routing.html) du livre Symfony.
549
550 A demain
551 --------
552
553 Aujourdhui, le site web en lui-mme na pas beaucoup de nouvelles fonctionnalits. Cependant, nous avons vu plus de codage sur les template, vous savez comment modifier le modle, et globalement le code  t refait  beaucoup dendroit.
554
555 Cela arrive tout le temps dans la vie dun projet Symfony: le code qui peut tre rutilis est refait en fragment ou en classe personnalise, le code qui apparat dans une action ou un template et qui appartient rellement au modle est dplac dans celui-ci. Mme si cela spare le code en un bon nombre de petits fichiers dissmins dans beaucoup de dossiers, la maintenance et lvolution est plus facile. De plus, la structure de fichiers dun projet Symfony le rend facile  localiser selon sa nature (Aide, modle, template, action, classe personnalise, etc.).
556
557 Le travail de refactorisation ralis aujourdhui va acclrer le dveloppement dans les jours  venir.
558 Et nous ferons priodiquement encore plus de refactorisation dans la vie de ce projet, puisque la manire dont nous dveloppons  faire une fonctionnalit utilisable sans se proccuper des  fonctionnalits  venir  require une bonne structure du code si nous ne voulons pas finir avec un dsordre total.
559
560 Quest-il prvu pour demain? Nous allons commencer  crire un formulaire et voir comment rcuprer ses informations. Nous allons galement diviser la liste des questions de la page daccueil en plusieurs pages. Dans le mme temps, nhsitez pas  tlcharger le code daujourdhui dans lespace de stockage SVN :
561  (tagged release_day_4) )  :
562
563     http://svn.askeet.com/tags/release_day_4/
564  
565 et  nous envoyer vos questions en utilisant la [mailing-list askeet] (mailto:askeet-subscribe@symfony-project.com)  ou le [forum ddi] (http://www.symfony-project.com/forum/index.php/f/8/).
566  
567 [1]: http://en.wikipedia.org/wiki/Agile_software_development  "Agile Software development definition at Wikipedia"
568 [2]: http://propel.phpdb.org/docs/user_guide/                 "Propel documentation"