Development

Documentation/fr_FR/jobeet/5

You must first sign up to be able to contribute.

Version 18 (modified by SomeOne, 8 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 : 25 Février 2009
  • Date de dernière modification : 14 Mars 2009 ( mise à jour par rapport à la version anglaise )

Jour 5: Le Routage

Après avoir fait le 4ème jour, vous connaissez maintenant le modèle MVC qui doit vous apparaître comme une manière de coder de plus en plus naturel. Prenez le temps de le comprendre et vous ne le regretterez pas. Hier, nous avons modifié le graphisme de Jobeet mais également passé en revue plusieurs concepts de symfony, comme le layout, les helpers et les slots.

Aujourd'hui nous explorerons le monde merveilleux du routage avec symfony.

Les ~URL~s

Pour l'instant, les liens des jobs présents sur la page d'accueil de Jobeet ressemblent à ça : /job/show/id/1. Si vous avez déjà développé des sites en PHP, vous êtes sûrement plus habitué à utiliser des URLs comme /job.php?id=1. Comment fait symfony pour que ça fonctionne ? Comment symfony détermine quelle action appeler en fonction de cette URL ? Pourquoi on récupère l' id du job avec $request->getParameter('id')? Aujourd'hui, nous allons répondre à toutes ces questions.

Tout d'abord, voyons ce que sont exactement les URLs. Sur le web, une URL est l'identifiant unique d'un contenu web. Quand vous allez à une URL, vous demandez au navigateur de chercher un contenu identifié par cette URL. Donc, si cette URL est l'interface entre le site web et l'utilisateur, elle doit contenir des informations significatives sur le contenu référencé. De plus, les URLs "traditionnelles" qui ne décrivent pas le contenu, exposent la structure interne de l'application. L'utilisateur n'a pas à savoir si votre site est développé en PHP ou si les jobs ont un identifiant dans la base de données. Exposer le fonctionnement interne de votre application est aussi très mauvais en terme de ~sécurité~: Que se passera-t-il si l'utilisateur essaye des URLs pour afficher du contenu auquel il ne doit pas avoir accès ? Evidemment, le développeur doit sécuriser ces accès mais la meilleure solution est de cacher les informations sensibles.

Les URLs sont si importantes, que symfony possède un framework dédié à leur gestion : le **~routage~**. Le routage gère aussi bien les URL internes que les URL externes. Après une requête, le routage analyse l'URL et la convertit en URI interne.

Vous avez déjà vu une URI interne sur la page des jobs qui utilise le template showSuccess.php :

    'job/show?id='.$job->getId()

Le helper ~url_for()~ convertit l'URI interne en URL propre :

    /job/show/id/1

L'URI interne se compose de plusieurs parties : job qui est le module, show qui est l'action et les paramètres servants à la requête passée dans l'action. Le modèle générique pour une URI interne est :

    MODULE/ACTION?key=value&key_1=value_1&...

Car le routage de symfony est un processus bi-directionnel. Vous pouvez changer les URL sans changer l'exécution technique. C'est l'un des avantages principaux du front-controller design pattern.

Configuration du Routage

Le lien entre les URIs internes et les URLs externes se paramètre dans le fichier de configuration ~routing.yml~ :

    # apps/frontend/config/routing.yml
    homepage:
      url:   /
      param: { module: default, action: index }

    default_index:
      url:   /:module
      param: { action: index }

    default:
      url:   /:module/:action/*

Le fichier routing.yml décrit les routes. Une route possède un nom (homepage), un modèle (/:module/:action/*), et d'autres paramètres que nous verrons plus tard (après la clé param).

A l'appel d'une requête, le routage essaye de faire correspondre un modèle avec l'URL donnée. La première route trouvée est utilisée, c'est pourquoi l'ordre dans routing.yml est important. Voyons quelques exemples pour mieux comprendre comment le routage fonctionne.

A l'appel de la page d'accueil de Jobeet, avec l'URL /job, la première route qui correspond est default_index. Dans le modèle, un mot qui est précédé de deux points (:) est une variable. Donc le modèle /:module signifie : trouver un / suivi par quelquechose. Dans notre exemple, la variable module aura la valeur job. Cette valeur peut alors être recherchée avec $request->getParameter('module') dans l'action. Cette route définit aussi une valeur par défaut pour la variable action. Donc toutes les URLs correspondantes à cette route possèderont par défaut le paramètre action avec la valeur index.

Si vous allez sur la page /job/show/id/1, symfony fera correspondre le dernier modèle : /:module/:action/*. Dans un modèle, une étoile (*) correspond à une collection de paires variable/valeur séparées par des slashes (/):

Request parameter Value
----------------- -----
module job
action show
id 1

NOTE

Les variables ~module~ et ~action~ sont réservées et sont utilisées par symfony pour déterminer l'action à exécuter.

L'URL /job/show/id/1 peut être créee dans un template en utilisant le helper url_for() :

    url_for('job/show?id='.$job->getId())

Vous pouvez aussi utiliser le nom de la route en le faisant précéder par @ :

    url_for('@default?module=job&action=show&id='.$job->getId())

Les deux façons d'appel sont équivalentes mais la dernière est beaucoup plus rapide car le routage n'a pas besoin d'analyser toutes les routes pour trouver la meilleure correspondance, et elle est moins attachée à l'exécution (les noms de module et d'action ne sont pas présents dans l'URI interne).

Personnaliser les Routes

Pour l'instant, l'appel de l'URL /, affiche la page par défaut de symfony. C'est parce que l'URL correspond à la ~route~ homepage. Mais il serait plus logique d'afficher la page d'accueil de Jobeet. Pour cela, il suffit de modifier la valeur de la variable module par job pour la route homepage :

    # apps/frontend/config/routing.yml
    homepage:
      url:   /
      param: { module: job, action: index }

On peut maintenant modifier le lien du logo Jobeet dans le layout pour utiliser la route homepage :

    <!-- apps/frontend/templates/layout.php -->
    <h1>
      <a href="<?php echo url_for('@homepage') ?>">
        <img src="/images/jobeet.gif" alt="Jobeet Job Board" />
      </a>
    </h1>

C'était facile ! Passons à quelque chose d'un peu plus évolué et modifions l'URL de la page job pour avoir quelque chose de plus significatif :

    /job/sensio-labs/paris-france/1/web-developer

Sans connaître Jobeet et sans visiter la page, vous pouvez comprendre grâce à cette URL que Sensio Labs recherche un dévelopeur Web pour travailler en France à Paris.

NOTE

Les URLs claires sont importantes car elles donnent des informations à l'utilisateur. C'est aussi très utile lorsqu'on copie l'URL dans un email ou pour optimiser le référencement de votre site sur les moteurs de recherche.

Le modèle suivant correspond à une telle URL :

    /job/:company/:location/:id/:position

Ajouter la route job_show_user au début du fichier routing.yml :

    job_show_user:
      url:   /job/:company/:location/:id/:position
      param: { module: job, action: show }

Si vous actualisez la page d'accueil de Jobeet, les liens des jobs n'ont pas changé. C'est parce que pour générer une route, vous devez indiquer toutes les variables requises. Il faut donc modifier le helper url_for() dans indexSuccess.php par :

    url_for('job/show?id='.$job->getId().'&company='.$job->getCompany().
      '&location='.$job->getLocation().'&position='.$job->getPosition())

Une ~URI interne~ peut aussi être définie dans un tableau array :

    url_for(array(
      'module'   => 'job',
      'action'   => 'show',
      'id'       => $job->getId(),
      'company'  => $job->getCompany(),
      'location' => $job->getLocation(),
      'position' => $job->getPosition(),
    ))

Requirements (Conditions)

Dans le tutorial du premier jour, nous avons parlé de la validation et de la gestion d'erreur qui sont indispensables. Le routage possède son propre système de ~validation~. Chaque modèle de variable peut être validé par une expression régulière en utilisant l'entrée ~requirements~ avec une définition de la ~route~ :

    job_show_user:
      url:   /job/:company/:location/:id/:position
      param: { module: job, action: show }
      requirements:
        id: \d+

L'entrée requirements précédente requiet que id soit une valeure numérique. Si ce n'est pas le cas, la route ne fonctionnera pas.

Route Class

Chaque route définie dans ~routing.yml~ est convertie en interne en objet de la classe `sfRoute`. Cette classe peut être modifiée en définissant une entrée class dans la définition de la route. Si le protocole ~HTTP~ vous est familier, vous savez qu'il définit plusieures "méthodes", comme ~GET~, ~POST~, ~HEAD~, ~DELETE~, et ~PUT~. Les trois premières sont supportées par tous les navigateurs, mais pas les deux autres.

Pour restreindre la route à une méthode déterminée , vous pouvez changer la classe de la route par `sfRequestRoute` et ajouter une condition pour la variable virtuelle sf_method :

    job_show_user:
      url:   /job/:company/:location/:id/:position
      class: sfRequestRoute
      param: { module: job, action: show }
      requirements:
        id: \d+
        sf_method: [get]

NOTE

Exiger qu'une route corresponde uniquement à une des ~méthodes HTTP~ n'est pas totalement équivalent à l'utilisation de sfWebRequest::isMethod() utilisé dans les actions. En effet le routage continuera de chercher une correspondance si la méthode ne correspond pas à celle définie.

Object Route Class

La nouvelle URI interne est un peu longue et pénible à écrire (url_for('job/show?id='.$job->getId().'&company='.$job->getCompany().'&location='.$job->getLocation().'&position='.$job->getPosition())). Mais comme nous venons de le voir dans la section précédente, la classe de la route peut être modifiée. Pour la route job_show_user, il est préférable d'utiliser la classe `~sfPropelRoute~` optimisée pour les routes représentées par des objets Propel ou des collections d'objets Propel :

    job_show_user:
      url:     /job/:company/:location/:id/:position
      class:   sfPropelRoute
      options: { model: JobeetJob, type: object }
      param:   { module: job, action: show }
      requirements:
        id: \d+
        sf_method: [get]

L'entrée options personnalise le comportement de l'itinéraire. Ici, l'option model indique que la classe de modèle Propel (JobeetJob) est liée à la route et l'option type indique que cette route est attachée à un objet (vous pouvez aussi utiliser l'option list si une route représente une collection d'objets).

La route job_show_user sait maintenant qu'elle est liée avec JobeetJob et nous pouvons simplifier l'appel de ~url_for()~ :

    url_for(array('sf_route' => 'job_show_user', 'sf_subject' => $job))

ou simplement :

    url_for('job_show_user', $job)

NOTE

Le premier exemple est utile quand vous devez passer plus d'arguments plutôt que l'objet seul.

Ca fonctionne car toutes les variables dans la route ont un accédant correspondant dans la classe JobeetJob (par exemple, la variable company de la route est remplaçée par la valeur de getCompany()).

A ce stade, les URLs générées ne sont pas encore comme nous le voudrions :

    http://jobeet.localhost/frontend_dev.php/job/Sensio+Labs/Paris%2C+France/1/Web+Developer

Nous devons utiliser la valeur "~slug~ify" sur la colonne pour remplaçer tous les charactères non ASCII par un -. Ouvrir le fichier JobeetJob et ajouter les fonctions suivantes à la classe :

    // lib/model/JobeetJob.php

    public function getCompanySlug()
    {
      return Jobeet::slugify($this->getCompany());
    }

    public function getPositionSlug()
    {
      return Jobeet::slugify($this->getPosition());
    }

    public function getLocationSlug()
    {
      return Jobeet::slugify($this->getLocation());
    }

Ensuite, créer le fichier lib/Jobeet.class.php et y ajouter la fonction slugify :

    // lib/Jobeet.class.php
    class Jobeet
    {
      static public function slugify($text)
      {
        // replace all non letters or digits by -
        $text = preg_replace('/\W+/', '-', $text);

        // trim and lowercase
        $text = strtolower(trim($text, '-'));

        return $text;
      }
    }

NOTE

Dans ce tutoriel, nous n'écrivons jamais la balise d'ouverture <?php dans les exemples de code qui contiennent uniquement du code PHP afin d'optimiser l'espace et réduire la consommation de papier. Evidemment, vous ne devez pas oublier de l'ajouter quand vous créez un nouveau fichier PHP.

Nous venons de définir trois nouveaux accédants "virtuels" : getCompanySlug(), getPositionSlug(), et getLocationSlug(). Ils retournent la valeur correspondante à leure colonne après avoir appliqué la fonction slugify(). Maintenant, vous pouvez remplaçer le nom réel des colonnes par leur équivalent virtuel dans la route job_show_user :

    job_show_user:
      url:     /job/:company_slug/:location_slug/:id/:position_slug
      class:   sfPropelRoute
      options: { model: JobeetJob, type: object }
      param:   { module: job, action: show }
      requirements:
        id: \d+
        sf_method: [get]

Avant d'actualier la page d'acceuil de Jobeet, vous devez vider le cache car nous avons ajouté une nouvelle classe (Jobeet) :

    $ php symfony cc

Les nouvelles URLs sont maintenant fonctionnelles :

    http://jobeet.localhost/frontend_dev.php/job/sensio-labs/paris-france/1/web-developer

Mais nous ne sommes qu'au milieu de l'histoire. La route est capable de générer une URL basée sur un objet, mais elle peut aussi trouver l'objet lié à une URL donnée. L'objet peut être récupéré avec la méthode getObject(). A l'analyse d'une requête entrante, le routage stocke la route correspondante en un objet qui peut être utilisé dans les actions. Donc, modifions la méthode executeShow() pour utiliser l'objet de la route et retrouver l'objet Jobeet :

    class jobActions extends sfActions
    {
      public function executeShow(sfWebRequest $request)
      {
        $this->job = $this->getRoute()->getObject();

        $this->forward404Unless($this->job);
      }

      // ...
    }

Si vous essayez d'afficher un job avec un id inconnu, vous verrez la page d'erreur 404 mais avec un message différent :

C'est parce que l'erreur ~404~ a été supprimée automatiquement par la méthode getRoute(). Donc, nous pouvons encore simplifier la méthode executeShow :

    class jobActions extends sfActions
    {
      public function executeShow(sfWebRequest $request)
      {
        $this->job = $this->getRoute()->getObject();
      }

      // ...
    }

TIP

Si vous ne voulez pas que la route génère une erreur 404, vous pouvez paramétrer l'option de routage ~allow_empty~ sur true.

NOTE

L'objet lié à une route est lazy loaded. Il est récupéré dans la base de donnée si vous appelez la méthode getRoute().

Le routage dans les Actions et Templates

Dans un template, le helper url_for() convertit une URI interne en URL externe. D'autres helpers symfony utilisent aussi une URI interne en tant qu'argument, comme le helper ~link_to()~ qui génère un tag <a> :

    <?php echo link_to($job->getPosition(), 'job_show_user', $job) ?>

Il produit le code HTML suivant :

    <a href="/job/sensio-labs/paris-france/1/web-developer">Web Developer</a>

Tous deux, url_for() et link_to() peuvent aussi générer des URLs absolues :

    url_for('job_show_user', $job, true);

    link_to($job->getPosition(), 'job_show_user', $job, true);

Si vous voulez générer une URL depuis une action, vous pouvez utiliser la méthode generateUrl() :

    $this->redirect($this->generateUrl('job_show_user', $job));

SIDEBAR

Les méthodes de la famille "~redirect~" Dans le tutorial d'hier, nous avons parlé des méthodes "forward". Ces méthodes renvoient la requête en cours vers une autre action sans rechargement de la page. Les méthodes "redirect" redirigent l'utilisateur vers une autre URL. Comme pour forward, vous pouvez utiliser la méthode redirect() ou les méthodes de raccourci redirectIf() et redirectUnless().

Collection Route Class

Pour le module job, nous avons déjà personnalisé la route pour l'action show. Mais les URLs pour les autres méthodes (index, new, edit, create, update, et delete) sont encore contrôlées par la route default :

    default:
      url: /:module/:action/*

La route default est idéale pour commençer à coder sans avoir besoin de définir beaucoup de routes. Mais comme cette route agit comme un "fourre-tout", elle ne peut pas être configurée pour des besoins spécifiques.

Toutes les actions pour job sont liées à la classe de modèle JobeetJob. Nous pouvons facilement définir une route ~sfPropelRoute~ personnelle pour chaque action comme déjà fait pour l'action ~show. Etant donné que le module job définit déjà les septs actions classiques pour un modèle, nous pouvons aussi utiliser la classe `~sfPropelRouteCollection~`. Ouvrez le fichier routing.yml et modifiez le comme suit :

    # apps/frontend/config/routing.yml
    job:
      class:   sfPropelRouteCollection
      options: { model: JobeetJob }

    job_show_user:
      url:     /job/:company_slug/:location_slug/:id/:position_slug
      class:   sfPropelRoute
      options: { model: JobeetJob, type: object }
      param:   { module: job, action: show }
      requirements:
        id: \d+
        sf_method: [get]

    # default rules
    homepage:
      url:   /
      param: { module: job, action: index }

    default_index:
      url:   /:module
      param: { action: index }

    default:
      url:   /:module/:action/*

La route job ci-dessus est tout simplement un raccourci qui génère automatiquement les sept routes sfPropelRoute suivantes :

    job:
      url:     /job.:sf_format
      class:   sfPropelRoute
      options: { model: JobeetJob, type: list }
      param:   { module: job, action: index, sf_format: html }
      requirements: { sf_method: get }

    job_new:
      url:     /job/new.:sf_format
      class:   sfPropelRoute
      options: { model: JobeetJob, type: object }
      param:   { module: job, action: new, sf_format: html }
      requirements: { sf_method: get }

    job_create:
      url:     /job.:sf_format
      class:   sfPropelRoute
      options: { model: JobeetJob, type: object }
      param:   { module: job, action: create, sf_format: html }
      requirements: { sf_method: post }

    job_edit:
      url:     /job/:id/edit.:sf_format
      class:   sfPropelRoute
      options: { model: JobeetJob, type: object }
      param:   { module: job, action: edit, sf_format: html }
      requirements: { sf_method: get }

    job_update:
      url:     /job/:id.:sf_format
      class:   sfPropelRoute
      options: { model: JobeetJob, type: object }
      param:   { module: job, action: update, sf_format: html }
      requirements: { sf_method: put }

    job_delete:
      url:     /job/:id.:sf_format
      class:   sfPropelRoute
      options: { model: JobeetJob, type: object }
      param:   { module: job, action: delete, sf_format: html }
      requirements: { sf_method: delete }

    job_show:
      url:     /job/:id.:sf_format
      class:   sfPropelRoute
      options: { model: JobeetJob, type: object }
      param:   { module: job, action: show, sf_format: html }
      requirements: { sf_method: get }

NOTE

Quelques routes générées par sfPropelRouteCollection ont la même ~URL~. Le routage est capable de les utiliser correctement car elles ont toutes une ~méthode HTTP~ différentes dans l'entrée requirements (conditions).

Les routes job_delete et job_update requièrent des méthodes HTTP non supportées par les navigateurs (respectivement ~DELETE~ and ~PUT~). Ca fonctionne car symfony les émulent. Pour voir un exemple, ouvrez le template _form.php :

    // apps/frontend/modules/job/templates/_form.php
    <form action="..." ...>
    <?php if (!$form->getObject()->isNew()): ?>
      <input type="hidden" name="sf_method" value="PUT" />
    <?php endif; ?>

    <?php echo link_to(
      'Delete',
      'job/delete?id='.$form->getObject()->getId(),
      array('method' => 'delete', 'confirm' => 'Are you sure?')
    ) ?>

Tous les helpers symfony peuvent être utilisés pour simuler n'importe quelle méthode HTTP grâce au paramètre spécial sf_method.

NOTE

symfony possède d'autres paramètres spéciaux comme sf_method, qui débutent tous par le préfixe sf_. Dans les routes générées ci-dessus, vous pouvez en voir un autre : sf_format qui sera expliqué un autre jour.

Débugger une Route

Quand vous avez beaucoup de routes, il est parfois utile de lister les route générées. La commande app:routes affiche toutes les routes d'une application :

    $ php symfony app:routes frontend

Il est également possible d'obtenir des informations de ~debug~age en passant le nom de la route en argument :

    $ php symfony app:routes frontend job_edit

Routes par défaut

Définir des ~route~s pour toutes les URLs est une bonne méthode de travail. Puisque la route job définit toutes les routes nécessaires pour l'utilisation de l'application Jobeet, nous pouvons supprimer ou commenter les routes par défaut dans le fichier de configuration routing.yml :

    # apps/frontend/config/routing.yml
    #default_index:
    #  url:   /:module
    #  param: { action: index }
    #
    #default:
    #  url:   /:module/:action/*

L'application Jobeet fonctionne toujours à l'identique.

À demain

Aujourd'hui, le tutoriel contenait beaucoup de nouvelles informations. Vous avez appris comment utiliser le routage de symfony et comment decouple your URLs from the technical implementation.

Demain, plutôt que d'apprendre de nouveaux concepts, nous verrons plus en détail ce que nous avons déjà appris.

Attachments