| 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 |
 |
|---|
| 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 |
 |
|---|
| 296 |
 |
|---|
| 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 |
 |
|---|
| 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" |
|---|