Routage

Les belles URL sont une obligation pour toute application web sérieuse. Cela signifie laisser derrière des URLs horribles comme index.php?article_id=57 en faveur de quelque chose comme /read/intro-to-symfony .

Avoir de la flexibilité est encore plus important. Que faire si vous avez besoin de changer l'URL d'une page de /blog à /news ? De combien de liens auriez-vous besoin pour traquer et mettre à jour pour effectuer le changement? Si vous utilisez le routeur de Symfony, la modification est simple.

Créer des routes

Tout d'abord, ajoutez le support pour les annotations via le SensioFrameworkExtraBundle:

  composer require sensio/framework-extra-bundle

Une route est une carte d'un chemin d'URL vers un contrôleur. Supposons que vous vouliez une route correspondant exactement au /blog et une autre route plus dynamique pouvant correspondre à n'importe quelle URL comme /blog/my-post ou /blog/all-about-symfony :

  • Annotations
     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
    30
    31
     // src/Controller/BlogController.php
    namespace App\Controller ;
    
    use Symfony\Bundle\FrameworkBundle\Controller\Controller ;
    use Symfony\Component\Routing\Annotation\Route ;
    
    class BlogController extends Controller
    {
        /**
         * Matches /blog exactly
         *
         * @Route("/blog", name="blog_list")
         */
        public function list()
        {
            // ...
        }
    
        /**
         * Matches /blog/*
         *
         * @Route("/blog/{slug}", name="blog_show")
         */
        public function show($slug)
        {
            // $slug will equal the dynamic part of the URL
            // e.g. at /blog/yay-routing, then $slug='yay-routing'
    
            // ...
        }
    }
    
  •  

Merci à ces deux routes:

  • Si l'utilisateur accède à /blog , la première route est reconnue et list() est exécutée;
  • Si l'utilisateur accède à /blog/* , la seconde route correspond et show() est exécutée. Parce que le chemin d'accès est /blog/{slug} , une variable $slug est passée à show() correspondant à cette valeur. Par exemple, si l'utilisateur va dans /blog/yay-routing , alors $slug sera égal à yay-routing .

Chaque fois que vous avez un {placeholder} dans votre chemin d'accès, cette partie devient un caractère générique: elle correspond à n'importe quelle valeur. Votre contrôleur peut maintenant avoir un argument appelé $placeholder (les noms génériques et  argument doivent correspondre).

Chaque route a également un nom interne: blog_list et blog_show . Ceux-ci peuvent être n'importe quoi (tant que chacun est unique) et n'ont encore aucune signification. Vous les utiliserez plus tard pour générer des URL .

Routage dans d'autres formats

Le @Route au-dessus de chaque méthode s'appelle une annotation . Si vous préférez configurer vos routes en YAML, XML ou PHP, ce n'est pas un problème! Créez simplement un nouveau fichier de routage (par exemple routes.xml ) et Symfony l'utilisera automatiquement.

Ajout d'un  {placeholder} pour Conditions requises

Imaginez que la route blog_list contienne une liste paginée d'articles de blog, avec des URL comme /blog/2 et /blog/3 pour les pages 2 et 3. Si vous modifiez le chemin de l'itinéraire vers /blog/{page} , vous aurez un problème:

  • blog_list: /blog/{page} correspondra à  /blog/* ;
  • blog_show: /blog/{slug} correspondra également  à /blog/* .

Lorsque deux routes correspondent à la même URL, la première route chargée est gagnante. Malheureusement, cela signifie que /blog/yay-routing correspondra à la blog_list . Pas bien!

Pour résoudre ce problème, ajoutez une exigence selon laquelle le caractère générique {page} ne peut correspondre qu'aux chiffres (chiffres):

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
     // src/Controller/BlogController.php
    namespace App\Controller ;
    
    use Symfony\Bundle\FrameworkBundle\Controller\Controller ;
    use Symfony\Component\Routing\Annotation\Route ;
    
    class BlogController extends Controller
    {
        /**
         * @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"})
         */
        public function list ($page)
        {
            // ...
        }
    
        /**
         * @Route("/blog/{slug}", name="blog_show")
         */
        public function show ($slug)
        {
            // ...
        }
    }
    
  •  

Le \d+ est une expression régulière qui correspond à un chiffre de n'importe quelle longueur. À présent:

URL Route Paramètres
/blog/2 blog_list $page = 2
/blog/yay-routing blog_show $slug = yay-routing

Pour en savoir plus sur les autres exigences relatives aux itinéraires, telles que la méthode HTTP, le nom d'hôte et les expressions dynamiques, consultez la rubrique Comment définir les exigences de route .

Donner à {placeholder} une valeur par défaut

Dans l'exemple précédent, la blog_list a un chemin de /blog/{page} . Si l'utilisateur visite /blog/1 , il correspondra. Mais s'ils visitent /blog , cela ne correspondra pas . Dès que vous ajoutez un {placeholder} à une route, il doit avoir une valeur.

