Les routes avec Symfony 6

Lorsque votre application reçoit une requête, elle appelle une action du contrôleur pour générer la réponse. La configuration du routage définit l’action à exécuter pour chaque URL entrante. Elle fournit également d’autres fonctionnalités utiles, comme la génération d’URL adaptées au référencement

Création d’itinéraires

Les itinéraires peuvent être configurés en YAML, XML, PHP ou en utilisant des attributs ou des annotations. Tous les formats offrent les mêmes fonctionnalités et performances, alors choisissez votre préféré. Symfony recommande les attributs car il est pratique de mettre la route et le contrôleur au même endroit.

Créer des routes en tant qu’attributs ou annotations

En PHP 8, vous pouvez utiliser les attributs natifs pour configurer les routes immédiatement. En PHP 7, où les attributs ne sont pas disponibles, vous pouvez utiliser des annotations à la place, fournies par la bibliothèque Doctrine Annotations.

Si vous souhaitez utiliser les annotations au lieu des attributs, exécutez cette commande une fois dans votre application pour les activer :

composer require doctrine/annotations

Cette commande crée également le fichier de configuration suivant :

# config/routes/annotations.yaml
controllers:
    resource: ../../src/Controller/
    type: annotation

kernel:
    resource: ../../src/Kernel.php
    type: annotation

Cette configuration indique à Symfony de rechercher les routes définies comme annotations dans toute classe PHP stockée dans le répertoire src/Controller/.

Supposons que vous voulez définir une route pour l’URL /blog dans votre application. Pour ce faire, créez une classe de contrôleur comme la suivante :

// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class BlogController extends AbstractController
{
    /**
     * @Route("/blog", name="blog_list")
     */
    public function list()
    {
        // ...
    }
}

Cette configuration définit une route appelée blog_list qui correspond lorsque l’utilisateur demande l’URL /blog. Lorsque la correspondance se produit, l’application exécute la méthode list() de la classe BlogController.

La chaîne de requête d’une URL n’est pas prise en compte lors de la recherche de routes. Dans cet exemple, les URL telles que /blog?foo=bar et /blog?foo=bar&bar=foo correspondent également à la route blog_list.

Le nom de la route (blog_list) n’est pas important pour l’instant, mais il sera essentiel plus tard lors de la génération des URLs. Vous devez simplement garder à l’esprit que chaque nom de route doit être unique dans l’application.

Création d’itinéraires dans des fichiers YAML, XML ou PHP

Au lieu de définir des routes dans les classes de contrôleur, vous pouvez les définir dans un fichier YAML, XML ou PHP distinct. Le principal avantage est qu’elles ne nécessitent aucune dépendance supplémentaire. Le principal inconvénient est que vous devez travailler avec plusieurs fichiers pour vérifier le routage d’une action de contrôleur.

L’exemple suivant montre comment définir en YAML/XML/PHP une route appelée blog_list qui associe l’URL /blog à l’action list() du BlogController :

# config/routes.yaml
blog_list:
    path: /blog
    # the controller value has the format 'controller_class::method_name'
    controller: App\Controller\BlogController::list

    # if the action is implemented as the __invoke() method of the
    # controller class, you can skip the '::method_name' part:
    # controller: App\Controller\BlogController
Par défaut, Symfony ne charge que les routes définies au format YAML. Si vous définissez des routes au format XML et/ou PHP, mettez à jour le fichier src/Kernel.php pour ajouter le support des extensions de fichiers .xml et .php.

Correspondance des méthodes HTTP

Par défaut, les routes répondent à tous les verbes HTTP (GET, POST, PUT, etc.). Utilisez l’option methods pour restreindre les verbes auxquels chaque route doit répondre :

// src/Controller/BlogApiController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class BlogApiController extends AbstractController
{
    /**
     * @Route("/api/posts/{id}", methods={"GET","HEAD"})
     */
    public function show(int $id): Response
    {
        // ... return a JSON response with the post
    }

    /**
     * @Route("/api/posts/{id}", methods={"PUT"})
     */
    public function edit(int $id): Response
    {
        // ... edit a post
    }
}

Les formulaires HTML ne prennent en charge que les méthodes GET et POST. Si vous appelez une route avec une méthode différente depuis un formulaire HTML, ajoutez un champ caché appelé _method avec la méthode à utiliser (par exemple ). Si vous créez vos formulaires avec Symfony Forms, cela est fait automatiquement pour vous.

Expressions de correspondance

Utilisez l’option condition si vous souhaitez qu’une route soit comparée à une autre en fonction d’une logique de correspondance arbitraire :

// src/Controller/DefaultController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class DefaultController extends AbstractController
{
    /**
     * @Route(
     *     "/contact",
     *     name="contact",
     *     condition="context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i'"
     * )
     *
     * expressions can also include configuration parameters:
     * condition: "request.headers.get('User-Agent') matches '%app.allowed_browsers%'"
     */
    public function contact(): Response
    {
        // ...
    }
}

La valeur de l’option condition est toute expression valide en ExpressionLanguage et peut utiliser n’importe laquelle de ces variables créées par Symfony :

context

Une instance de RequestContext, qui contient les informations les plus fondamentales sur la route en cours de correspondance.

demande

L’objet Symfony Request qui représente la requête en cours.

Dans les coulisses, les expressions sont compilées en PHP brut. Pour cette raison, l’utilisation de la clé de condition ne cause pas de surcharge supplémentaire au-delà du temps d’exécution du PHP sous-jacent.

