Comme nous avons pu le voir, le rôle du routeur est, à partir d'une URL, de déterminer quel contrôleur appeler et avec quels arguments. Cela permet de configurer son application pour avoir de très belles URL, ce qui est important pour le référencement et même pour le confort des visiteurs. Soyons d'accord, l'URL /article/le-systeme-de-route
est bien plus sexy que index.php?controlleur=article&methode=voir&id=5
!
Vous avez sans doute déjà entendu parler d'URL Rewriting ? Le routeur, bien que différent, permet effectivement de faire l'équivalent de l'URL Rewriting, mais il le fait côté PHP, et donc est bien mieux intégré à notre code.
- Le fonctionnement
- Les routes de base
- Les routes avancées
- Générer des URL
- Application : les routes de notre blog
Le fonctionnement
L'objectif de ce chapitre est de vous transmettre toutes les connaissances pour pouvoir créer ce que l'on appelle un fichier de mapping des routes (un fichier de correspondances, en français). Ce fichier, généralement situé dans votreBundle/Resources/config/routing.yml
, contient la définition des routes. Chaque route fait la correspondance entre une URL et le contrôleur à appeler. Je vous invite à mettre dès maintenant les routes présentées au code suivant dans le fichier, nous allons travailler dessus dans ce chapitre :
1 2 3 4 5 6 7 8 9 10 11 12 13 | # src/sdz/BlogBundle/Resources/config/routing.yml sdzblog_accueil: path: /blog defaults: { _controller: SdzBlogBundle:Blog:index } sdzblog_voir: path: /blog/article/{id} defaults: { _controller: SdzBlogBundle:Blog:voir } sdzblog_ajouter: path: /blog/ajouter defaults: { _controller: SdzBlogBundle:Blog:ajouter } |
Petit rappel au cas où : l'indentation se fait avec 4 espaces par niveau, et non avec des tabulations.
Vous pouvez supprimer la route helloTheWorld
que nous avons créée au chapitre précédent, elle ne nous resservira plus. Vous pouvez voir qu'à la place nous avons maintenant une route sdzblog_accueil
, qui pointe vers la même action du contrôleur.
Fonctionnement du routeur
Dans le code précédent, vous pouvez distinguer trois blocs. Chacun correspond à une route. Nous les verrons en détail plus loin, mais vous pouvez constater que chaque route prend :
- Une entrée (ligne
path
) : c'est l'URL à capturer ; - Une sortie (ligne
defaults
) : ce sont les paramètres de la route, notamment celui qui dit quel est le contrôleur à appeler.
Le but du routeur est donc, à partir d'une URL, de trouver la route correspondante et de retourner le contrôleur que veut cette route. Pour trouver la bonne route, le routeur va les parcourir une par une, dans l'ordre du fichier, et s'arrêter à la première route qui fonctionne. La figure suivante est un schéma équivalent au chapitre précédent, mais actualisé pour notre fichier de routes précédent.
Et voici en texte le fonctionnement, pas à pas :
- On appelle l'URL
/blog/article/5
. - Le routeur essaie de faire correspondre cette URL avec le
path
de la première route. Ici,/blog/article/5
ne correspond pas du tout à/blog
(lignepath
de la première route). - Le routeur passe donc à la route suivante. Il essaie de faire correspondre
/blog/article/5
avec/blog/article/{id}
. Nous le verrons plus loin, mais{id}
est un paramètre, une sorte de joker « je prends tout ». Cette route correspond, car nous avons bien : -/blog/article
(URL) =/blog/article
(route) ;5
(URL) ={id}
(route).
- Le routeur s'arrête donc, il a trouvé sa route.
- Il demande à la route : « Quel contrôleur souhaites-tu appeler, et avec quels paramètres ? », la route répond : « Je veux le contrôleur
SdzBlogBundle:Blog:voir
, avec le paramètre$id = 5
. » - Le routeur renvoie donc ces informations au Kernel (le noyau de Symfony2).
- Le noyau va exécuter le bon contrôleur !
Dans le cas où le routeur ne trouve aucune route correspondante, le noyau de Symfony2 va déclencher une erreur 404.
Pour chaque page, il est possible de visualiser toutes les routes que le routeur essaie une à une, et celle qu'il utilise finalement. C'est le Profiler qui s'occupe de tracer cela, accessible depuis la barre d'outils : cliquez sur le nom de la route dans la barre d'outils, « sdzblog_accueil » si vous êtes sur la page /blog
. Ce lien vous amène dans l'onglet « Request » du Profiler, mais allez dans l'onglet « Routing » qui nous intéresse. Vous devriez obtenir la figure suivante.
Vous pouvez voir qu'il y a déjà pas mal de routes définies alors que nous n'avons rien fait. Ces routes qui commencent par /_profiler/
sont les routes nécessaires au Profiler, dans lequel vous êtes. Eh oui, c'est un bundle également, le bundle WebProfilerBundle !
Convention pour le nom du contrôleur
Vous l'avez vu, lorsque l'on définit le contrôleur à appeler dans la route, il y a une convention à respecter : la même que pour appeler un template (nous l'avons vue au chapitre précédent). Un rappel ne fait pas de mal : lorsque vous écrivez « SdzBlogBundle:Blog:voir », vous avez trois informations :
- « SdzBlogBundle » est le nom du bundle dans lequel aller chercher le contrôleur. En terme de fichier, cela signifie pour Symfony2 : « Va voir dans le répertoire de ce bundle. ». Dans notre cas, Symfony2 ira voir dans
src/Sdz/BlogBundle
. - « Blog » est le nom du contrôleur à ouvrir. En terme de fichier, cela correspond à
controller/BlogController.php
dans le répertoire du bundle. Dans notre cas, nous avons comme chemin absolusrc/Sdz/BlogBundle/controller/BlogController.php
. - « voir » est le nom de l'action à exécuter au sein du contrôleur. Attention, lorsque vous définissez cette méthode dans le contrôleur, vous devez la faire suivre du suffixe « Action », comme ceci :
<?php public function voirAction()
.
Les routes de base
Créer une route
Étudions la première route plus en détail :
1 2 3 4 5 | # src/Sdz/BlogBundle/config/Resources/routing.yml sdzblog_accueil: path: /blog defaults: { _controller: SdzBlogBundle:Blog:index } |
Ce bloc représente ce que l'on nomme une « route ». Elle est constituée au minimum de trois éléments :
sdzblog_accueil
est le nom de la route. Il n'a aucune importance dans le travail du routeur, mais il interviendra lorsque l'on voudra générer des URL : eh oui, on n'écrira pas l'URL à la main, mais on fera appel au routeur pour qu'il fasse le travail à notre place ! Retenez donc pour l'instant qu'il faut qu'un nom soit unique et clair. On a donc préfixé les routes de « sdzblog » pour l'unicité entre bundles.path: /blog
est l'URL sur laquelle la route s'applique. Ici, « /blog » correspond à une URL absolue du typehttp://www.monsite.com/blog
.defaults: { _controller: SdzBlogBundle:Blog:index }
correspond au contrôleur à appeler.
Vous avez maintenant les bases pour créer une route simple !
Créer une route avec des paramètres
Reprenons la deuxième route de notre exemple :
1 2 3 4 5 | # src/Sdz/BlogBundle/Resources/config/routing.yml sdzblog_voir: path: /blog/article/{id} defaults: { _controller: SdzBlogBundle:Blog:voir } |
Grâce au paramètre {id}
dans le path
de notre route, toutes les URL du type /blog/article/*
seront gérées par cette route, par exemple : /blog/article/5
ou /blog/article/654
, ou même /blog/article/sodfihsodfih
(on n'a pas encore dit que {id}
devait être un nombre, patience !). Par contre, l'URL /blog/article
ne sera pas interceptée, car le paramètre {id}
n'est pas renseigné. En effet, les paramètres sont par défaut obligatoires, nous verrons quand et comment les rendre facultatifs plus loin dans ce chapitre.
Mais si le routeur s’arrêtait là, il n'aurait aucun intérêt. Toute sa puissance réside dans le fait que ce paramètre {id}
est accessible depuis votre contrôleur ! Si vous appelez l'URL /blog/article/5
, alors depuis votre contrôleur vous aurez la variable $id
(du nom du paramètre) qui aura pour valeur « 5 ». Je vous invite à créer la méthode correspondante dans le contrôleur :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class BlogController extends Controller { // … ici la méthode indexAction() que l'on a déjà créée // La route fait appel à SdzBlogBundle:Blog:voir, on doit donc définir la méthode voirAction // On donne à cette méthode l'argument $id, pour correspondre au paramètre {id} de la route public function voirAction($id) { // $id vaut 5 si l'on a appelé l'URL /blog/article/5 // Ici, on récupèrera depuis la base de données l'article correspondant à l'id $id // Puis on passera l'article à la vue pour qu'elle puisse l'afficher return new Response("Affichage de l'article d'id : ".$id."."); } } |
N'oubliez pas de tester votre code à l'adresse suivante : http://localhost/Symfony/web/app_dev.php/blog/article/5
, et amusez-vous à changer la valeur du paramètre.
Vous pouvez bien sûr multiplier les paramètres au sein d'une même route. Ajoutez cette route juste après la route sdzblog_voir, pour l'exemple :
1 2 3 4 5 | # src/Sdz/BlogBundle/Resources/config/routing.yml sdzblog_voir_slug: path: /blog/{annee}/{slug}.{format} defaults: { _controller: SdzBlogBundle:Blog:voirSlug } |
Cette route permet d'intercepter les URL suivantes : /blog/2011/mon-weekend.html
ou /blog/2012/symfony.xml
, etc. Et voici la méthode correspondante qu'on aurait côté contrôleur :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class BlogController extends Controller { // … ici les méthodes indexAction() et voirAction() que l'on a déjà créées // On récupère tous les paramètres en arguments de la méthode public function voirSlugAction($slug, $annee, $format) { // Ici le contenu de la méthode return new Response("On pourrait afficher l'article correspondant au slug '".$slug."', créé en ".$annee." et au format ".$format."."); } } |
Notez que l'ordre des arguments dans la définition de la méthode voirSlugAction()
n'a pas d'importance. La route fait la correspondance à partir du nom des variables utilisées, non à partir de leur ordre. C'est toujours bon à savoir !
Revenez à notre route et notez également le point entre les paramètres {slug}
et {format}
: vous pouvez en effet séparer vos paramètres soit avec le slash (« / »), soit avec le point (« . »). Veillez donc à ne pas utiliser de point dans le contenu de vos paramètres. Par exemple, pour notre paramètre {slug}
, une URL /blog/2011/mon-weekend.etait.bien.html
ne va pas correspondre à cette route, car :
{annee}
= 2011 ;{slug}
= mon-weekend ;{format}
= etait ;- ? = bien ;
- ? = html ;
La route attend des paramètres à mettre en face de ces dernières valeurs, et comme il n'y en a pas cette route dit : « Cette URL ne me correspond pas, passez à la route suivante. » Attention donc à ce petit détail.
Les routes avancées
Créer une route avec des paramètres et leurs contraintes
Nous avons créé une route avec des paramètres, très bien. Mais si quelqu'un essaie d'atteindre l'URL /blog/oaisd/aouish.oasidh
, eh bien, rien ne l'en empêche ! Et pourtant, « oaisd » n'est pas tellement une année valide ! La solution ? Les contraintes sur les paramètres. Reprenons notre dernière route sdzblog_voir_slug :
1 2 3 4 5 | # src/Sdz/BlogBundle/Resources/config/routing.yml sdzblog_voir_slug: path: /blog/{annee}/{slug}.{format} defaults: { _controller: SdzBlogBundle:Blog:voirSlug } |
Nous voulons ne récupérer que les bonnes URL où l'année vaut « 2010 » et non « oshidf », par exemple. Cette dernière devrait retourner une erreur 404 (page introuvable). Pour cela, il nous suffit qu'aucune route ne l'intercepte ; ainsi, le routeur arrivera à la fin du fichier sans aucune route correspondante et il déclenchera tout seul une erreur 404.
Comment faire pour que notre paramètre {annee}
n'intercepte pas « oshidf » ? C'est très simple :
1 2 3 4 5 6 7 8 | # src/Sdz/BlogBundle/Resources/config/routing.yml sdzblog_voir_slug: path: /blog/{annee}/{slug}.{format} defaults: { _controller: SdzBlogBundle:Blog:voirSlug } requirements: annee: \d{4} format: html|xml |
Nous avons ajouté la section requirements
. Comme vous pouvez le voir, on utilise les expressions régulières pour déterminer les contraintes que doivent respecter les paramètres. Ici :
\d{4}
veut dire « quatre chiffres à la suite ». L'URL/blog/sdff/mon-weekend.html
ne sera donc pas interceptée. Vous l'avez reconnue, c'est une expression régulière. Vous pouvez utiliser n'importe laquelle, je vous invite à lire le cours correspondant de M@teo21.html|xml
signifie « soit HTML, soit XML ». L'URL/blog/2011/mon-weekend.rss
ne sera donc pas interceptée.
N'hésitez surtout pas à faire les tests ! Cette route est opérationnelle, nous avons créé l'action correspondante dans le contrôleur. Essayez donc de bien comprendre quels paramètres sont valides, lesquels ne le sont pas. Vous pouvez également changer la section requirements
.
Maintenant, nous souhaitons aller plus loin. En effet, si le « .xml » est utile pour récupérer l'article au format XML (pourquoi pas ?), le « .html » semble inutile : par défaut, le visiteur veut toujours du HTML. Il faut donc rendre le paramètre {format}
facultatif.
Utiliser des paramètres facultatifs
Reprenons notre route et ajoutons-y la possibilité pour {format}
de ne pas être renseigné :
1 2 3 4 5 6 7 8 | # src/Sdz/BlogBundle/Resources/config/routing.yml sdzblog_voir_slug: path: /blog/{annee}/{slug}.{format} defaults: { _controller: SdzBlogBundle:Blog:voirSlug, format: html } requirements: annee: \d{4} format: html|xml |
Nous avons juste ajouté une valeur par défaut dans le tableau defaults
: format: html
. C'est aussi simple que cela !
Ainsi, l'URL /blog/2011/mon-weekend
sera bien interceptée et le paramètre format
sera mis à sa valeur par défaut, à savoir « html ». Au niveau du contrôleur, rien ne change : vous gardez l'argument $format
comme avant et celui-ci vaudra « html », la valeur par défaut.
Utiliser des « paramètres système »
Prenons l'exemple de notre paramètre {format}
: lorsqu'il vaut « xml », vous allez afficher du XML et devrez donc envoyer le header avec le bon Content-type
. Les développeurs de Symfony2 ont pensé à nous et prévu des « paramètres système ». Ils s'utilisent exactement comme des paramètres classiques, mais effectuent automatiquement des actions supplémentaires :
- Le paramètre
{_format}
: lorsqu'il est utilisé (comme notre paramètre{format}
, ajoutez juste un underscore), alors un header avec leContent-type
correspondant est envoyé. Exemple : vous appelez/blog/2011/mon-weekend.xml
et le routeur va dire à l'objetRequest
que l'utilisateur demande du XML. Ainsi, l'objetResponse
enverra un headerContent-type: application/xml
. Vous n'avez plus à vous en soucier ! Depuis le contrôleur, vous pouvez récupérer ce format soit avec l'argument$_format
comme n'importe quel autre argument, soit via la méthodegetRequestFormat()
de l'objetRequest
. Par exemple :<?php $this->get('request')->getRequestFormat()
. - Le paramètre
{_locale}
: lorsqu'il est utilisé, il va définir la langue dans laquelle l'utilisateur souhaite obtenir la page. Ainsi, si vous avez défini des fichiers de traduction ou si vous employez des bundles qui en utilisent, alors les traductions dans la langue du paramètre{_locale}
seront chargées. Pensez à mettre unrequirements:
sur la valeur de ce paramètre pour éviter que vos utilisateurs ne demandent le russe alors que votre site n'est que bilingue français-anglais.
Ajouter un préfixe lors de l'import de nos routes
Vous avez remarqué que nous avons mis /blog
au début du path
de chacune de nos routes. En effet, on crée un blog, on aimerait donc que toutes les URL aient ce préfixe /blog
. Au lieu de les répéter à chaque fois, Symfony2 vous propose de rajouter un préfixe lors de l'import du fichier de notre bundle.
Modifiez donc le fichier app/config/routing.yml
comme suit :
1 2 3 4 5 | # app/config/routing.yml
SdzBlogBundle:
resource: "@SdzBlogBundle/Resources/config/routing.yml"
prefix: /blog
|
Vous pouvez ainsi enlever la partie /blog
de chacune de vos routes. Bonus : si un jour vous souhaitez changer /blog
par /blogdemichel
, vous n'aurez qu'à modifier une seule ligne.
Générer des URL
Pourquoi générer des URL ?
J'ai mentionné précédemment que le routeur pouvait aussi générer des URL à partir du nom des routes. En effet, vu que le routeur a toutes les routes à sa disposition, il est capable d'associer une route à une certaine URL, mais également de reconstruire l'URL correspondant à une certaine route. Ce n'est pas une fonctionnalité annexe, mais bien un outil puissant que nous avons là !
Par exemple, nous avons une route nommée « sdzblog_voir » qui écoute l'URL /blog/article/{id}
. Vous décidez un jour de raccourcir vos URL et vous aimeriez bien que vos articles soient disponibles depuis /blog/a/{id}
. Si vous aviez écrit toutes vos URL à la main, vous auriez dû toutes les changer à la main, une par une. Grâce à la génération d'URL, vous ne modifiez que la route : ainsi, toutes les URL générées seront mises à jour ! C'est un exemple simple, mais vous pouvez trouver des cas bien réels et tout aussi gênants sans la génération d'URL.
Comment générer des URL ?
1. Depuis le contrôleur
Pour générer une URL, vous devez le demander au routeur en lui donnant deux arguments : le nom de la route ainsi que les éventuels paramètres de cette route.
Depuis un contrôleur, c'est la méthode <?php $this->generateUrl()
qu'il faut appeler. Par exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <?php // src/Sdz/BlogBundle/Controller/BlogController.php namespace Sdz\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class BlogController extends Controller { public function indexAction() { // On fixe un id au hasard ici, il sera dynamique par la suite, évidemment $id = 5; // On veut avoir l'URL de l'article d'id $id. $url = $this->generateUrl('sdzblog_voir', array('id' => $id)); // $url vaut « /blog/article/5 » // On redirige vers cette URL (ça ne sert à rien, on est d'accord, c'est pour l'exemple !) return $this->redirect($url); } } |
Pour générer une URL absolue, lorsque vous l'envoyez par e-mail, par exemple, il faut mettre le troisième argument à true
. Exemple :
1 2 | <?php $url = $this->generateUrl('sdzblog_voir', array('id' => $id), true); |
Ainsi, $url
vaut http://monsite.com/blog/article/5
et pas uniquement /blog/article/5
.
2. Depuis une vue Twig
Vous aurez bien plus l'occasion de devoir générer une URL depuis la vue. C'est la fonction path
qu'il faut utiliser depuis un template Twig :
1 2 3 | {# Dans une vue Twig, en considérant bien sûr que la variable article_id est disponible #} <a href="{{ path('sdzblog_voir', { 'id': article_id }) }}">Lien vers l'article d'id {{ article_id }}</a> |
Et pour générer une URL absolue depuis Twig, pas de troisième argument, mais on utilise la fonction url()
au lieu de path()
. Elle s'utilise exactement de la même manière, seul le nom change.
Voilà : vous savez générer des URL, ce n'était vraiment pas compliqué. Pensez bien à utiliser la fonction {{ path }}
pour tous vos liens dans vos templates.
Application : les routes de notre blog
Construction des routes
Revenons à notre blog. Maintenant que nous savons créer des routes, je vous propose de faire un premier jet de ce que seront nos URL. Voici les routes que je vous propose de créer, libre à vous d'en changer.
Page d'accueil
On souhaite avoir une URL très simple pour la page d'accueil : /blog
. Comme /blog
est défini comme préfixe lors du chargement des routes de notre bundle, le path
ici est « /
». Mais on veut aussi pouvoir parcourir les articles plus anciens, donc il nous faut une notion de page courante. En ajoutant le paramètre facultatif {page}
, nous aurons :
|
page = 1 |
|
page = 1 |
|
page = 2 |
C'est plutôt joli, non ? Voici la route :
1 2 3 4 5 6 7 | # src/Sdz/BlogBundle/Resources/config/routing.yml sdzblog_accueil: path: /{page} defaults: { _controller: SdzBlogBundle:Blog:index, page: 1 } requirements: page: \d* |
Page de visualisation d'un article
Pour la page d'un unique article, la route est très simple. Il suffit juste de bien mettre un paramètre {id}
qui nous servira à récupérer le bon article côté contrôleur. Voici la route :
1 2 3 4 5 | # src/Sdz/BlogBundle/Resources/config/routing.yml sdzblog_voir: path: /article/{id} defaults: { _controller: SdzBlogBundle:Blog:voir } |
Ajout, modification et suppression
Les routes sont simples :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # src/Sdz/BlogBundle/Resources/config/routing.yml sdzblog_ajouter: path: /ajouter defaults: { _controller: SdzBlogBundle:Blog:ajouter } sdzblog_modifier: path: /modifier/{id} defaults: { _controller: SdzBlogBundle:Blog:modifier } requirements: id: \d+ sdzblog_supprimer: path: /supprimer/{id} defaults: { _controller: SdzBlogBundle:Blog:supprimer } requirements: id: \d+ |
Récapitulatif
Voici le code complet de notre fichier src/Sdz/BlogBundle/Resources/config/routing.yml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | # src/Sdz/BlogBundle/Resources/config/routing.yml sdzblog_accueil: path: /{page} defaults: { _controller: SdzBlogBundle:Blog:index, page: 1 } requirements: page: \d* sdzblog_voir: path: /article/{id} defaults: { _controller: SdzBlogBundle:Blog:voir } requirements: id: \d+ sdzblog_ajouter: path: /ajouter defaults: { _controller: SdzBlogBundle:Blog:ajouter } sdzblog_modifier: path: /modifier/{id} defaults: { _controller: SdzBlogBundle:Blog:modifier } requirements: id: \d+ sdzblog_supprimer: path: /supprimer/{id} defaults: { _controller: SdzBlogBundle:Blog:supprimer } requirements: id: \d+ |
N'oubliez pas de bien ajouter le préfixe /blog
lors de l'import de ce fichier, dans app/config/routing.yml
:
1 2 3 4 5 | # app/config/routing.yml SdzBlogBundle: resource: "@SdzBlogBundle/Resources/config/routing.yml" prefix: /blog |
Pour conclure
Ce chapitre est terminé, et vous savez maintenant tout ce qu'il faut savoir sur le routeur et les routes.
Retenez que ce système de routes vous permet premièrement d'avoir des belles URL, et deuxièmement de découpler le nom de vos URL du nom de vos contrôleurs. Ajoutez à cela la génération d'URL, et vous avez un système extrêmement flexible et maintenable.
Le tout sans trop d'efforts !
Pour plus d'informations sur le système de routes, n'hésitez pas à lire la documentation officielle.
En résumé
- Une route est composée au minimum de deux éléments : l'URL à faire correspondre (son
path
), et le contrôleur à exécuter (attribut_controller
). - Le routeur essaie de faire correspondre chaque route à l'URL appelée par l'internaute, et ce dans l'ordre d'apparition des routes : la première route qui correspond est sélectionnée.
- Une route peut contenir des arguments, facultatifs ou non, représentés par les accolades {argument}, et dont la valeur peut être soumise à des contraintes via la section
requirements
. - Le routeur est également capable de générer des URL à partir du nom d'une route, et de ses paramètres éventuels.