Development

Documentation/fr_FR/jobeet/7 (diff)

You must first sign up to be able to contribute.

Changes from Version 1 of Documentation/fr_FR/jobeet/7

Show
Ignore:
Author:
sebastien.b (IP: 82.64.69.22)
Timestamp:
03/14/09 22:13:51 (9 years ago)
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Documentation/fr_FR/jobeet/7

    v0 v1  
     1{{{ 
     2#!html 
     3<div style="border: solid 2px #f00;padding:10px;margin:5px;background-color: #faa"> 
     4}}} 
     5Cette page fait partie de la traduction en français de la documentation de Symfony. Il s'agit d'une version traduite qui peut comporter des erreurs. La seule version officielle est la [http://www.symfony-project.org/jobeet/1_2/Propel/en/ version en anglais]. 
     6 
     7 * Traduction : Sébastien B 
     8 * Date de traduction : 14 Mars 
     9 * Date de dernière modification :  
     10{{{ 
     11#!html 
     12</div> 
     13<div style="width:85%"> 
     14}}} 
     15= Jour 7: Ajout de la page catégorie = 
     16 
     17Hier, vous avez pu améliorer vos connaissances dans plusieurs domaines : requêtes 
     18avec Propel, fixtures, routage, débuggage et la configuration personnalisée. 
     19Et nous avions fini la leçon en vous laissant un petit défi. 
     20 
     21Si vous avez travaillé sur la mise en place de la page catégorie, ce tutorial 
     22vous permettra de mieux compremdre. 
     23 
     24Prêt ? Voici une des implémentations possibles. 
     25 
     26== __La route des catégories__ == 
     27 
     28Pour commençer, nous devons ajouter une route pour utiliser des URL propres. 
     29Ajoutez ce code au début du fichier de configuration : 
     30{{{ 
     31    # apps/frontend/config/routing.yml 
     32    category: 
     33      url:      /category/:slug 
     34      class:    sfPropelRoute 
     35      param:    { module: category, action: show } 
     36      options:  { model: JobeetCategory, type: object } 
     37}}} 
     38>>'''__TIP__''' 
     39>A chaque fois que vous commencez à implémenter une nouvelle fonctionnalité, créer 
     40>une route pour les URL en premier est une bonne méthode de travail. Et c'est 
     41>obligatoire si jamais vous supprimez les règles de routage par défaut. 
     42 
     43Une route peut avoir comme paramètre n'importe quelle colonne de l'objet relationnel. 
     44Il est également possible d'utiliser n'importe quelle autre valeur si un accesseur 
     45est définit dans la classe de l'objet. Etant donné que le paramètre `slug` ne fait 
     46référence à aucune colonne dans la table `category`, nous devons ajouter un accessseur 
     47virtuel dans `JobeetCategory` pour faire fonctionner la route : 
     48{{{ 
     49#!php 
     50    // lib/model/JobeetCategory.php 
     51    public function getSlug() 
     52    { 
     53      return Jobeet::slugify($this->getName()); 
     54    } 
     55}}} 
     56== __Lien vers la catégorie__ == 
     57 
     58A présent, éditez le template `indexSuccess.php` du module `job` et ajoutez le lien 
     59vers la page de la catégorie : 
     60{{{ 
     61#!php 
     62    <!-- some HTML code --> 
     63 
     64            <h1> 
     65              <?php echo link_to($category, 'category', $category) ?> 
     66            </h1> 
     67 
     68    <!-- some HTML code --> 
     69 
     70          </table> 
     71 
     72          <?php if (($count = $category->countActiveJobs() - sfConfig::get('app_max_jobs_on_homepage')) > 0): ?> 
     73            <div class="more_jobs"> 
     74              and <?php echo link_to($count, 'category', $category) ?> 
     75              more... 
     76            </div> 
     77          <?php endif; ?> 
     78        </div> 
     79      <?php endforeach; ?> 
     80    </div> 
     81}}} 
     82Le lien est affiché seulement si la catégorie possède plus de 10 jobs. Pour faire 
     83fonctionner cette contrainte, nous allons ajouter la méthode `countActiveJobs()` 
     84au modèle `JobeetCategory` : 
     85{{{ 
     86#!php 
     87    // lib/model/JobeetCategory.php 
     88    public function countActiveJobs() 
     89    { 
     90      $criteria = new Criteria(); 
     91      $criteria->add(JobeetJobPeer::CATEGORY_ID, $this->getId()); 
     92 
     93      return JobeetJobPeer::countActiveJobs($criteria); 
     94    } 
     95}}} 
     96La méthode `countActiveJobs()` utilise la méthode `countActiveJobs()` qui n'existe 
     97pas encore dans le modèle `JobeetJobPeer`. Remplaçer le contenu du fichier `JobeetJobPeer.php` 
     98par le code suivant : 
     99{{{ 
     100#!php 
     101    // lib/model/JobeetJobPeer.php 
     102    class JobeetJobPeer extends BaseJobeetJobPeer 
     103    { 
     104      static public function getActiveJobs(Criteria $criteria = null) 
     105      { 
     106        return self::doSelect(self::addActiveJobsCriteria($criteria)); 
     107      } 
     108 
     109      static public function countActiveJobs(Criteria $criteria = null) 
     110      { 
     111        return self::doCount(self::addActiveJobsCriteria($criteria)); 
     112      } 
     113 
     114      static public function addActiveJobsCriteria(Criteria $criteria = null) 
     115      { 
     116        if (is_null($criteria)) 
     117        { 
     118          $criteria = new Criteria(); 
     119        } 
     120 
     121        $criteria->add(self::EXPIRES_AT, time(), Criteria::GREATER_THAN); 
     122        $criteria->addDescendingOrderByColumn(self::CREATED_AT); 
     123 
     124        return $criteria; 
     125      } 
     126 
     127      static public function doSelectActive(Criteria $criteria) 
     128      { 
     129        return self::doSelectOne(self::addActiveJobsCriteria($criteria)); 
     130      } 
     131    } 
     132}}} 
     133Comme vous pouvez le constater, nous avons refactorisé tout le code de `JobeetJobPeer` 
     134afin d'utiliser la nouvelle méthode `addActiveJobsCriteria()` qui est partagée. 
     135Notre code utilise maintenant la philosophie [http://en.wikipedia.org/wiki/Don%27t_repeat_yourself DRY (Don't Repeat Yourself)]. 
     136 
     137>>'''__TIP__''' 
     138>La première fois qu'une partie de code est réutilisée, le copier peut être suffisant. 
     139>Mais si vous en avez besoin pour d'autres utilisations, vous devez refactoriser 
     140>toutes les méthodes faisant appel à la fonction ou méthode partagée comme nous 
     141>venons de le faire. 
     142 
     143Au lieu d'utiliser `doSelect()` puis de compter le nombre de résultats dans la 
     144méthode `countActiveJobs()`, nous avons utilisé la méthode `doCount()`qui est beaucoup 
     145plus rapide. 
     146 
     147Nous venons de modifier beaucoup de fichiers pour cette simple fonctionnalité. Mais 
     148pour chaque ajout de code, nous avons essayé de le mettre dans la bonne couche de 
     149l'application et nous avons également essayé de rendre le code réutilisable. Dans 
     150la foulée, nous avons refactorisé du code existant. C'est une méthode de travail 
     151typique quand vous travaillez sur un projet symfony. 
     152 
     153[[Image(homepage.png)]] 
     154 
     155== __Job Category Module Creation__ == 
     156 
     157Il est temps de créer le module `category` : 
     158{{{ 
     159    $ php symfony generate:module frontend category 
     160}}} 
     161Si vous avez créer le module avant de lire ce tutoriel, vous avez sûrement utilisé 
     162la commande `propel:generate-module`. C'est correct mais 90% du code généré nous 
     163est inutile. La commande `generate:module` nous permet de créer un module vierge. 
     164 
     165>>'''__TIP__''' 
     166>Pourquoi ne pas simplement ajouter une action `category` au module `job` ? Nous 
     167>pourrions le faire mais le sujet principal de la page catégorie est une catégorie. 
     168>Il paraît donc normal de créer une module `category` à part entière. 
     169 
     170A l'accès de la page catégorie, la route `category` devra trouver la catégorie 
     171associée avec la variable `slug`. Mais étant donné que `slug` n'est pas stocké dans 
     172la base de donnée et parcequ'il est impossible d'en déduire la catégorie, nous 
     173n'avons aucun moyen de trouver la catégorie pour le moment. 
     174 
     175== __Mise à jour de la base de donnée__ == 
     176 
     177Nous devons ajouter la colonne `slug` à la table `category` :` 
     178{{{ 
     179    # config/schema.yml 
     180    propel: 
     181      jobeet_category: 
     182        id:           ~ 
     183        name:         { type: varchar(255), required: true } 
     184        slug:         { type: varchar(255), required: true, index: unique } 
     185}}} 
     186 
     187A présent, `slug` est une colonne réelle et vous pouvez donc supprimer la méthode 
     188`getSlug()` du modèle `JobeetCategory`. 
     189 
     190A chaque modification du nom de la catégorie, la valeur de `slug` doit être mise 
     191à jour. Ajoutons la méthode `setName()` : 
     192{{{ 
     193#!php 
     194    // lib/model/JobeetCategory.php 
     195    public function setName($name) 
     196    { 
     197      parent::setName($name); 
     198 
     199      $this->setSlug(Jobeet::slugify($name)); 
     200    } 
     201}}} 
     202 
     203Utilisez la commande `propel:build-all-load` pour mettre à jour les tables de la 
     204base de donnée et recharger les données grâce aux fixtures : 
     205{{{ 
     206    $ php symfony propel:build-all-load --no-confirmation 
     207}}} 
     208Nous pouvons maintenant créer la méthode `executeShow()`. Remplaçez le code du 
     209fichier des actions du module catégorie par : 
     210{{{ 
     211#!php 
     212    // apps/frontend/modules/category/actions/actions.class.php 
     213    class categoryActions extends sfActions 
     214    { 
     215      public function executeShow(sfWebRequest $request) 
     216      { 
     217        $this->category = $this->getRoute()->getObject(); 
     218      } 
     219    } 
     220}}} 
     221>>'''__NOTE__''' 
     222>Puisque nous venons de supprimer la méthode `executeIndex()`, vous pouvez 
     223>supprimer le template `indexSuccess.php` qui a été généré automatiquement 
     224>(`apps/frontend/modules/category/templates/indexSuccess.php`). 
     225 
     226Pour la dernière étape, il rest à créer le template `showSuccess.php` : 
     227{{{ 
     228#!php 
     229    // apps/frontend/modules/category/templates/showSuccess.php 
     230    <?php use_stylesheet('jobs.css') ?> 
     231 
     232    <?php slot('title', sprintf('Jobs in the %s category', $category->getName())) ?> 
     233 
     234    <div class="category"> 
     235      <div class="feed"> 
     236        <a href="">Feed</a> 
     237      </div> 
     238      <h1><?php echo $category ?></h1> 
     239    </div> 
     240 
     241    <table class="jobs"> 
     242      <?php foreach ($category->getActiveJobs() as $i => $job): ?> 
     243        <tr class="<?php echo fmod($i, 2) ? 'even' : 'odd' ?>"> 
     244          <td class="location"> 
     245            <?php echo $job->getLocation() ?> 
     246          </td> 
     247          <td class="position"> 
     248            <?php echo link_to($job->getPosition(), 'job_show_user', $job) ?> 
     249          </td> 
     250          <td class="company"> 
     251            <?php echo $job->getCompany() ?> 
     252          </td> 
     253        </tr> 
     254      <?php endforeach; ?> 
     255    </table> 
     256}}} 
     257== __Partials__ == 
     258 
     259Notez que nous avons fait un copié/collé du bloc `<table>` du template `indexSuccess.php` 
     260du module job qui crée la liste des jobs. C'est une mauvaise façon de faire. Il 
     261est temps d'apprendre une nouvelle astuce. Qaund vous avez besoin de réutiliser 
     262une portion de code d'une template,vous pouvez créer un **partial**. Un partial 
     263est une partie de code d'un template qui peut être partagé avec d'autres templates. 
     264Un partial, est simplement un autre template dont le nom commença par un 
     265underscore (`_`). 
     266 
     267Créez le fichier `_list.php` : 
     268{{{ 
     269#!php 
     270    // apps/frontend/modules/job/templates/_list.php 
     271    <table class="jobs"> 
     272      <?php foreach ($jobs as $i => $job): ?> 
     273        <tr class="<?php echo fmod($i, 2) ? 'even' : 'odd' ?>"> 
     274          <td class="location"> 
     275            <?php echo $job->getLocation() ?> 
     276          </td> 
     277          <td class="position"> 
     278            <?php echo link_to($job->getPosition(), 'job_show_user', $job) ?> 
     279          </td> 
     280          <td class="company"> 
     281            <?php echo $job->getCompany() ?> 
     282          </td> 
     283        </tr> 
     284      <?php endforeach; ?> 
     285    </table> 
     286}}} 
     287Vous pouvez inclure un partial en utilisant le helper `include_partial()` : 
     288{{{ 
     289#!php 
     290    <?php include_partial('job/list', array('jobs' => $jobs)) ?> 
     291}}} 
     292Le premier argument du helper `include_partial()` est le nom du partial ( nom du 
     293module source , un slash `/`, et le nom du partial sans le underscore `_`. Le 
     294second argument est un tableau contenant les variables à passer dans le partial. 
     295 
     296>>'''__NOTE__''' 
     297>Pourquoi ne pas utiliser la méthode `include()` intégrée à PHP plutôt que le 
     298>helper `include_partial()` ? La principale différence est le support du cache 
     299>intégré au helper `include_partial()`. 
     300 
     301Remplaçez le bloc de code HTML `<table>` dans les deux templates par un appel 
     302`include_partial()` : 
     303{{{ 
     304#!php 
     305    // in apps/frontend/modules/job/templates/indexSuccess.php 
     306    <?php include_partial('job/list', array('jobs' => $category->getActiveJobs(sfConfig::get('app_max_jobs_on_homepage')))) ?> 
     307 
     308    // in apps/frontend/modules/category/templates/showSuccess.php 
     309    <?php include_partial('job/list', array('jobs' => $category->getActiveJobs())) ?> 
     310}}} 
     311== __Pagination__ == 
     312 
     313Conditions du Jour 2 : 
     314 
     315  "La page doit afficher une liste de 20 jobs avec une pagination." 
     316 
     317Pour paginer une liste d'objets Propel, symfony fournit une class dédiée : 
     318[http://www.symfony-project.org/api/1_2/sfPropelPager `sfPropelPager`]. Dans 
     319le fichier d'actions du module `category`, au lieu de passer les objets job au 
     320template `showSuccess`, nous allons passer un paginateur : 
     321{{{ 
     322#!php 
     323    // apps/frontend/modules/category/actions/actions.class.php 
     324    public function executeShow(sfWebRequest $request) 
     325    { 
     326      $this->category = $this->getRoute()->getObject(); 
     327 
     328      $this->pager = new sfPropelPager( 
     329        'JobeetJob', 
     330        sfConfig::get('app_max_jobs_on_category') 
     331      ); 
     332      $this->pager->setCriteria($this->category->getActiveJobsCriteria()); 
     333      $this->pager->setPage($request->getParameter('page', 1)); 
     334      $this->pager->init(); 
     335    } 
     336}}} 
     337>>'''__TIP__''' 
     338>La méthode `sfRequest::getParameter()` utilise une valeur par défaut pour le 
     339>second argument. Dans l'action ci-dessus, si le paramètre requis `page` n'existe 
     340>pas, alors `getParameter()` retournera `1`. 
     341 
     342Le constructeur `sfPropelPager` utilise la classe du modèle et le nombre maximum 
     343d'items à afficher par page. Ajouter la dernière ligne au fichier de configuration : 
     344{{{ 
     345    # apps/frontend/config/app.yml 
     346    all: 
     347      active_days:          30 
     348      max_jobs_on_homepage: 10 
     349      max_jobs_on_category: 20 
     350}}} 
     351La méthode `sfPropelPager::setCriteria()` prend un objet `Criteria` à utiliser 
     352pour récupérer les items de la base de données. 
     353 
     354Et la méthode `getActiveJobsCriteria()` : 
     355{{{ 
     356#!php 
     357    // lib/model/JobeetCategory.php 
     358    public function getActiveJobsCriteria() 
     359    { 
     360      $criteria = new Criteria(); 
     361      $criteria->add(JobeetJobPeer::CATEGORY_ID, $this->getId()); 
     362 
     363      return JobeetJobPeer::addActiveJobsCriteria($criteria); 
     364    } 
     365}}} 
     366 
     367Maintenant que nous avons définit la méthode `getActiveJobsCriteria()`, nous 
     368pouvons refactoriser les autres méthodes du modèle `JobeetCategory` qui l'utilise : 
     369{{{ 
     370#!php 
     371    // lib/model/JobeetCategory.php 
     372    public function getActiveJobs($max = 10) 
     373    { 
     374      $criteria = $this->getActiveJobsCriteria(); 
     375      $criteria->setLimit($max); 
     376 
     377      return JobeetJobPeer::doSelect($criteria); 
     378    } 
     379 
     380    public function countActiveJobs() 
     381    { 
     382      $criteria = $this->getActiveJobsCriteria(); 
     383 
     384      return JobeetJobPeer::doCount($criteria); 
     385    } 
     386}}} 
     387 
     388Et pour finir, mettons le template à jour : 
     389{{{ 
     390#!php 
     391    <!-- apps/frontend/modules/category/templates/showSuccess.php --> 
     392    <?php use_stylesheet('jobs.css') ?> 
     393 
     394    <?php slot('title', sprintf('Jobs in the %s category', $category->getName())) ?> 
     395 
     396    <div class="category"> 
     397      <div class="feed"> 
     398        <a href="">Feed</a> 
     399      </div> 
     400      <h1><?php echo $category ?></h1> 
     401    </div> 
     402 
     403    <?php include_partial('job/list', array('jobs' => $pager->getResults())) ?> 
     404 
     405    <?php if ($pager->haveToPaginate()): ?> 
     406      <div class="pagination"> 
     407        <a href="<?php echo url_for('category', $category) ?>?page=1"> 
     408          <img src="/images/first.png" alt="First page" /> 
     409        </a> 
     410 
     411        <a href="<?php echo url_for('category', $category) ?>?page=<?php echo $pager->getPreviousPage() ?>"> 
     412          <img src="/images/previous.png" alt="Previous page" title="Previous page" /> 
     413        </a> 
     414 
     415        <?php foreach ($pager->getLinks() as $page): ?> 
     416          <?php if ($page == $pager->getPage()): ?> 
     417            <?php echo $page ?> 
     418          <?php else: ?> 
     419            <a href="<?php echo url_for('category', $category) ?>?page=<?php echo $page ?>"><?php echo $page ?></a> 
     420          <?php endif; ?> 
     421        <?php endforeach; ?> 
     422 
     423        <a href="<?php echo url_for('category', $category) ?>?page=<?php echo $pager->getNextPage() ?>"> 
     424          <img src="/images/next.png" alt="Next page" title="Next page" /> 
     425        </a> 
     426 
     427        <a href="<?php echo url_for('category', $category) ?>?page=<?php echo $pager->getLastPage() ?>"> 
     428          <img src="/images/last.png" alt="Last page" title="Last page" /> 
     429        </a> 
     430      </div> 
     431    <?php endif; ?> 
     432 
     433    <div class="pagination_desc"> 
     434      <strong><?php echo $pager->getNbResults() ?></strong> jobs in this category 
     435 
     436      <?php if ($pager->haveToPaginate()): ?> 
     437        - page <strong><?php echo $pager->getPage() ?>/<?php echo $pager->getLastPage() ?></strong> 
     438      <?php endif; ?> 
     439    </div> 
     440}}} 
     441La majeure partie de ce code utilise des liens vers les différentes pages. Voici 
     442la liste des méthodes de `sfPropelPager` employées dans ce template : 
     443{{{ 
     444  * `getResults()`: Returns an array of Propel objects for the current page 
     445  * `getNbResults()`: Returns the total number of results 
     446  * `haveToPaginate()`: Returns `true` if there is more than one page 
     447  * `getLinks()`: Returns a list of page links to display 
     448  * `getPage()`: Returns the current page number 
     449  * `getPreviousPage()`: Returns the previous page number 
     450  * `getNextPage()`: Returns the next page number 
     451  * `getLastPage()`: Returns the last page number 
     452}}} 
     453 
     454[[Image(pagination.png)]] 
     455 
     456== __A demain__ == 
     457 
     458Si vous avez travaillé sur votre propre inétgration des catégories hier et que vous 
     459avez le sentiment de ne rien avoir appris aujourd'hui, alors vous commençez à 
     460comprendre la philosophie de symfony. Le processus pour ajouter une nouvelle 
     461fonctionnalité à un site symfony est toujours le même : 
     462- penser les URLs 
     463- créer actions nécessaires 
     464- mettre à jour le modèle 
     465- et écrire quelques templates. 
     466Si vous appliquez ces bonnes méthodes de développement, vous deviendrez très vite 
     467un expert de symfony. 
     468 
     469Demain nous entamerons une nouvelle semaine de travail sur Jobeet. Pour fêter ça, 
     470nous aborderons une nouveauté : les tests. 
     471{{{ 
     472#!html 
     473</div> 
     474}}}