Alors, comment pouvez-vous faire correspondre blog_list une fois que l'utilisateur visite /blog ? En ajoutant une valeur par défaut :

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
     // src/Controller/BlogController.php
    namespace App\Controller ;
    
    use Symfony\Bundle\FrameworkBundle\Controller\Controller ;
    use Symfony\Component\Routing\Annotation\Route ;
    
    class BlogController extends Controller
    {
        /**
         * @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"})
         */
        public function list($page = 1)
        {
            // ...
        }
    }
    
  •  

Maintenant, lorsque l'utilisateur visite /blog , la route blog_list correspondra et $page aura la valeur 1 par défaut.

Liste de toutes vos routes

Au fur et à mesure que votre application grandit, vous aurez de nombreuses routes! Pour les voir tous, lancez:

  php bin/console debug:router
1
2
3
4
5
6
------------------------------ -------- -------------------------------------
 Name                           Method   Path
------------------------------ -------- -------------------------------------
 app_lucky_number              ANY    /lucky/number/{max}
 ...
------------------------------ -------- -------------------------------------

Exemple de routage avancé

Avec tout cela en tête, consultez cet exemple avancé:

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     // src/Controller/ArticleController.php
    
    // ...
    class ArticleController extends Controller
    {
        /**
         * @Route(
         *     "/articles/{_locale}/{year}/{slug}.{_format}",
         *     defaults={"_format": "html"},
         *     requirements={
         *         "_locale": "en|fr",
         *         "_format": "html|rss",
         *         "year": "\d+"
         *     }
         * )
         */
        public function show ($_locale , $year , $slug)
        {
        }
    }
    

Comme vous l'avez vu, cette route ne correspondra que si la partie {_locale} de l'URL est en ou fr et si {year} est un nombre. Cette route montre également comment vous pouvez utiliser un point entre des espaces réservés au lieu d'une barre oblique. Les URL correspondant à cette route peuvent ressembler à ceci:

  • /articles/en/2010/my-post
  • /articles/fr/2010/my-post.rss
  • /articles/en/2013/my-latest-post.html
Le paramètre de routage _format spécial

Cet exemple met également en évidence le paramètre spécial de routage _format . Lorsque vous utilisez ce paramètre, la valeur correspondante devient le "format de requête" de l'objet Request .

En fin de compte, le format de la requête est utilisé pour définir le Content-Type de Content-Type de la réponse (par exemple, un format de requête json traduit par Content-Type of application/json ).

Remarque

Parfois, vous souhaitez configurer certaines parties de vos routes globalement configurables. Symfony vous fournit un moyen de le faire en exploitant les paramètres du conteneur de service. En savoir plus à ce sujet dans " Comment utiliser les paramètres du conteneur de service dans vos routes ".

Paramètres de routage spéciaux

Comme vous l'avez vu, chaque paramètre de routage ou valeur par défaut est éventuellement disponible en tant qu'argument dans la méthode du contrôleur. De plus, quatre paramètres sont spéciaux: chacun ajoute une fonctionnalité unique à votre application:

_controller

Comme vous l'avez vu, ce paramètre est utilisé pour déterminer quel contrôleur est exécuté lorsque l'itinéraire est reconnu.

_format

Utilisé pour définir le format de la requête (en savoir plus ).

_fragment

Utilisé pour définir l'identificateur de fragment, la dernière partie facultative d'une URL qui commence par un caractère # et qui est utilisée pour identifier une partie d'un document.

_locale

Utilisé pour définir les paramètres régionaux sur la demande (en savoir plus ).

Rediriger des URL avec des barres obliques

Historiquement, les URL ont suivi la convention UNIX d'ajouter des barres obliques pour les répertoires (par exemple https://example.com/foo/ ) et de les supprimer pour faire référence aux fichiers ( https://example.com/foo ). Bien que le fait de proposer un contenu différent pour que les deux URL soient correctes, il est courant de traiter les deux URL comme la même URL et de les rediriger.

Symfony suit cette logique pour rediriger les URL avec et sans slash (mais uniquement pour les requêtes GET et HEAD ):

Chemin de route Si l'URL demandée est /foo Si l'URL demandée est /foo/
/foo Il correspond ( 200 réponse de statut) Il ne correspond pas (réponse de statut 404 )
/foo/ Il fait une redirection 301 vers /foo/ Il correspond ( 200 réponse de statut)

En résumé, l'ajout d'une barre oblique finale dans le chemin de routage est le meilleur moyen de s'assurer que les deux URL fonctionnent. Lisez l'article Rediriger les URL avec une barre oblique final pour savoir comment éviter l'erreur 404 lorsque l'URL de la requête contient une barre oblique et que le chemin d'accès ne l'est pas.

Modèle de nommage du contrôleur

