Author: forresst
Date: 2010-01-11 16:57:07 +0100 (Mon, 11 Jan 2010)
New Revision: 26479

Added:
   doc/branches/1.4/jobeet/fr/07.markdown
Log:
[doc-fr][1.4] Add doc in french, jobeet/07 rev:en/24521


Added: doc/branches/1.4/jobeet/fr/07.markdown
===================================================================
--- doc/branches/1.4/jobeet/fr/07.markdown                              (rev 0)
+++ doc/branches/1.4/jobeet/fr/07.markdown      2010-01-11 15:57:07 UTC (rev 
26479)
@@ -0,0 +1,619 @@
+Jour 7 : Jouons avec la page Catégorie
+=====================================
+
+Hier, vous avez pu améliorer vos connaissances dans plusieurs domaines : 
requêtes
+avec ##ORM##, jeux de test, routage, débogage et la configuration 
personnalisée.
+Et nous avions fini la leçon en vous laissant un petit défi.
+
+J'espère que vous avez travaillé sur la page catégorie de Jobeet car le 
tutoriel
+d'aujourd'hui aura alors beaucoup plus de valeur pour vous.
+
+Prêt ? Voici une des implémentations possibles.
+
+La route de la catégorie
+------------------------
+
+Pour commencer, nous devons ajouter une route pour utiliser des URL propres.
+Ajoutez ce code au début du fichier de configuration :
+
+    [yml]
+    # apps/frontend/config/routing.yml
+    category:
+      url:      /category/:slug
+      class:    sfPropelRoute
+      param:    { module: category, action: show }
+      options:  { model: JobeetCategory, type: object }
+
+>**TIP**
+>A chaque fois que vous commencez à implémenter une nouvelle fonctionnalité, 
créer
+>une route pour les URL en premier est une bonne méthode de travail. Et c'est
+>obligatoire si jamais vous supprimez les règles de routage par défaut.
+
+Une route peut avoir comme paramètre n'importe quelle colonne de l'objet 
associé.
+Il est également possible d'utiliser n'importe quelle autre valeur si un 
accesseur
+est défini dans la classe de l'objet. Étant donné que le paramètre `slug` ne 
fait
+référence à aucune colonne dans la table `category`, nous devons ajouter un 
accessseur
+virtuel dans `JobeetCategory` pour faire fonctionner la route :
+
+    [php]
+<propel>
+    // lib/model/JobeetCategory.php
+</propel>
+<doctrine>
+    // lib/model/doctrine/JobeetCategory.class.php
+</doctrine>
+    public function getSlug()
+    {
+      return Jobeet::slugify($this->getName());
+    }
+
+Le lien de la catégorie
+-----------------------
+
+A présent, éditez le Template `indexSuccess.php` du module `job` et ajoutez le 
lien
+vers la page catégorie :
+
+    [php]
+    <!-- some HTML code -->
+
+            <h1>
+              <?php echo link_to($category, 'category', $category) ?>
+            </h1>
+
+    <!-- some HTML code -->
+
+          </table>
+
+          <?php if (($count = $category->countActiveJobs() -
+              ➥ sfConfig::get('app_max_jobs_on_homepage')) > 0): ?>
+            <div class="more_jobs">
+              and <?php echo link_to($count, 'category', $category) ?>
+              more...
+            </div>
+          <?php endif; ?>
+        </div>
+      <?php endforeach; ?>
+    </div>
+
+Nous ajoutons seulement les liens, s'il y a plus de 10 emplois à afficher pour 
la
+catégorie actuelle. Le lien contient le nombre d'emplois non affichés. Pour 
que ce
+Template fonctionne, nous devons ajouter la méthod `countActiveJobs()` pour
+`JobeetCategory` :
+
+<propel>
+    [php]
+    // lib/model/JobeetCategory.php
+    public function countActiveJobs()
+    {
+      $criteria = new Criteria();
+      $criteria->add(JobeetJobPeer::CATEGORY_ID, $this->getId());
+
+      return JobeetJobPeer::countActiveJobs($criteria);
+    }
+
+La méthode `countActiveJobs()` utilise la méthode `countActiveJobs()` qui 
n'existe
+pas encore dans le modèle `JobeetJobPeer`. Remplacer le contenu du fichier
+`JobeetJobPeer.php` par le code suivant :
+</propel>
+<doctrine>
+    [php]
+    // lib/model/doctrine/JobeetCategory.class.php
+    public function countActiveJobs()
+    {
+      $q = Doctrine_Query::create()
+        ->from('JobeetJob j')
+        ->where('j.category_id = ?', $this->getId());
+
+      return Doctrine::getTable('JobeetJob')->countActiveJobs($q);
+    }
+
+La méthode `countActiveJobs()` utilise la méthode `countActiveJobs()` qui 
n'existe
+pas encore dans le modèle `JobeetJobTable`. Remplacer le contenu du fichier
+`JobeetJobTable.php` par le code suivant :
+</doctrine>
+
+<propel>
+    [php]
+    // lib/model/JobeetJobPeer.php
+    class JobeetJobPeer extends BaseJobeetJobPeer
+    {
+      static public function getActiveJobs(Criteria $criteria = null)
+      {
+        return self::doSelect(self::addActiveJobsCriteria($criteria));
+      }
+
+      static public function countActiveJobs(Criteria $criteria = null)
+      {
+        return self::doCount(self::addActiveJobsCriteria($criteria));
+      }
+
+      static public function addActiveJobsCriteria(Criteria $criteria = null)
+      {
+        if (is_null($criteria))
+        {
+          $criteria = new Criteria();
+        }
+
+        $criteria->add(self::EXPIRES_AT, time(), Criteria::GREATER_THAN);
+        $criteria->addDescendingOrderByColumn(self::CREATED_AT);
+
+        return $criteria;
+      }
+
+      static public function doSelectActive(Criteria $criteria)
+      {
+        return self::doSelectOne(self::addActiveJobsCriteria($criteria));
+      }
+    }
+
+Comme vous pouvez le constater, nous avons refactorisé tout le code de 
`JobeetJobPeer` afin
+d'utiliser la nouvelle méthode `addActiveJobsCriteria()` qui est partagée. 
Notre code utilise
+maintenant la philosophie [DRY (Don't Repeat 
Yourself)](http://en.wikipedia.org/wiki/Don%27t_repeat_yourself).
+</propel>
+<doctrine>
+    [php]
+    // lib/model/doctrine/JobeetJobTable.class.php
+    class JobeetJobTable extends Doctrine_Table
+    {
+      public function retrieveActiveJob(Doctrine_Query $q)
+      {
+        return $this->addActiveJobsQuery($q)->fetchOne();
+      }
+
+      public function getActiveJobs(Doctrine_Query $q = null)
+      {
+        return $this->addActiveJobsQuery($q)->execute();
+      }
+
+      public function countActiveJobs(Doctrine_Query $q = null)
+      {
+        return $this->addActiveJobsQuery($q)->count();
+      }
+
+      public function addActiveJobsQuery(Doctrine_Query $q = null)
+      {
+        if (is_null($q))
+        {
+          $q = Doctrine_Query::create()
+            ->from('JobeetJob j');
+        }
+
+        $alias = $q->getRootAlias();
+
+        $q->andWhere($alias . '.expires_at > ?', date('Y-m-d H:i:s', time()))
+          ->addOrderBy($alias . '.created_at DESC');
+
+        return $q;
+      }
+    }
+
+Comme vous pouvez le constater, nous avons refactorisé tout le code de 
`JobeetJobTable` afin
+d'utiliser la nouvelle méthode `addActiveJobsQuery()` qui est partagée. Notre 
code utilise
+maintenant la philosophie [DRY (Don't Repeat 
Yourself)](http://en.wikipedia.org/wiki/Don%27t_repeat_yourself).
+</doctrine>
+
+>**TIP**
+>La première fois qu'une partie de code est réutilisée, le copier peut être 
suffisant.
+>Mais si vous en avez besoin pour d'autres utilisations, vous devez 
refactoriser toutes les
+>méthodes faisant appel à la fonction ou méthode partagée comme nous venons de 
le faire.
+
+<propel>
+Au lieu d'utiliser `doSelect()` puis de compter le nombre de résultats dans la 
méthode
+`countActiveJobs()`, nous avons utilisé la méthode `doCount()` qui est 
beaucoup plus rapide.
+</propel>
+<doctrine>
+Au lieu d'utiliser `execute()` puis de compter le nombre de résultats dans la 
méthode
+`countActiveJobs()`, nous avons utilisé la méthode `count()` qui est beaucoup 
plus rapide.
+</doctrine>
+
+Nous venons de modifier beaucoup de fichiers pour cette simple fonctionnalité. 
Mais pour
+chaque ajout de code, nous avons essayé de le mettre dans la bonne couche de 
l'application
+et nous avons également essayé de rendre le code réutilisable. Dans la foulée, 
nous
+avons refactorisé du code existant. C'est une méthode de travail typique quand 
vous
+travaillez sur un projet symfony. Dans la capture d'écran suivante, nous 
montrons que 5 emplois
+pour diminuer l'affichage, vous devriez en voir 10 (le paramètre 
`max_jobs_on_homepage`):
+
+![Page 
d'accueil](http://www.symfony-project.org/images/jobeet/1_4/07/homepage.png)
+
+Création du module de la catégorie d'un emploi
+----------------------------------------------
+
+Il est temps de créer le ~module|Module~ `category` :
+
+    $ php symfony generate:module frontend category
+
+Si vous avez créé le module, vous avez sûrement utilisé
+`propel:generate-module`. C'est très bien, mais comme nous n'aurons pas
+besoin de 90% du code généré, j'ai utilisé le `generate:module` qui crée un
+module vide.
+
+>**TIP**
+>Pourquoi ne pas ajouter une action `category` pour le module `job` ? Nous 
pourrions, mais
+>comme le sujet principal de la page catégorie est une catégorie, il parait 
plus naturel
+>de créer un module de `category` dédiée.
+
+Lorsque vous accédez à la page catégorie, la route `category` devra trouver la 
catégorie
+associée avec la variable `slug`. Mais étant donné que ~slug|Slug~ n'est pas 
stocké dans
+la base de donnée et parce qu'il est impossible d'en déduire la catégorie, nous
+n'avons aucun moyen de trouver la catégorie pour le moment.
+
+Mise à jour de la base de donnée
+--------------------------------
+
+Nous devons ajouter la colonne `slug` à la table `category` :
+
+<propel>
+    [yml]
+    # config/schema.yml
+    propel:
+      jobeet_category:
+        id:           ~
+        name:         { type: varchar(255), required: true }
+        slug:         { type: varchar(255), required: true, index: unique }
+</propel>
+<doctrine>
+Cette colonne `slug` peut être prise en charge par un comportement de Doctrine 
nommé `Sluggable`.
+Nous avons simplement besoin d'activer le comportement de notre modèle 
`JobeetCategory` et il
+prendra soin de tout pour vous.
+
+    [yml]
+    # config/doctrine/schema.yml
+    JobeetCategory:
+      actAs:
+        Timestampable: ~
+        Sluggable:
+          fields: [name]
+      columns:
+        name:
+          type: string(255)
+          notnull:  true
+
+</doctrine>
+
+A présent, `slug` est une colonne réelle et vous pouvez donc supprimer la 
méthode
+`getSlug()` du modèle `JobeetCategory`.
+
+<propel>
+A chaque modification du nom de la catégorie, la valeur de `slug` doit être 
mise
+à jour. Ajoutons la méthode `setName()` :
+
+    [php]
+    // lib/model/JobeetCategory.php
+    public function setName($name)
+    {
+      parent::setName($name);
+
+      $this->setSlug(Jobeet::slugify($name));
+    }
+</propel>
+<doctrine>
+>**NOTE**
+>Le paramètre de la colonne slug est automatiquement pris en compte quand vous
+>sauvegardez un enregistrement. La valeur de slug est construite à partir du 
champ
+>`name` et est passée en objet.
+</doctrine>
+
+Utilisez la tâche `propel:build --all --and-load` pour mettre à jour les 
tables de la
+base de donnée et recharger les données avec vos jeux de test :
+
+    $ php symfony propel:build --all --and-load --no-confirmation
+
+Nous pouvons maintenant créer la méthode `executeShow()`. Remplacez le contenu
+du fichier des actions de `category` avec le code suivant :
+
+    [php]
+    // apps/frontend/modules/category/actions/actions.class.php
+    class categoryActions extends sfActions
+    {
+      public function executeShow(sfWebRequest $request)
+      {
+        $this->category = $this->getRoute()->getObject();
+      }
+    }
+
+>**NOTE**
+>Puisque nous venons de supprimer la méthode `executeIndex()`, vous pouvez
+>aussi supprimer le Template `indexSuccess.php` qui a été généré 
automatiquement
+>(`apps/frontend/modules/category/templates/indexSuccess.php`).
+
+Pour la dernière étape, il reste à créer le template `showSuccess.php` :
+
+    [php]
+    // apps/frontend/modules/category/templates/showSuccess.php
+    <?php use_stylesheet('jobs.css') ?>
+
+    <?php slot('title', sprintf('Jobs in the %s category', 
$category->getName())) ?>
+
+    <div class="category">
+      <div class="feed">
+        <a href="">Feed</a>
+      </div>
+      <h1><?php echo $category ?></h1>
+    </div>
+
+    <table class="jobs">
+      <?php foreach ($category->getActiveJobs() as $i => $job): ?>
+        <tr class="<?php echo fmod($i, 2) ? 'even' : 'odd' ?>">
+          <td class="location">
+            <?php echo $job->getLocation() ?>
+          </td>
+          <td class="position">
+            <?php echo link_to($job->getPosition(), 'job_show_user', $job) ?>
+          </td>
+          <td class="company">
+            <?php echo $job->getCompany() ?>
+          </td>
+        </tr>
+      <?php endforeach; ?>
+    </table>
+
+~Partials|Partial Templates~
+----------------------------
+
+Notez que nous avons copié et collé la balise `<table>` afin de créer une liste
+d'emploi depuis le Template job `indexSuccess.php`. C'est mauvais. Il est temps
+d'apprendre un nouveau tour. Lorsque vous avez besoin de réutiliser une partie 
d'un
+Template, vous devez créer un **~partial|Partial Templates~**. Un partial est 
un extrait du
+code du ~Template|Templates~ qui peut être partagé entre plusieurs Templates. 
Un
+partial est juste un autre Template qui commence par un caractère de 
soulignement (`_`).
+
+Créez le fichier `_list.php` :
+
+    [php]
+    // apps/frontend/modules/job/templates/_list.php
+    <table class="jobs">
+      <?php foreach ($jobs as $i => $job): ?>
+        <tr class="<?php echo fmod($i, 2) ? 'even' : 'odd' ?>">
+          <td class="location">
+            <?php echo $job->getLocation() ?>
+          </td>
+          <td class="position">
+            <?php echo link_to($job->getPosition(), 'job_show_user', $job) ?>
+          </td>
+          <td class="company">
+            <?php echo $job->getCompany() ?>
+          </td>
+        </tr>
+      <?php endforeach; ?>
+    </table>
+
+Vous pouvez inclure un partial en utilisant le helper `~include_partial()~` :
+
+    [php]
+    <?php include_partial('job/list', array('jobs' => $jobs)) ?>
+
+Le premier argument du helper `include_partial()` est le nom du partial (nom du
+module, un slash `/`, et le nom du partial sans le caractère de soulignement 
(`_`). Le
+second argument est un tableau contenant les variables à passer dans le 
partial.
+
+>**NOTE**
+>Pourquoi ne pas utiliser la méthode `include()` intégrée à PHP plutôt que le
+>helper `include_partial()` ? La principale différence est le support du cache
+>intégré au helper `include_partial()`.
+
+Remplaçez le bloc de code HTML `<table>` dans les deux templates par un appel
+`include_partial()` :
+
+    [php]
+    // in apps/frontend/modules/job/templates/indexSuccess.php
+    <?php include_partial('job/list', array('jobs' => 
$category->getActiveJobs(sfConfig::get('app_max_jobs_on_homepage')))) ?>
+
+    // in apps/frontend/modules/category/templates/showSuccess.php
+    <?php include_partial('job/list', array('jobs' => 
$category->getActiveJobs())) ?>
+
+~Pagination~ de la liste
+------------------------
+
+Conditions du Jour 2 :
+
+  "La liste est paginée avec 20 emplois par page."
+
+Pour paginer une liste d'objets ##ORM##, symfony fournit une classe dédiée :
+[`sfPropelPager`](http://www.symfony-project.org/api/1_4/sfPropelPager). Dans
+l'action `category`, au lieu de passer les objets job au template 
`showSuccess`,
+nous allons passer un paginateur :
+
+    [php]
+    // apps/frontend/modules/category/actions/actions.class.php
+    public function executeShow(sfWebRequest $request)
+    {
+      $this->category = $this->getRoute()->getObject();
+
+      $this->pager = new sfPropelPager(
+        'JobeetJob',
+        sfConfig::get('app_max_jobs_on_category')
+      );
+<propel>
+      $this->pager->setCriteria($this->category->getActiveJobsCriteria());
+</propel>
+<doctrine>
+      $this->pager->setQuery($this->category->getActiveJobsQuery());
+</doctrine>
+      $this->pager->setPage($request->getParameter('page', 1));
+      $this->pager->init();
+    }
+
+>**TIP**
+>La méthode `sfRequest::getParameter()` prend une valeur par défaut pour le
+>second argument. Dans l'action ci-dessus, si le paramètre requis `page` 
n'existe
+>pas, alors `getParameter()` retournera `1`.
+
+Le constructeur `sfPropelPager` utilise la classe du modèle et le nombre 
maximum
+d'items à afficher par page. Ajouter la dernière ligne au fichier de 
configuration :
+
+    [yml]
+    # apps/frontend/config/app.yml
+    all:
+      active_days:          30
+      max_jobs_on_homepage: 10
+      max_jobs_on_category: 20
+
+<propel>
+La méthode `sfPropelPager::setCriteria()` prend un objet `Criteria` à utiliser
+lors de la sélection des items de la base de données.
+</propel>
+<doctrine>
+La méthode `sfDoctrinePager::setQuery()` prend un objet `Doctrine_Query` à 
utiliser
+lors de la sélection des items de la base de données.
+</doctrine>
+
+<propel>
+Ajoutez la méthode `getActiveJobsCriteria()` :
+</propel>
+<doctrine>
+Ajoutez la méthode `getActiveJobsQuery()` :
+</doctrine>
+
+<propel>
+    [php]
+    // lib/model/JobeetCategory.php
+    public function getActiveJobsCriteria()
+    {
+      $criteria = new Criteria();
+      $criteria->add(JobeetJobPeer::CATEGORY_ID, $this->getId());
+
+      return JobeetJobPeer::addActiveJobsCriteria($criteria);
+    }
+</propel>
+<doctrine>
+    [php]
+    // lib/model/doctrine/JobeetCategory.class.php
+    public function getActiveJobsQuery()
+    {
+      $q = Doctrine_Query::create()
+        ->from('JobeetJob j')
+        ->where('j.category_id = ?', $this->getId());
+
+      return Doctrine::getTable('JobeetJob')->addActiveJobsQuery($q);
+    }
+</doctrine>
+
+<propel>
+Maintenant que nous avons défini la méthode `getActiveJobsCriteria()`, nous
+pouvons refactoriser les autres méthodes du modèle `JobeetCategory` qui 
l'utilise :
+</propel>
+<doctrine>
+Maintenant que nous avons défini la méthode `getActiveJobsQuery()`, nous
+pouvons refactoriser les autres méthodes du modèle `JobeetCategory` qui 
l'utilise :</doctrine>
+</doctrine>
+
+<propel>
+    [php]
+    // lib/model/JobeetCategory.php
+    public function getActiveJobs($max = 10)
+    {
+      $criteria = $this->getActiveJobsCriteria();
+      $criteria->setLimit($max);
+
+      return JobeetJobPeer::doSelect($criteria);
+    }
+
+    public function countActiveJobs()
+    {
+      $criteria = $this->getActiveJobsCriteria();
+
+      return JobeetJobPeer::doCount($criteria);
+    }
+</propel>
+<doctrine>
+    [php]
+    // lib/model/doctrine/JobeetCategory.class.php
+    public function getActiveJobs($max = 10)
+    {
+      $q = $this->getActiveJobsQuery()
+        ->limit($max);
+
+      return $q->execute();
+    }
+
+    public function countActiveJobs()
+    {
+      return $this->getActiveJobsQuery()->count();
+    }
+</doctrine>
+
+Et pour finir, mettons le Template à jour :
+
+    [php]
+    <!-- apps/frontend/modules/category/templates/showSuccess.php -->
+    <?php use_stylesheet('jobs.css') ?>
+
+    <?php slot('title', sprintf('Jobs in the %s category', 
$category->getName())) ?>
+
+    <div class="category">
+      <div class="feed">
+        <a href="">Feed</a>
+      </div>
+      <h1><?php echo $category ?></h1>
+    </div>
+
+    <?php include_partial('job/list', array('jobs' => $pager->getResults())) ?>
+
+    <?php if ($pager->haveToPaginate()): ?>
+      <div class="pagination">
+        <a href="<?php echo url_for('category', $category) ?>?page=1">
+          <img src="/images/first.png" alt="First page" />
+        </a>
+
+        <a href="<?php echo url_for('category', $category) ?>?page=<?php echo 
$pager->getPreviousPage() ?>">
+          <img src="/images/previous.png" alt="Previous page" title="Previous 
page" />
+        </a>
+
+        <?php foreach ($pager->getLinks() as $page): ?>
+          <?php if ($page == $pager->getPage()): ?>
+            <?php echo $page ?>
+          <?php else: ?>
+            <a href="<?php echo url_for('category', $category) ?>?page=<?php 
echo $page ?>"><?php echo $page ?></a>
+          <?php endif; ?>
+        <?php endforeach; ?>
+
+        <a href="<?php echo url_for('category', $category) ?>?page=<?php echo 
$pager->getNextPage() ?>">
+          <img src="/images/next.png" alt="Next page" title="Next page" />
+        </a>
+
+        <a href="<?php echo url_for('category', $category) ?>?page=<?php echo 
$pager->getLastPage() ?>">
+          <img src="/images/last.png" alt="Last page" title="Last page" />
+        </a>
+      </div>
+    <?php endif; ?>
+
+    <div class="pagination_desc">
+      <strong><?php echo count($pager) ?></strong> jobs in this category
+
+      <?php if ($pager->haveToPaginate()): ?>
+        - page <strong><?php echo $pager->getPage() ?>/<?php echo 
$pager->getLastPage() ?></strong>
+      <?php endif; ?>
+    </div>
+
+L'essentiel de ce code traite des liens vers d'autres pages. Voici la liste
+des méthodes `sfPropelPager` utilisées dans ce Template :
+
+  * `getResults()`: Retourne un tableau d'objets ##ORM## de la page actuelle
+  * `getNbResults()`: Retourne le nombre total de résultats
+  * `haveToPaginate()`: Retourne `true` s'il y a plus d'une page
+  * `getLinks()`: Retourne une liste de liens vers des pages à afficher
+  * `getPage()`: Retourne le numéro de la page actuelle
+  * `getPreviousPage()`: Retourne le numéro de la page précédente
+  * `getNextPage()`: Retourne le numéro de la page suivante
+  * `getLastPage()`: Retourne le numéro de la dernière page
+
+Comme `sfPropelPager` implémente également les interfaces `Iterator` et 
`Countable`,
+vous pouvez utiliser la fonction `count()` pour obtenir le nombre de résultats 
au lieu
+de la méthode `getNbResults()`.
+
+![Pagination](http://www.symfony-project.org/images/jobeet/1_4/07/pagination.png)
+
+À demain
+--------
+
+Si vous avez travaillé sur votre propre implémentation d'hier et le sentiment 
que
+vous n'avez pas appris beaucoup aujourd'hui, cela signifie que vous êtes 
habituer à
+la philosophie de symfony. Le processus pour ajouter une nouvelle 
fonctionnalité à un site
+web symfony est toujours le même : réfléchir sur les URL, créer certaines 
actions, mettre à jour
+le modèle et écrire quelques Templates. Et, si vous pouvez appliquer certaines 
bonnes pratiques de
+développement en plus, vous deviendrez un maître symfony très rapidement.
+
+Demain sera le début d'une nouvelle semaine pour Jobeet. Pour fêter ça, nous 
allons
+parler d'un tout nouveau sujet : les tests.
+
+__ORM__

-- 
You received this message because you are subscribed to the Google Groups 
"symfony SVN" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/symfony-svn?hl=en.


Reply via email to