Débogage des routes

Comme votre application se développe, vous aurez éventuellement beaucoup de routes. Symfony inclut quelques commandes pour vous aider à déboguer les problèmes de routage. Tout d’abord, la commande debug:router liste toutes les routes de votre application dans le même ordre que celui dans lequel Symfony les évalue :

php bin/console debug:router

----------------  -------  -------  -----  --------------------------------------------
Name              Method   Scheme   Host   Path
----------------  -------  -------  -----  --------------------------------------------
homepage          ANY      ANY      ANY    /
contact           GET      ANY      ANY    /contact
contact_process   POST     ANY      ANY    /contact
article_show      ANY      ANY      ANY    /articles/{_locale}/{year}/{title}.{_format}
blog              ANY      ANY      ANY    /blog/{page}
blog_show         ANY      ANY      ANY    /blog/{slug}
----------------  -------  -------  -----  --------------------------------------------

Passez le nom (ou une partie du nom) d’une route à cet argument pour imprimer les détails de la route :

php bin/console debug:router app_lucky_number

+-------------+---------------------------------------------------------+
| Property    | Value                                                   |
+-------------+---------------------------------------------------------+
| Route Name  | app_lucky_number                                        |
| Path        | /lucky/number/{max}                                     |
| ...         | ...                                                     |
| Options     | compiler_class: Symfony\Component\Routing\RouteCompiler |
|             | utf8: true                                              |
+-------------+---------------------------------------------------------+

L’autre commande s’appelle router:match et montre quelle route correspondra à l’URL donnée. C’est utile pour découvrir pourquoi une certaine URL n’exécute pas l’action de contrôleur que vous attendez :

php bin/console router:match /lucky/number/8

  [OK] Route "app_lucky_number" matches

Paramètres des routes

Les exemples précédents ont défini des routes où l’URL ne change jamais (par exemple /blog). Cependant, il est courant de définir des routes où certaines parties sont variables. Par exemple, l’URL pour afficher un article de blog inclura probablement le titre ou le slug (par exemple /blog/my-first-post ou /blog/all-about-symfony).

Dans les routes Symfony, les parties variables sont enveloppées dans { … } et elles doivent avoir un nom unique. Par exemple, la route permettant d’afficher le contenu des articles de blog est définie comme suit : /blog/{slug} :

// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class BlogController extends AbstractController
{
    // ...

    /**
     * @Route("/blog/{slug}", name="blog_show")
     */
    public function show(string $slug): Response
    {
        // $slug will equal the dynamic part of the URL
        // e.g. at /blog/yay-routing, then $slug='yay-routing'

        // ...
    }
}

Le nom de la partie variable ({slug} dans cet exemple) est utilisé pour créer une variable PHP où le contenu de la route est stocké et transmis au contrôleur. Si un utilisateur visite l’URL /blog/my-first-post, Symfony exécute la méthode show() dans la classe BlogController et passe un argument $slug = ‘my-first-post’ à la méthode show().

Les routes peuvent définir un nombre quelconque de paramètres, mais chacun d’entre eux ne peut être utilisé qu’une seule fois sur chaque route (par exemple, /blog/posts-about-{category}/page/{pageNumber}).

Validation des paramètres

Imaginez que votre application possède une route blog_show (URL : /blog/{slug}) et une route blog_list (URL : /blog/{page}). Étant donné que les paramètres de route acceptent n’importe quelle valeur, il n’y a aucun moyen de différencier les deux routes.

Si l’utilisateur demande /blog/my-first-post, les deux routes correspondront et Symfony utilisera la route qui a été définie en premier. Pour résoudre ce problème, ajoutez une validation au paramètre {page} en utilisant l’option requirements :

// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class BlogController extends AbstractController
{
    /**
     * @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"})
     */
    public function list(int $page): Response
    {
        // ...
    }

    /**
     * @Route("/blog/{slug}", name="blog_show")
     */
    public function show(string $slug): Response
    {
        // ...
    }
}

L’option requirements définit les expressions régulières PHP auxquelles les paramètres de l’itinéraire doivent correspondre pour que l’itinéraire tout entier corresponde. Dans cet exemple, \d+ est une expression régulière qui correspond à un chiffre de n’importe quelle longueur. Maintenant :

URL	Route	Parameters
/blog/2	blog_list	$page = 2
/blog/my-first-post	blog_show	$slug = my-first-post

Les exigences de route (et les chemins de route aussi) peuvent inclure des paramètres de configuration, ce qui est utile pour définir des expressions régulières complexes une fois et les réutiliser dans plusieurs routes.

Les paramètres prennent également en charge les propriétés PCRE Unicode, qui sont des séquences d’échappement correspondant à des types de caractères génériques. Par exemple, \p{Lu} correspond à tout caractère majuscule dans n’importe quelle langue, \p{Greek} correspond à tout caractère grec, etc.

Si vous préférez, les exigences peuvent être soulignées dans chaque paramètre en utilisant la syntaxe {nom_du_paramètre}. Cette fonctionnalité rend la configuration plus concise, mais elle peut diminuer la lisibilité de la route lorsque les exigences sont complexes :

// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class BlogController extends AbstractController
{
    /**
     * @Route("/blog/{page<\d+>}", name="blog_list")
     */
    public function list(int $page): Response
    {
        // ...
    }
}