La valeur du controller dans vos routes a un format très simple CONTROLLER_CLASS::METHOD . Si votre contrôleur est enregistré en tant que service, vous pouvez également utiliser un seul séparateur de deux points (par exemple, service_name:index ).

enlightened Pour faire référence à une action qui est implémentée en tant que méthode __invoke() d'une classe de contrôleur, vous n'avez pas besoin de passer le nom de la méthode, mais simplement utiliser le nom de classe complet (par exemple App\Controller\BlogController ).

Génération d'URL

Le système de routage peut également générer des URL. En réalité, le routage est un système bidirectionnel: mappage de l'URL à un contrôleur et renvoi à une URL.

Pour générer une URL, vous devez spécifier le nom de la route (par exemple blog_show ) et tous les caractères génériques (par exemple slug = my-blog-post ) utilisés dans le chemin de cette route. Avec cette information, n'importe quelle URL peut facilement être générée:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
 class MainController extends Controller
{
    public function show ($slug)
    {
        // ...

        // /blog/my-blog-post
        $url = $this -> generateUrl (
            'blog_show' ,
            array ('slug' => 'my-blog-post')
        );
    }
}

Si vous devez générer une URL à partir d'un service, entrez une indication de type le service UrlGeneratorInterface :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
 // src/Service/SomeService.php

use Symfony\Component\Routing\Generator\UrlGeneratorInterface ;

class SomeService
{
    private $router ;

    public function __construct(UrlGeneratorInterface $router)
    {
        $this -> router = $router ;
    }

    public function someMethod()
    {
        $url = $this->router->generate(
            'blog_show' ,
            array ('slug' => 'my-blog-post')
        );
        // ...
    }
}

Génération d'URL avec des query strings

La méthode generate() prend un tableau de valeurs génériques pour générer l'URI. Mais si vous en transmettez d'autres, ils seront ajoutés à l'URI en tant que query strings:

1
2
3
4
5
 $this -> router -> generate ('blog' , array(
    'page' => 2 ,
    'category' => 'Symfony' ,
));
// /blog/2?category=Symfony

Génération d'URL à partir d'un template

Pour générer des URL dans Twig, consultez l'article de template: Lier aux pages . Si vous devez également générer des URL en JavaScript, consultez la rubrique comment genérer des routes URLs en javascript

Génération d'URL absolues

Par défaut, le routeur génère des URL relatives (par exemple /blog ). À partir d'un contrôleur, passez UrlGeneratorInterface::ABSOLUTE_URL au troisième argument de la méthode generateUrl() :

 use Symfony\Component\Routing\Generator\UrlGeneratorInterface ;

$this -> generateUrl( 'blog_show' , array ( 'slug' => 'my-blog-post' ), UrlGeneratorInterface :: ABSOLUTE_URL );
// http://www.example.com/blog/my-blog-post
 

Remarque

L'hôte utilisé lors de la génération d'une URL absolue est automatiquement détecté à l'aide de l'objet Request cours. Lorsque vous générez des URL absolues en dehors du contexte Web (par exemple, dans une commande de console), cela ne fonctionne pas. Voir Comment générer des URL à partir de la console pour savoir comment résoudre ce problème.

Dépannage

Voici quelques erreurs courantes que vous pourriez rencontrer en travaillant avec le routage:

Contrôleur "App \ Controller \ BlogController :: show ()" nécessite que vous fournissiez une valeur pour l'argument "$slug".

Cela se produit lorsque votre méthode de contrôleur a un argument (par exemple $slug ):

 public function show($slug)
{
    // ..
}

Mais votre chemin d'accès n'a pas de caractère générique {slug} (par exemple, c'est /blog/show ). Ajoutez un {slug} au chemin de votre route: /blog/show/{slug} ou donnez à l'argument une valeur par défaut (par exemple $slug = null ).

Certains paramètres obligatoires sont manquants ("slug") pour générer une URL pour la route "blog_show".

Cela signifie que vous essayez de générer une URL vers la route blog_show mais que vous ne transmettez pas de valeur de slug (ce qui est obligatoire, car elle contient un caractère générique {slug} ) dans le chemin d'accès. Pour résoudre ce problème, transmettez une valeur de slug lors de la génération de la route:

 $this->generateUrl('blog_show' , array ('slug' => 'slug-value'));

// or, in Twig
// {{ path('blog_show', {'slug': 'slug-value'}) }}

Traduire des routes

Symfony ne prend pas en charge la définition de routes avec des contenus différents en fonction de la langue de l'utilisateur. Dans ce cas, vous pouvez définir plusieures route par contrôleur, un pour chaque langue prise en charge; ou utilisez l'un des bundles créés par la communauté pour implémenter cette fonctionnalité, tels que JMSI18nRoutingBundle et BeSimpleI18nRoutingBundle .

Routage, vérifiez! Maintenant, découvrez la puissance des contrôleurs .

En savoir plus sur le routage