Development

Documentation/fr_FR/jobeet/doctrine/6 (diff)

You must first sign up to be able to contribute.

Changes from Version 1 of Documentation/fr_FR/jobeet/doctrine/6

Show
Ignore:
Author:
sebastien.b (IP: 82.254.103.74)
Timestamp:
03/12/09 22:33:17 (9 years ago)
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Documentation/fr_FR/jobeet/doctrine/6

    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/Doctrine/en/ version en anglais]. 
     6 
     7 * Traduction : Sébastien B 
     8 * Date de traduction : 12 Mars 2009 
     9 * Date de dernière modification : 12 Mars 2009 
     10{{{ 
     11#!html 
     12</div> 
     13}}} 
     14= Jour 6: Aller plus loin avec le Modèle = 
     15 
     16Hier était un grand jour. Vous avez appris comment créer des URLs propres et comment[[BR]] 
     17utiliser symfony pour automatiser beaucoup de choses.[[BR]][[BR]] 
     18 
     19Aujourd'hui, nous allons améliorer Jobeet en optimisant le code ci et là. Vous en[[BR]] 
     20apprendrez plus sur toutes les fonctions que nous avons déjà présenté au cours[[BR]] 
     21des jours précédents.[[BR]][[BR]] 
     22 
     23== __L'objet Doctrine Query__ == 
     24 
     25Conditions du Jour 2 :[[BR]] 
     26 
     27  === "Quand un utilisateur arrive sur Jobeet, il doit voir la liste des jobs actifs." === 
     28 
     29Mais pour l'instant, tous les jobs sont affichés, qu'ils soient actifs ou non :[[BR]] 
     30{{{ 
     31#!php 
     32    // apps/frontend/modules/job/actions/actions.class.php 
     33    class jobActions extends sfActions 
     34    { 
     35      public function executeIndex(sfWebRequest $request) 
     36      { 
     37        $this->jobeet_job_list = Doctrine::getTable('JobeetJob') 
     38          ->createQuery('a') 
     39          ->execute(); 
     40      } 
     41 
     42      // ... 
     43    } 
     44}}} 
     45Un job est considéré actif s'il a été posté il y a moins de 30 jours. La méthode[[BR]] 
     46`~Doctrine_Query~::execute()` crée la requête à exécuter sur la base de données.[[BR]] 
     47Dans le code ci-dessus, aucune condition n'est spécifiée, ce qui signifie que[[BR]] 
     48tous les enregistrements seront retournés.[[BR]] 
     49 
     50Modifions cela pour n'afficher que les jobs actifs : 
     51{{{ 
     52#!php 
     53    public function executeIndex(sfWebRequest $request) 
     54    { 
     55      $q = Doctrine_Query::create() 
     56        ->from('JobeetJob j') 
     57        ->where('j.created_at > ?', 
     58         âž¥ date('Y-m-d h:i:s', time() - 86400 * 30)); 
     59 
     60      $this->jobeet_job_list = $q->execute(); 
     61    } 
     62}}} 
     63 
     64== __Debugging Doctrine generated SQL__ == 
     65 
     66Etant donné que vous n'écrivez pas les requêtes SQL, c'est Doctrine qui prend en[[BR]] 
     67charge la génération des requêtes propres au moteur de base de donnée choisi le[[BR]] 
     683ème jour. Mais il est parfois utile de connaître le code SQL généré par Doctrine;.[[BR]] 
     69Par exemple, pour debugger une requête qui ne fonctionne pas. Dans l'environnement[[BR]] 
     70de développement, symfony enregistre toutes ces requêtes ( ainsi que beaucoup[[BR]] 
     71d'autres informations ) dans le répertoire `log/`. There is one log file for every[[BR]] 
     72combination of an application and an environment. Le fichier qui nous intéresse[[BR]] 
     73est `frontend_dev.log` : 
     74{{{ 
     75    Dec 04 13:58:33 symfony [info] {sfDoctrineLogger} executeQuery : SELECT 
     76    j.id AS j__id, j.category_id AS j__category_id, j.type AS j__type, 
     77    j.company AS j__company, j.logo AS j__logo, j.url AS j__url, 
     78    j.position AS j__position, j.location AS j__location, 
     79    j.description AS j__description, j.how_to_apply AS j__how_to_apply, 
     80    j.token AS j__token, j.is_public AS j__is_public, 
     81    j.is_activated AS j__is_activated, j.email AS j__email, 
     82    j.expires_at AS j__expires_at, j.created_at AS j__created_at, 
     83    j.updated_at AS j__updated_at FROM jobeet_job j 
     84    WHERE j.created_at > ? (2008-11-08 01:13:35) 
     85}}} 
     86Comme vous pouvez le voir, Doctrine a généré une clause WHERE pour la colonne[[BR]] 
     87`created_at` column (`WHERE j.created_at > ?`).[[BR]] 
     88 
     89>>'''__NOTE__''' 
     90>Dans la requête, `?` indique que Doctrine utilise les conditions déclarées.[[BR]] 
     91>La valeur actuelle de `?` ( '2008-11-08 01:13:35' dans l'exemple ci-dessus )[[BR]] 
     92>est passée pendant l'exécution de la requête. L'utilisation de conditions[[BR]] 
     93>déclarées reduit considérablement le risque d'attaques du type [~SQL injection~](http://en.wikipedia.org/wiki/Sql_injection) 
     94 
     95Le travail est facilité mais devoir basculer entre le navigateur, l'IDE et le[[BR]] 
     96fichier log à chaque fois que l'on veut tester une modification est assez contraignant.[[BR]] 
     97Heureusement, symfony possède une barre d'outil de débuggage. Toutes les informations[[BR]] 
     98nécessaire sont disponibles dans votre navigateur :[[BR]] 
     99 
     100[[Image(web_debug_sql.png)]] 
     101 
     102== __Object ~Serialization~__ == 
     103 
     104Jusqu'à présent, notre code fonctionne mais il est loin d'être parfait et ne prend[[BR]] 
     105pas en charge les contraintes évoqués le 2ème jour :[[BR]] 
     106 
     107  === "Un utilisateur peut activer à nouveau ou augmenter la validité de l'offre d'emploi pour une période de 30 jours supplémentaires..." === 
     108 
     109Le code actuel se base sur la valeur de la colonne `created_at` qui stocke la[[BR]] 
     110date de création ce qui ne nous permet pas de satisfaire la condition ci-dessus.[[BR]][[BR]] 
     111 
     112Mais si vous vous rappelez le schéma de la base décrit le 3ème jour, nous avons[[BR]] 
     113aussi défini une colonne `expires_at`. Pour l'instant cette valeur est vide car[[BR]] 
     114nous ne l'avons pas renseignée dans le fichier du répertoire fixture. Cette valeur[[BR]] 
     115peut être automatiquement renseignée à plus 30 jours à partir de la date courante[[BR]] 
     116lors de la création d'un job.[[BR]] 
     117 
     118Quand vous devez créer une action automatique avant que l'objet Doctrine soit[[BR]] 
     119sérialisé dans la base, vous pouvez surpasser la méthode `save()` de la classe[[BR]] 
     120du modèle : 
     121{{{ 
     122#!php 
     123    // lib/model/doctrine/JobeetJob.class.php 
     124    class JobeetJob extends BaseJobeetJob 
     125    { 
     126      public function save(Doctrine_Connection $conn = null) 
     127      { 
     128        if ($this->isNew() && !$this->getExpiresAt()) 
     129        { 
     130          $now = $this->getCreatedAt() ? strtotime($this->getCreatedAt()) : time(); 
     131          $this->setExpiresAt(date('Y-m-d h:i:s', $now + 86400 * 30)); 
     132        } 
     133 
     134        return parent::save($conn); 
     135      } 
     136 
     137      // ... 
     138    } 
     139}}} 
     140La méthode `~isNew()~` renvoie `true` quand l'objet n'est pas encore sérialisé[[BR]] 
     141dans la base et `false` dans la cas contraire.[[BR]][[BR]] 
     142 
     143A présent, modifions l'action pour récupérer les jobs actifs en utilisant la[[BR]] 
     144colonne `expires_at` au lieu de la colonne `created_at` : [[BR]] 
     145{{{ 
     146#!php 
     147    public function executeIndex(sfWebRequest $request) 
     148    { 
     149      $q = Doctrine_Query::create() 
     150        ->from('JobeetJob j') 
     151        ->where('j.expires_at > ?', date('Y-m-d h:i:s', time())); 
     152 
     153      $this->jobeet_job_list = $q->execute(); 
     154    } 
     155}}} 
     156La requête sélectionnera seulement les jobs possédant une date `expires_at`.[[BR]][[BR]] 
     157 
     158== __Aller plus loin avec Fixtures__ == 
     159 
     160Si vous actualisez la page d'accueil de Jobeet, vous ne constaterez aucun[[BR]] 
     161changement. Modifions le fichier fixture pour ajouter un job qui a expiré : 
     162{{{ 
     163    # data/fixtures/jobs.yml 
     164    JobeetJob: 
     165      # other jobs 
     166 
     167      expired_job: 
     168        JobeetCategory: programming 
     169        company:        Sensio Labs 
     170        position:       Web Developer 
     171        location:       Paris, France 
     172        description:    Lorem ipsum dolor sit amet, consectetur adipisicing elit. 
     173        how_to_apply:   Send your resume to lorem.ipsum [at] dolor.sit 
     174        is_public:      true 
     175        is_activated:   true 
     176        expires_at:     '2005-12-01 00:00:00' 
     177        token:          job_expired 
     178        email:          job@example.com 
     179}}} 
     180>>'''__NOTE__'''[[BR]] 
     181>Faites bien attention quand vous faîtes un copier/coller du code. il faut conserver[[BR]] 
     182>l'indentation. Il doit y avoir deux espaces devant `expired_job`.[[BR]] 
     183 
     184Comme vous pouvez le constater, il est possible de définir une valeur pour la colonne[[BR]] 
     185`created_at` même si elle est automatiquement remplie par Doctrine. La valeur[[BR]] 
     186définie sera utilisé à la place de la valeur automatique. Rechargez les fixtures[[BR]] 
     187et actualisez la page d'accueil pour vérifier que l'ancien job n'apparaisse pas :[[BR]] 
     188{{{ 
     189    $ php symfony Doctrine:data-load 
     190}}} 
     191Vous pouvez aussi exécuter la requête suivante pour être sûr que la colonne `expires_at`[[BR]] 
     192soit automatiquement renseignée en fonction de la valeur de la colonne `created_at`[[BR]] 
     193grâce à la méthode `save()` :[[BR]] 
     194{{{ 
     195    SELECT `position`, `created_at`, `expires_at` FROM `jobeet_job`; 
     196}}} 
     197== __Configuration Personnalisée__ == 
     198 
     199Dans la méthode `JobeetJob::save()`, nous avons figé le nombre de jours qui détermine[[BR]] 
     200l'expiration d'un job. Il serait préférable que la valeur de 30 jours soit paramétrable.[[BR]] 
     201symfony utilise le fichier configuration interne `~app.yml~` qui permet de définir[[BR]] 
     202des paramètres spécifiques à l'application. Ce fichier YAML peut contenir[[BR]] 
     203n'importe quel paramètre nécessaire : 
     204{{{ 
     205    # apps/frontend/config/app.yml 
     206    all: 
     207      active_days: 30 
     208}}} 
     209Dans l'application, ces paramètres sont disponibles à travers la classe globale[[BR]] 
     210`~sfConfig~` : 
     211{{{ 
     212#!php 
     213    sfConfig::get('app_active_days') 
     214}}} 
     215Les paramètres utilisent le préfixe `app_` car la classe `sfConfig` fournit[[BR]] 
     216également des accès aux paramètres symfony que nous verrons plus tard.[[BR]] 
     217 
     218Mettez le code à jour pour prendre en compte ce nouveau paramètre : 
     219{{{ 
     220#!php 
     221    public function save(Doctrine_Connection $conn = null) 
     222    { 
     223      if ($this->isNew() && !$this->getExpiresAt()) 
     224      { 
     225        $now = $this->getCreatedAt() ? strtotime($this->getCreatedAt()) : time(); 
     226        $this->setExpiresAt(date('Y-m-d h:i:s', $now + 86400 * sfConfig::get('app_active_days'))); 
     227      } 
     228 
     229      return parent::save($conn); 
     230    } 
     231}}} 
     232Le fichier de configuration `app.yml` est un bon moyen de centraliser les paramètres[[BR]] 
     233globaux de votre application.[[BR]][[BR]] 
     234 
     235Pour finir, si vous avez besoin de définir des paramètres étendus, il suffit de[[BR]] 
     236créer un nouveau fichier `app.yml` dans le répertoire `config` à la racine de[[BR]] 
     237votre projet symfony.[[BR]][[BR]] 
     238 
     239== __Refactoring__ == 
     240 
     241Bien que notre code fonctionne correctement, il n'est pas encore parfait.[[BR]] 
     242Etes-vous capable de repérer le problème ?[[BR]][[BR]] 
     243 
     244Le code `Doctrine_Query` n'appartient pas à l'action ( couche Controlleur ), mais 
     245à la couche Modèle. Dans le modèle ~MVC~, le Modèle définit toute la ~logique métier~, 
     246et le Controlleur récupère les données du Modèle. Etant donné que le code renvoie 
     247une collection de jobs, déplaçons le dans la classe `JobeetJobPeer` et créons la 
     248méthode `getActiveJobs()` : 
     249{{{ 
     250#!php 
     251    // lib/model/doctrine/JobeetJobTable.class.php 
     252    class JobeetJobTable extends Doctrine_Table 
     253    { 
     254      public function getActiveJobs() 
     255      { 
     256        $q = $this->createQuery('j') 
     257          ->where('j.expires_at > ?', date('Y-m-d h:i:s', time())); 
     258 
     259        return $q->execute(); 
     260      } 
     261    } 
     262}}} 
     263Il est maintenant possible de récupérer les jobs actifs grâce à cette méthode dans[[BR]] 
     264les actions : 
     265{{{ 
     266#!php 
     267    public function executeIndex(sfWebRequest $request) 
     268    { 
     269      $this->jobeet_job_list = Doctrine::getTable('JobeetJob')->getActiveJobs(); 
     270    } 
     271}}} 
     272La refactorisation présente plusieurs avantages :[[BR]][[BR]] 
     273 
     274 * Le code pour récupérer les jobs actifs se trouve dans la Modèle, là ou est sa place[[BR]] 
     275 * Le code du Controlleur est plus lisible[[BR]] 
     276 * La méthode `getActiveJobs()` est réutilisable ( dans une autre action par exemple )[[BR]] 
     277 * Le code du modèle peut être testé indépendemment[[BR]] 
     278 
     279Récupérons les jobs grâce à la colonne `expires_at` : 
     280{{{ 
     281#!php 
     282    public function getActiveJobs() 
     283    { 
     284      $q = $this->createQuery('j') 
     285        ->where('j.expires_at > ?', date('Y-m-d h:i:s', time())) 
     286        ->orderBy('j.expires_at DESC'); 
     287 
     288      return $q->execute(); 
     289    } 
     290}}} 
     291La méthode `orderBy` ajoute une clauser `ORDER BY` à la requête.[[BR]] 
     292Il existe aussi la méthode `addOrderBy()`.[[BR]] 
     293 
     294== __Catégories en page d'accueil__ == 
     295 
     296Conditions du 2ème jour :[[BR]][[BR]] 
     297 
     298  === "Les jobs sont affichés par catégorie et par leur date de publication ( les nouveaux jobs en tête de liste." === 
     299 
     300Jusqu'à présent, nous n'avons pas pris en compte la catégorie associée aux jobs.[[BR]] 
     301Afin d'afficher les jobs par catégorie, nous allons d'abord récupérer toutes les[[BR]] 
     302catégories asscoiées à au moins un job.[[BR]][[BR]] 
     303 
     304Editer la classe `JobeetCategoryTable` et ajouter la méthode `getWithJobs()` : 
     305{{{ 
     306#!php 
     307    // lib/model/doctrine/JobeetCategoryTable.class.php 
     308    class JobeetCategoryTable extends Doctrine_Table 
     309    { 
     310      public function getWithJobs() 
     311      { 
     312        $q = $this->createQuery('c') 
     313          ->leftJoin('c.JobeetJobs j') 
     314          ->where('j.expires_at > ?', date('Y-m-d h:i:s', time())); 
     315 
     316        return $q->execute(); 
     317      } 
     318    } 
     319}}} 
     320 
     321Modifiez l'action `index` en conséquence : 
     322{{{ 
     323#!php 
     324    // apps/frontend/modules/job/actions/actions.class.php 
     325    public function executeIndex(sfWebRequest $request) 
     326    { 
     327      $this->categories = Doctrine::getTable('JobeetCategory')->getWithJobs(); 
     328    } 
     329}}} 
     330Dans le template, nous devons rechercher les jobs actifs dans chaque catégorie et[[BR]] 
     331les afficher. 
     332{{{ 
     333#!php 
     334    // apps/frontend/modules/job/indexSuccess.php 
     335    <?php use_stylesheet('jobs.css') ?> 
     336 
     337    <div id="jobs"> 
     338      <?php foreach ($categories as $category): ?> 
     339        <div class="category_<?php echo Jobeet::slugify($category->getName()) ?>"> 
     340          <div class="category"> 
     341            <div class="feed"> 
     342              <a href="">Feed</a> 
     343            </div> 
     344            <h1><?php echo $category ?></h1> 
     345          </div> 
     346 
     347          <table class="jobs"> 
     348            <?php foreach ($category->getActiveJobs() as $i => $job): ?> 
     349              <tr class="<?php echo fmod($i, 2) ? 'even' : 'odd' ?>"> 
     350                <td class="location"> 
     351                  <?php echo $job->getLocation() ?> 
     352                </td> 
     353                <td class="position"> 
     354                  <?php echo link_to($job->getPosition(), 'job_show_user', $job) ?> 
     355                </td> 
     356                <td class="company"> 
     357                  <?php echo $job->getCompany() ?> 
     358                </td> 
     359              </tr> 
     360            <?php endforeach; ?> 
     361          </table> 
     362        </div> 
     363      <?php endforeach; ?> 
     364    </div> 
     365}}} 
     366>>'''__NOTE__'''[[BR]] 
     367>Pour afficher le nom d'une catégorie, nous utilisons `echo $category` dans le template.[[BR]] 
     368>Ca vous paraît bizarre ? `$category` n'est pas un objet, comment peut-on afficher[[BR]] 
     369>le nom de la catégorie avec un `echo`. La réponse se trouve au jour 3 quand nous[[BR]] 
     370>avons défini la méthode magique `__toString()` pour toutes les classes du modèle.[[BR]] 
     371 
     372Nous devons ajouter la méthode `getActiveJobs()` à la classe `JobeetCategory` qui[[BR]] 
     373retoune les jobs actifs pour l'objet catégorie : 
     374{{{ 
     375#!php 
     376    // lib/model/doctrine/JobeetCategory.class.php 
     377    public function getActiveJobs() 
     378    { 
     379      $q = Doctrine_Query::create() 
     380        ->from('JobeetJob j') 
     381        ->where('j.category_id = ?', $this->getId()); 
     382 
     383      return Doctrine::getTable('JobeetJob')->getActiveJobs($q); 
     384    } 
     385}}} 
     386La méthode JobeetCategory::getActiveJobs()` utilise la méthode `Doctrine::getTable('JobeetJob')->getActiveJobs()`[[BR]] 
     387pour rechercher les jobs actifs de la catégorie donnée.[[BR]] 
     388 
     389A l'appel de `Doctrine::getTable('JobeetJob')->getActiveJobs()`, nous voulons[[BR]] 
     390restreindre la condition autrement qu'en fournissant uniquement une catégorie.[[BR]] 
     391Au lieu de passer l'objet catégorie, nous avons décidé de passer un objet `Doctrine_Query`[[BR]] 
     392qui est la meilleure solution pour encapsuler une condition générique.[[BR]] 
     393 
     394Pour ce faire, il faut fusionner cet objet `Doctrine_Query` avec les criteria de la[[BR]] 
     395méthode`getActiveJobs()`. `Doctrine_Query` étant un objet, ce sera simple :[[BR]] 
     396{{{ 
     397#!php 
     398    // lib/model/doctrine/JobeetJobTable.class.php 
     399    public function getActiveJobs(Doctrine_Query $q = null) 
     400    { 
     401      if (is_null($q)) 
     402      { 
     403        $q = Doctrine_Query::create() 
     404          ->from('JobeetJob j'); 
     405      } 
     406 
     407      $q->andWhere('j.expires_at > ?', date('Y-m-d h:i:s', time())) 
     408        ->addOrderBy('j.expires_at DESC'); 
     409 
     410      return $q->execute(); 
     411    } 
     412}}} 
     413 
     414== __Limiter les résultats__ == 
     415 
     416Il reste encore une condition à implémenter pour la liste des jobs en page d'accueil :[[BR]] 
     417 
     418  === "Chaque catégorie doit afficher les 10 premiers jobs et un lien doit permettre d'afficher tous les jobs d'une catégorie choisie." === 
     419 
     420C'est assez simple de l'ajouter à la méthode `getActiveJobs()` : 
     421{{{ 
     422#!php 
     423    // lib/model/doctrine/JobeetCategory.class.php 
     424    public function getActiveJobs($max = 10) 
     425    { 
     426      $q = Doctrine_Query::create() 
     427        ->from('JobeetJob j') 
     428        ->where('j.category_id = ?', $this->getId()) 
     429        ->limit($max); 
     430 
     431      return Doctrine::getTable('JobeetJob')->getActiveJobs($q); 
     432    } 
     433}}} 
     434La clause `~LIMIT~` est figée dans le code du Modèle, mais il est préférable de[[BR]] 
     435pouvoir configurer cette valeur. Modifiez le template pour utiliser le nombre[[BR]] 
     436maximum de jobs configuré dans `app.yml` : 
     437{{{ 
     438#!php 
     439    <!-- apps/frontend/modules/job/indexSuccess.php --> 
     440    <?php foreach ($category->getActiveJobs(sfConfig::get('app_max_jobs_on_homepage')) as $i => $job): ?> 
     441}}} 
     442et ajoutez le nouveau paramètre dans `app.yml`: 
     443{{{ 
     444    all: 
     445      active_days:          30 
     446      max_jobs_on_homepage: 10 
     447}}} 
     448 
     449[[Image(homepage.png)]] 
     450 
     451== __Fixtures dynamiques__ == 
     452 
     453A moins de passer la valeur `max_jobs_on_homepage` à une, vous ne verrez aucune[[BR]] 
     454différence. Nous devons ajouter des jobs ans le fichier ~fixture~s. Evidemment,[[BR]] 
     455vous pouvez faire 20, 30, ... copier/coller des jobs existants mais il y a une[[BR]] 
     456meilleure solution. La duplication n'est pas une bonne méthode, même pour les fichiers[[BR]] 
     457fixture.[[BR]][[BR]] 
     458 
     459symfony à la rescousse ! Dans symfony, les fichiers ~YAML~ peuvent contenir du code[[BR]] 
     460PHP qui sera évalué juste avant l'analyse du fichier. Editez le fichier fixture[[BR]] 
     461`jobs.yml` et ajoutez le code suivant à la fin : 
     462{{{ 
     463#!php 
     464    JobeetJob: 
     465    # Starts at the beginning of the line (no whitespace before) 
     466    <?php for ($i = 100; $i <= 130; $i++): ?> 
     467      job_<?php echo $i ?>: 
     468        JobeetCategory: programming 
     469        company:      Company <?php echo $i."\n" ?> 
     470        position:     Web Developer 
     471        location:     Paris, France 
     472        description:  Lorem ipsum dolor sit amet, consectetur adipisicing elit. 
     473        how_to_apply: | 
     474          Send your resume to lorem.ipsum [at] company_<?php echo $i ?>.sit 
     475        is_public:    true 
     476        is_activated: true 
     477        token:        job_<?php echo $i."\n" ?> 
     478        email:        job@example.com 
     479 
     480    <?php endfor; ?> 
     481}}} 
     482Attention ! L'analyseur de YAML n'aime pas les erreurs de formatage. Gardez bien[[BR]] 
     483à l'esprit les conseils suivants si vous ajoutez du code PHP dans un fichier YAML :[[BR]][[BR]] 
     484 
     485 * `<?php ?>` doit toujours commençer une ligne ou être intégré dans une valeur.[[BR]] 
     486 * Si `<?php ?>` fint une ligne, vous devez indiquer clairement une nouvelle ligne ("\n").[[BR]] 
     487 
     488Rechargez les données fixtures avec `Doctrine:data-load` et vérifiez que seulement[[BR]] 
     489`10` jobs soient affichés en page d'accueil pour la catégorie `Programming`.[[BR]] 
     490Dans la capture d'écran suivante, nous avons modifié le nombre maximum de jobs sur[[BR]] 
     4915 pour avoir une image de taille raisonnable : 
     492 
     493[[Image(pagination.png)]] 
     494 
     495== __Sécuriser la page Job__ == 
     496 
     497Même si vous connaissez l'URL d'un job qui a expiré, il ne doit plus être possible[[BR]] 
     498d'y accéder. Essayez l'URL d'un job expiré ( remplaçer l'`id` par l'`id`[[BR]] 
     499correspondant dans la base de donnée - `SELECT id, token FROM jobeet_job WHERE[[BR]] 
     500expires_at < NOW()` ) : 
     501{{{ 
     502    /frontend_dev.php/job/sensio-labs/paris-france/ID/web-developer-expired 
     503}}} 
     504Au lieu d'afficher le job, nous devons rediriger l'utilisateur vers une erreur 404.[[BR]] 
     505Mais comment faire alors que le job est recherché automatiquement par la route ?[[BR]][[BR]] 
     506{{{ 
     507    # apps/frontend/config/routing.yml 
     508    job_show_user: 
     509      url:     /job/:company_slug/:location_slug/:id/:position_slug 
     510      class:   sfDoctrineRoute 
     511      options: 
     512        model: JobeetJob 
     513        type:  object 
     514        method_for_query: retrieveActiveJob 
     515      param:   { module: job, action: show } 
     516      requirements: 
     517        id: \d+ 
     518        sf_method: [GET] 
     519}}} 
     520>>'''__NOTE__''' 
     521>La paramètre `~method_for_query~` ne fonctionne pas avant la version 1.2.2. 
     522 
     523La méthode `retrieveActiveJob` reçevra l'objet `Doctrine_Query` crée par la route : 
     524{{{ 
     525#!php 
     526    // lib/model/doctrine/JobeetJobTable.class.php 
     527    class JobeetJobTable extends Doctrine_Table 
     528    { 
     529      public function retrieveActiveJob(Doctrine_Query $q) 
     530      { 
     531        $q->andWhere('a.expires_at > ?', date('Y-m-d h:i:s', time())); 
     532 
     533        return $q->fetchOne(); 
     534      } 
     535 
     536      // ... 
     537    } 
     538}}} 
     539Maintenant, si vous essayez d'accéder à un job qui a expiré, vous serez redirigé[[BR]] 
     540vers une page d'erreur 404. 
     541 
     542[[Image(exception.png)]] 
     543 
     544== __Lien vers la page des Catégories__ == 
     545 
     546A présent, nous allons ajouter un lien vers la catégorie et créer la page de cette[[BR]] 
     547catégorie.[[BR]] 
     548 
     549Une minute. L'heure n'est pas encore écoulée et nous n'avons pas beaucoup travaillé.[[BR]] 
     550En fait, vous avez tout le temps nécessaire pour mettre en pratique tout ce que[[BR]] 
     551nous avons déjà appris et implémenter cette fonction par vous-même. Vous pourrez[[BR]] 
     552vérifier votre travail demain.[[BR]] 
     553 
     554== __A demain__ == 
     555 
     556Travaillez votre projet Jobeet. N'hésitez pas à abuser de la documentation en ligne[[BR]] 
     557[http://www.symfony-project.org/api/1_2/ API documentation] et de toutes les 
     558ressources [http://www.symfony-project.org/doc/1_2/ documentation] disponibles[[BR]] 
     559sur le site pour vous aider. On se retrouve demain pour découvrir l'implémentation[[BR]] 
     560des catégories.[[BR]][[BR]] 
     561 
     562Bonne chance !