Development

Documentation/fr_FR/jobeet/7

You must first sign up to be able to contribute.

Version 1 (modified by sebastien.b, 9 years ago)
--

Cette 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 version en anglais.

  • Traduction : Sébastien B
  • Date de traduction : 14 Mars
  • Date de dernière modification :

Jour 7: Ajout de la page catégorie

Hier, vous avez pu améliorer vos connaissances dans plusieurs domaines : requêtes avec Propel, fixtures, routage, débuggage et la configuration personnalisée. Et nous avions fini la leçon en vous laissant un petit défi.

Si vous avez travaillé sur la mise en place de la page catégorie, ce tutorial vous permettra de mieux compremdre.

Prêt ? Voici une des implémentations possibles.

La route des catégories

Pour commençer, nous devons ajouter une route pour utiliser des URL propres. Ajoutez ce code au début du fichier de configuration :

    # 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 relationnel. Il est également possible d'utiliser n'importe quelle autre valeur si un accesseur est définit dans la classe de l'objet. Etant 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 :

    // lib/model/JobeetCategory.php
    public function getSlug()
    {
      return Jobeet::slugify($this->getName());
    }

Lien vers la catégorie

A présent, éditez le template indexSuccess.php du module job et ajoutez le lien vers la page de la catégorie :

    <!-- 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>

Le lien est affiché seulement si la catégorie possède plus de 10 jobs. Pour faire fonctionner cette contrainte, nous allons ajouter la méthode countActiveJobs() au modèle JobeetCategory :

    // 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. Remplaçer le contenu du fichier JobeetJobPeer.php par le code suivant :

    // 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).

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.

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.

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.

Job Category Module Creation

Il est temps de créer le module category :

    $ php symfony generate:module frontend category

Si vous avez créer le module avant de lire ce tutoriel, vous avez sûrement utilisé la commande propel:generate-module. C'est correct mais 90% du code généré nous est inutile. La commande generate:module nous permet de créer un module vierge.

TIP

Pourquoi ne pas simplement ajouter une action category au module job ? Nous pourrions le faire mais le sujet principal de la page catégorie est une catégorie. Il paraît donc normal de créer une module category à part entière.

A l'accès de la page catégorie, la route category devra trouver la catégorie associée avec la variable slug. Mais étant donné que slug n'est pas stocké dans la base de donnée et parcequ'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 :`

    # config/schema.yml
    propel:
      jobeet_category:
        id:           ~
        name:         { type: varchar(255), required: true }
        slug:         { type: varchar(255), required: true, index: unique }

A présent, slug est une colonne réelle et vous pouvez donc supprimer la méthode getSlug() du modèle JobeetCategory.

A chaque modification du nom de la catégorie, la valeur de slug doit être mise à jour. Ajoutons la méthode setName() :

    // lib/model/JobeetCategory.php
    public function setName($name)
    {
      parent::setName($name);

      $this->setSlug(Jobeet::slugify($name));
    }

Utilisez la commande propel:build-all-load pour mettre à jour les tables de la base de donnée et recharger les données grâce aux fixtures :

    $ php symfony propel:build-all-load --no-confirmation

Nous pouvons maintenant créer la méthode executeShow(). Remplaçez le code du fichier des actions du module catégorie par :

    // 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 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 rest à créer le template showSuccess.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

Notez que nous avons fait un copié/collé du bloc <table> du template indexSuccess.php du module job qui crée la liste des jobs. C'est une mauvaise façon de faire. Il est temps d'apprendre une nouvelle astuce. Qaund vous avez besoin de réutiliser une portion de code d'une template,vous pouvez créer un **partial**. Un partial est une partie de code d'un template qui peut être partagé avec d'autres templates. Un partial, est simplement un autre template dont le nom commença par un underscore (_).

Créez le fichier _list.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 include_partial('job/list', array('jobs' => $jobs)) ?>

Le premier argument du helper include_partial() est le nom du partial ( nom du module source , un slash /, et le nom du partial sans le underscore _. 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() :

    // 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

Conditions du Jour 2 :

"La page doit afficher une liste de 20 jobs avec une pagination."

Pour paginer une liste d'objets Propel, symfony fournit une class dédiée : `sfPropelPager`. Dans le fichier d'actions du module category, au lieu de passer les objets job au template showSuccess, nous allons passer un paginateur :

    // 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')
      );
      $this->pager->setCriteria($this->category->getActiveJobsCriteria());
      $this->pager->setPage($request->getParameter('page', 1));
      $this->pager->init();
    }

TIP

La méthode sfRequest::getParameter() utilise 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 :

    # apps/frontend/config/app.yml
    all:
      active_days:          30
      max_jobs_on_homepage: 10
      max_jobs_on_category: 20

La méthode sfPropelPager::setCriteria() prend un objet Criteria à utiliser pour récupérer les items de la base de données.

Et la méthode getActiveJobsCriteria() :

    // lib/model/JobeetCategory.php
    public function getActiveJobsCriteria()
    {
      $criteria = new Criteria();
      $criteria->add(JobeetJobPeer::CATEGORY_ID, $this->getId());

      return JobeetJobPeer::addActiveJobsCriteria($criteria);
    }

Maintenant que nous avons définit la méthode getActiveJobsCriteria(), nous pouvons refactoriser les autres méthodes du modèle JobeetCategory qui l'utilise :

    // 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);
    }

Et pour finir, mettons le template à jour :

    <!-- 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 $pager->getNbResults() ?></strong> jobs in this category

      <?php if ($pager->haveToPaginate()): ?>
        - page <strong><?php echo $pager->getPage() ?>/<?php echo $pager->getLastPage() ?></strong>
      <?php endif; ?>
    </div>

La majeure partie de ce code utilise des liens vers les différentes pages. Voici la liste des méthodes de sfPropelPager employées dans ce template :

  * `getResults()`: Returns an array of Propel objects for the current page
  * `getNbResults()`: Returns the total number of results
  * `haveToPaginate()`: Returns `true` if there is more than one page
  * `getLinks()`: Returns a list of page links to display
  * `getPage()`: Returns the current page number
  * `getPreviousPage()`: Returns the previous page number
  * `getNextPage()`: Returns the next page number
  * `getLastPage()`: Returns the last page number

A demain

Si vous avez travaillé sur votre propre inétgration des catégories hier et que vous avez le sentiment de ne rien avoir appris aujourd'hui, alors vous commençez à comprendre la philosophie de symfony. Le processus pour ajouter une nouvelle fonctionnalité à un site symfony est toujours le même : - penser les URLs - créer actions nécessaires - mettre à jour le modèle - et écrire quelques templates. Si vous appliquez ces bonnes méthodes de développement, vous deviendrez très vite un expert de symfony.

Demain nous entamerons une nouvelle semaine de travail sur Jobeet. Pour fêter ça, nous aborderons une nouveauté : les tests.

Attachments