#[Route] — l'attribut PHP
#[Route] est IS_REPEATABLE sur classe et méthode — ses paramètres couvrent path, methods, requirements, defaults, host, condition, priority.
L'attribut #[Route] est le point d'entrée universel du composant Routing. Il porte
le flag IS_REPEATABLE — tu peux en empiler plusieurs sur la même méthode pour déclarer
plusieurs chemins — et il fonctionne aussi bien sur une classe (préfixe) que sur une méthode (route finale).
Tous les paramètres sont optionnels sauf
path (ou son alias positionnel).
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] class Route { public function __construct( public string|array|null $path = null, // chemin ou tableau de chemins public ?string $name = null, // nom de la route public array $requirements = [], // regex par paramètre public array $options = [], // options bas niveau public array $defaults = [], // valeurs par défaut public ?string $host = null, // contrainte de domaine array|string $methods = [], // GET, POST… array|string $schemes = [], // http, https public ?string $condition = null, // expression ExpressionLanguage public ?int $priority = null, // ordre de matching ?string $locale = null, // → defaults['_locale'] ?string $format = null, // → defaults['_format'] ?bool $utf8 = null, // → options['utf8'] ?bool $stateless = null, // → defaults['_stateless'] string|array|null $env = null, // filtre par environnement ) {} }
Sucre syntaxique — locale, format, stateless
Ces trois paramètres sont des raccourcis : à la compilation, Symfony les déplace automatiquement dans defaults ou options.
| Paramètre | Traduit en | Exemple |
|---|---|---|
locale: 'fr' | defaults['_locale'] = 'fr' | Force la locale sur cette route |
format: 'json' | defaults['_format'] = 'json' | Format de réponse par défaut |
utf8: true | options['utf8'] = true | Active le mode UTF-8 sur le pattern regex |
stateless: true | defaults['_stateless'] = true | Désactive la session sur cette route |
Prefix de classe — préfixer chemin et nom
Placer #[Route] sur la classe préfixe automatiquement toutes les routes des méthodes :
#[Route('/api', name: 'api_')] class UserController { #[Route('/users', name: 'users_list', methods: 'GET')] public function list(): Response { /* ... */ } // → route finale : nom = 'api_users_list', chemin = '/api/users' #[Route('/users/{id}', name: 'users_show', methods: 'GET')] public function show(int $id): Response { /* ... */ } // → route finale : nom = 'api_users_show', chemin = '/api/users/{id}' }
Route multipaths — IS_REPEATABLE
La même action peut écouter sur plusieurs chemins grâce au flag IS_REPEATABLE :
#[Route('/home', name: 'homepage')] #[Route('/', name: 'root')] public function index(): Response { // Répond à /home et à / }
env : restreindre une route à certains environnements évite de l'exposer en production.
#[Route('/debug/profiler', env: 'dev')] — la route n'existe tout simplement pas en prod.
priority (valeur plus haute = priorité plus haute).
Une route sans priority vaut 0.
Requirements et paramètres avancés
Les requirements valident les segments d'URL — les paramètres optionnels et les defaults contrôlent ce qui est requis pour matcher.
Les requirements sont des expressions régulières qui s'appliquent aux segments dynamiques
de l'URL. Sans requirement, un paramètre accepte n'importe quelle valeur sauf le slash /.
Avec un requirement, seules les URL qui satisfont la regex sont matchées.
La syntaxe longue via le paramètre
requirements de l'attribut, et la syntaxe inline
directement dans le chemin.
// Syntaxe longue — requirements séparés #[Route('/user/{id}', requirements: ['id' => '\d+'])] // Syntaxe inline — pattern dans le chemin avec {nom} #[Route('/user/{id<\d+>}')] // Plusieurs requirements #[Route('/blog/{year}/{slug}', requirements: [ 'year' => '\d{4}', 'slug' => '[\w-]+', ])]
Patterns courants
| Pattern | Correspond à | Exemple |
|---|---|---|
\d+ | Un ou plusieurs chiffres | /user/42 |
\d{4} | Exactement 4 chiffres | /blog/2024 |
[\w-]+ | Lettres, chiffres, tirets | /article/mon-titre |
[a-z]{2} | Code langue ISO 2 lettres | /fr, /en |
1|2|3 | Valeurs fixes | /api/v1, /api/v2 |
Paramètres optionnels
Trois façons d'indiquer qu'un paramètre est facultatif :
// 1. Point d'interrogation sans valeur par défaut → null #[Route('/blog/{page?}')] // 2. Point d'interrogation avec valeur par défaut inline #[Route('/blog/{page?1}')] // 3. Via defaults (syntaxe explicite) #[Route('/blog/{page}', defaults: ['page' => 1])]
Ces variables ne viennent pas du chemin — elles enrichissent le tableau de match ou contrôlent le comportement du framework.
| Variable | Rôle |
|---|---|
_locale | Locale active sur cette route |
_format | Format de réponse (html, json, xml…) |
_stateless | Si true, désactive la session pour cette requête |
_controller | Contrôleur à invoquer — défini via defaults si pas d'attribut |
_route | Nom de la route matchée (lu en lecture seule) |
_canonical_route | Nom de la route parente pour les routes localisées |
Requirements sur les enums PHP
Pour restreindre un paramètre aux cas d'un enum backed, on liste les valeurs :
enum Status: string { case Active = 'active'; case Archived = 'archived'; case Draft = 'draft'; } #[Route('/posts/{status}', requirements: ['status' => 'active|archived|draft'])] // Encore mieux : générer dynamiquement depuis l'enum #[Route('/posts/{status}', requirements: [ 'status' => implode('|', array_column(Status::cases(), 'value')) ])]
"\d+" = \\d+).
Avec les heredocs ou les requirements en tableau inline d'attribut, un seul backslash suffit.
Saisis une URL et observe quelles routes la matchent.
generate() — générer des URLs
generate() transforme un nom de route + paramètres en URL — les constantes de référence contrôlent si l'URL est absolue, relative, ou de réseau.
Générer une URL à la main c'est coller des chaînes — fragile, non refactorable. La méthode
generate() de UrlGeneratorInterface résout ça : tu donnes un nom de route
et un tableau de paramètres, elle retourne l'URL correcte selon la configuration actuelle.
generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH): string
Les 4 types de référence
| Constante | Valeur | Format retourné | Exemple |
|---|---|---|---|
ABSOLUTE_PATH |
1 | Chemin absolu (défaut) | /blog/mon-article |
ABSOLUTE_URL |
0 | URL complète avec schéma et hôte | https://example.com/blog/mon-article |
RELATIVE_PATH |
2 | Chemin relatif depuis l'URL courante | ../../blog/mon-article |
NETWORK_PATH |
3 | Sans schéma, avec hôte | //example.com/blog/mon-article |
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; // Injection dans un service public function __construct(private readonly UrlGeneratorInterface $urlGenerator) {} // Chemin absolu (par défaut) $url = $this->urlGenerator->generate('blog_show', ['slug' => 'mon-article']); // → /blog/mon-article // URL absolue — pour les emails, les webhooks $url = $this->urlGenerator->generate( 'blog_show', ['slug' => 'mon-article'], UrlGeneratorInterface::ABSOLUTE_URL ); // → https://example.com/blog/mon-article
Paramètres supplémentaires → query string
Tout paramètre passé à generate() qui ne correspond pas à un segment de la route
est automatiquement ajouté en query string :
$url = $this->urlGenerator->generate('blog_list', [ 'page' => 3, 'category' => 'php', 'sort' => 'date', ]); // → /blog?page=3&category=php&sort=date
Paramètres spéciaux
// _fragment → ancre # $url = $this->urlGenerator->generate('article_show', [ 'slug' => 'intro-symfony', '_fragment' => 'section-2', ]); // → /article/intro-symfony#section-2 // _query → fusion avec les paramètres existants $url = $this->urlGenerator->generate('search', [ '_query' => ['q' => 'symfony', 'page' => 1], ]); // → /search?q=symfony&page=1
Exceptions
| Exception | Cause |
|---|---|
RouteNotFoundException |
Le nom de route passé n'existe pas dans la RouteCollection |
MissingMandatoryParametersException |
Un paramètre sans valeur par défaut est absent de l'appel |
InvalidParameterException |
La valeur d'un paramètre ne satisfait pas son requirement |
RequestContext::setHost(), setScheme(), setHttpPort()
— sinon Symfony génère une URL avec localhost.
Routing dans le contrôleur
Dans les contrôleurs Symfony, #[Route] décrit la route, generateUrl() génère les URLs, et $request->attributes contient les paramètres matchés.
Dans un contrôleur Symfony, le routing s'intègre naturellement : l'attribut #[Route]
décrit la route, les paramètres du chemin arrivent directement comme arguments de la méthode,
et generateUrl() encapsule l'accès au générateur.
#[Route] sur une méthode de contrôleur
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; #[Route('/blog', name: 'blog_')] final class BlogController extends AbstractController { #[Route('/', name: 'index', methods: 'GET')] public function index(): Response { // route blog_index → GET /blog/ return $this->render('blog/index.html.twig'); } #[Route('/{slug}', name: 'show', methods: 'GET', requirements: ['slug' => '[\w-]+'])] public function show(string $slug): Response { // Symfony injecte $slug depuis les attributs de la requête // route blog_show → GET /blog/{slug} return $this->render('blog/show.html.twig', ['slug' => $slug]); } }
generateUrl() — raccourci de AbstractController
// generateUrl() enveloppe UrlGeneratorInterface::generate() $url = $this->generateUrl('blog_show', ['slug' => 'mon-article']); // → /blog/mon-article // URL absolue use Symfony\Component\Routing\Generator\UrlGeneratorInterface; $url = $this->generateUrl( 'blog_show', ['slug' => 'mon-article'], UrlGeneratorInterface::ABSOLUTE_URL ); // → https://example.com/blog/mon-article
$request->attributes — lire les paramètres matchés
use Symfony\Component\HttpFoundation\Request; public function show(Request $request, string $slug): Response { // Paramètres disponibles dans $request->attributes $routeName = $request->attributes->get('_route'); // 'blog_show' $routeParams = $request->attributes->get('_route_params'); // ['slug' => '...'] $controller = $request->attributes->get('_controller'); // 'App\...\BlogController::show' $slugValue = $request->attributes->get('slug'); // idem que $slug }
redirect() et redirectToRoute()
// Redirection vers une URL externe ou interne return $this->redirect('https://example.com'); return $this->redirect($this->generateUrl('homepage'), 301); // Redirection via nom de route — le plus courant return $this->redirectToRoute('blog_index'); return $this->redirectToRoute('blog_show', ['slug' => 'nouveaute'], 302);
Dans un service hors contrôleur, injecte directement l'interface via le constructeur. Ne pas hériter d'
AbstractController depuis un service — c'est réservé aux contrôleurs.
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; final class NotificationMailer { public function __construct( private readonly UrlGeneratorInterface $urlGenerator ) {} public function sendConfirmation(string $token): void { $link = $this->urlGenerator->generate( 'confirm_email', ['token' => $token], UrlGeneratorInterface::ABSOLUTE_URL ); } }
{slug}
→ argument string $slug. Le type PHP est automatiquement casté (int pour \d+).
Condition et routes dynamiques
L'option condition filtre les routes avec une expression ExpressionLanguage — les routes s'appliquent seulement si l'expression est vraie.
Le paramètre condition d'une route accepte une expression ExpressionLanguage.
Contrairement aux requirements (qui portent sur la forme de l'URL), la condition s'évalue à
l'exécution en interrogeant la requête ou le contexte — un filtre dynamique sur le monde réel.
request — l'objet HttpFoundation\Request completcontext — le RequestContext (hôte, schéma, méthode, baseUrl)params — les paramètres déjà extraits par le matching de l'URL
// Conditionner sur un header HTTP #[Route('/api/data', condition: "request.headers.get('Accept') === 'application/json'" )] // Conditionner sur le sous-domaine #[Route('/dashboard', condition: "context.getHost() matches '/^admin\\..*$/'" )] // Conditionner sur HTTPS #[Route('/checkout', condition: "context.getScheme() === 'https'" )] // Combiner plusieurs conditions #[Route('/api/beta', condition: "request.headers.has('X-Beta') and context.getScheme() === 'https'" )]
Condition vs priority — deux mécanismes distincts
priority | condition | |
|---|---|---|
| Évaluation | À la compilation (ordre statique) | À l'exécution (par requête) |
| Coût | Nul — déterminé une fois | Faible — exécuté à chaque requête |
| Porte sur | L'ordre de test des routes | Le résultat du test (oui/non) |
| Usage | Routes génériques vs spécifiques sur même chemin | Variantes d'une route selon le contexte |
Cas d'usage typiques
Combine le filtre
env (qui supprime la route en prod) avec une condition sur l'IP
pour une protection supplémentaire :
#[Route('/debug/mail', env: 'dev', condition: "request.getClientIp() === '127.0.0.1'" )]
Répondre différemment aux bots sans changer l'URL :
#[Route('/article/{slug}', name: 'article_bot', priority: 1, condition: "request.headers.get('User-Agent') matches '/Googlebot|Bingbot/i'" )] #[Route('/article/{slug}', name: 'article_show')]
condition: "request.cookies.has('ab_variant_b')"
ResourceNotFoundException. La condition ne "saute pas" la route — elle empêche son
match, ce qui peut mener à un 404 si c'était la seule candidate.
RouteCollection et loaders
RouteCollection accumule les routes issues de toutes les sources — les loaders décrivent comment les charger.
Derrière les attributs et les fichiers YAML se cache une structure commune : la
RouteCollection. C'est elle qui est compilée en cache PHP. Les loaders sont les
adaptateurs qui lisent vos sources et alimentent cette collection.
Conteneur de routes indexées par nom. Toutes les sources (attributs, YAML, PHP, closures) finissent dans une seule
RouteCollection fusionnée avant compilation.
API de RouteCollection
use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; $routes = new RouteCollection(); // Ajouter une route $routes->add('blog_show', new Route('/blog/{slug}', [ '_controller' => 'App\Controller\BlogController::show', ])); // Préfixer tous les chemins de la collection $routes->addPrefix('/api'); // Chaque route /blog/{slug} devient /api/blog/{slug} // Préfixer tous les noms $routes->addNamePrefix('api_'); // 'blog_show' → 'api_blog_show' // Restreindre toutes les routes à un host $routes->setHost('api.{domain}', [], ['domain' => '\w+']); // Fusionner deux collections $main->addCollection($routes);
routes.php — RoutingConfigurator
Symfony fournit une API fluide pour déclarer les routes en PHP, plus souple que YAML :
// config/routes.php use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; return function (RoutingConfigurator $routes): void { $routes->add('homepage', '/') ->controller([HomeController::class, 'index']) ->methods(['GET']); $routes->add('blog_show', '/blog/{slug}') ->controller([BlogController::class, 'show']) ->requirements(['slug' => '[\w-]+']) ->defaults(['_format' => 'html']) ->methods(['GET']) ->host('example.com') ->priority(10); // Importer un autre fichier ou un répertoire de contrôleurs $routes->import('../src/Controller/', 'attribute') ->prefix('/app') ->namePrefix('app_'); };
Loaders disponibles
| Loader | Source | Type d'import |
|---|---|---|
AttributeDirectoryLoader |
Répertoire de classes PHP | 'attribute' |
AttributeFileLoader |
Fichier PHP unique | 'attribute' |
YamlFileLoader |
Fichier .yaml / .yml |
'yaml' |
PhpFileLoader |
Fichier routes.php |
'php' |
GlobFileLoader |
Pattern glob (*.yaml) |
'glob' |
ClosureLoader |
Closure retournant une RouteCollection |
'closure' |
CompiledRoute avec staticPrefix, regex, tokens
et pathVariables. Ce code PHP compilé est stocké dans var/cache/ et
rechargé à chaque requête sans relire les fichiers de config.
addCollection()
ne préservent pas automatiquement la priorité. Si deux routes ont le même chemin,
c'est la dernière ajoutée qui gagne — sauf si tu utilises explicitement priority.
Routes avec host
L'option host limite une route à un domaine ou un sous-domaine — utile pour les multi-tenants ou les sous-domaines d'API.
L'option host ajoute une contrainte de domaine à la route — elle ne matche que
si la requête arrive sur le bon hôte. C'est le mécanisme de base pour les architectures
multi-tenants, les sous-domaines d'API ou les domaines par locale.
Domaine fixe
// Cette route ne répond qu'à api.example.com #[Route('/users', name: 'api_users', host: 'api.example.com')] public function users(): Response { /* ... */ } // Sur www.example.com, cette route retourne une ResourceNotFoundException
Variable dans le host
Le domaine peut contenir des paramètres, exactement comme un chemin :
// Sous-domaine dynamique : alice.myapp.com, bob.myapp.com #[Route('/dashboard', name: 'tenant_dashboard', host: '{tenant}.myapp.com', requirements: ['tenant' => '[a-z][a-z0-9-]+'], )] public function dashboard(string $tenant): Response { // $tenant vaut 'alice' si la requête vient de alice.myapp.com }
setHost() sur RouteCollection
Pour appliquer une contrainte de host à un groupe entier de routes en routes.php :
return function (RoutingConfigurator $routes): void { $apiRoutes = $routes->import('../src/Controller/Api/', 'attribute'); $apiRoutes->host('api.{domain}') ->requirements(['domain' => 'example\.com|example\.fr']) ->prefix('/v1') ->namePrefix('api_v1_'); };
RequestContext pour la génération d'URL absolues
Quand on génère une URL avec une contrainte de host, Symfony a besoin du contexte courant. En dehors d'une requête HTTP :
use Symfony\Component\Routing\RequestContext; $context = new RequestContext(); $context->setHost('alice.myapp.com'); $context->setScheme('https'); $context->setHttpsPort(443); $urlGenerator->setContext($context); $url = $urlGenerator->generate( 'tenant_dashboard', ['tenant' => 'alice'], UrlGeneratorInterface::ABSOLUTE_URL ); // → https://alice.myapp.com/dashboard
ResourceNotFoundException.
SERVER_NAME dans ta configuration web. Utilise http://alice.localhost
ou configure un wildcard dans /etc/hosts pour tester les routes à sous-domaine.
Routes localisées
Les routes localisées permettent d'avoir des paths différents par locale — Symfony génère automatiquement une route par locale.
Les routes localisées permettent d'avoir une URL différente par langue tout en gardant un seul
contrôleur. Symfony crée automatiquement une route par locale et maintient un lien entre elles
via _canonical_route.
Le paramètre
path accepte un tableau associatif locale → chemin.
Symfony génère autant de routes que de locales.
#[Route( path: ['en' => '/blog/{slug}', 'fr' => '/article/{slug}'], name: 'blog_post', methods: 'GET', requirements: ['slug' => '[\w-]+'], )] public function show(string $slug): Response { /* ... */ }
Symfony génère deux routes distinctes :
| Nom de route | Chemin | _canonical_route |
|---|---|---|
blog_post.en | /blog/{slug} | blog_post |
blog_post.fr | /article/{slug} | blog_post |
Génération d'URL avec _locale
// Générer la version française $url = $this->generateUrl('blog_post', [ 'slug' => 'mon-article', '_locale' => 'fr', ]); // → /article/mon-article // Générer la version anglaise $url = $this->generateUrl('blog_post', [ 'slug' => 'my-article', '_locale' => 'en', ]); // → /blog/my-article // Cibler directement une variante localisée $url = $this->generateUrl('blog_post.fr', ['slug' => 'mon-article']); // → /article/mon-article
_canonical_route — SEO et navigation
_canonical_route est automatiquement défini à 'blog_post' sur les deux variantes.
Il sert à générer les balises hreflang :
// Dans un contrôleur ou un template : générer les alternatives linguistiques $canonicalRoute = $request->attributes->get('_canonical_route'); // 'blog_post' $currentParams = $request->attributes->get('_route_params'); // ['slug' => '...'] // <link rel="alternate" hreflang="fr" href="..." /> foreach (['en', 'fr'] as $locale) { $href = $this->generateUrl($canonicalRoute, array_merge( $currentParams, ['_locale' => $locale] ), UrlGeneratorInterface::ABSOLUTE_URL); }
Routes localisées en YAML
# config/routes.yaml blog_post: path: en: /blog/{slug} fr: /article/{slug} controller: App\Controller\BlogController::show methods: GET requirements: slug: '[\w-]+'
path('blog_post', {'slug': ..., '_locale': app.locale}) plutôt que
path('blog_post.fr', ...) — le nom canonique s'adapte automatiquement à la locale
active, tandis que la variante suffixée la force.
/{_locale}/blog/{slug} pour une URL
/en/blog/my-article. Les routes localisées font l'inverse — un chemin
totalement différent par langue. Les deux approches sont légitimes selon le besoin SEO.
match() et exceptions
Le matching transforme une URL en tableau de paramètres — les exceptions signalent les cas d'erreur avec précision.
Le matching est l'opération inverse de la génération : une URL entre, un tableau de
paramètres sort. UrlMatcherInterface::match() est le cœur du processus, et ses
exceptions décrivent précisément pourquoi une URL n'a pas matché.
match(string $pathinfo): array — retourne un tableau associatif ou lance une exception.
use Symfony\Component\Routing\Matcher\UrlMatcher; use Symfony\Component\Routing\RequestContext; $context = new RequestContext(); $matcher = new UrlMatcher($routes, $context); // Matcher un chemin $params = $matcher->match('/blog/mon-article'); // Résultat typique : // [ // '_route' => 'blog_show', // '_controller' => 'App\Controller\BlogController::show', // 'slug' => 'mon-article', // '_locale' => 'fr', // si route localisée // '_canonical_route' => 'blog_show', // si variante localisée // ]
matchRequest() — avec la méthode HTTP
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Matcher\RequestMatcherInterface; // matchRequest prend en compte Request::getMethod() $params = $matcher->matchRequest($request);
Les trois exceptions
| Exception | HTTP | Cause | Infos supplémentaires |
|---|---|---|---|
ResourceNotFoundException |
404 | Aucune route ne correspond au chemin | — |
MethodNotAllowedException |
405 | Le chemin matche mais pas la méthode HTTP | getAllowedMethods(): string[] |
RouteNotFoundException |
— | Nom de route inexistant dans generate() |
— |
use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Symfony\Component\Routing\Exception\MethodNotAllowedException; try { $params = $matcher->match($path); } catch (MethodNotAllowedException $e) { // Chemin trouvé mais mauvaise méthode $allowed = $e->getAllowedMethods(); // ['GET', 'HEAD'] // → répondre 405 avec header Allow: GET, HEAD } catch (ResourceNotFoundException $e) { // Aucune route ne correspond // → répondre 404 }
Déboguer avec debug:router
# Lister toutes les routes php bin/console debug:router # Chercher une route par nom php bin/console debug:router blog_show # Tester le matching d'une URL php bin/console router:match /blog/mon-article # Tester avec une méthode spécifique php bin/console router:match /blog/mon-article --method=POST
router:match est ton meilleur outil de diagnostic — il te dit exactement
quelle route a matché, quels paramètres ont été extraits, et si une route est rejetée pour cause
de méthode non autorisée.
Saisis un chemin, sélectionne une méthode HTTP, et observe le résultat du matching.
Synthèse — guide de choix et anti-patterns
Le guide de choix et les anti-patterns du Routing Symfony.
Dix sections en arrière, tu avais un attribut #[Route]. Maintenant tu as le
tableau complet : requirements, génération, conditions, collections, hosts, locales et matching.
Voici le guide de décision pour choisir le bon outil.
Quand utiliser quoi
| Besoin | Outil | Pourquoi |
|---|---|---|
| Contraindre la forme d'un segment d'URL | requirements |
Évalué à la compilation, coût nul à l'exécution |
| Filtrer selon la requête (header, schéma, IP) | condition |
Accès à request et context via ExpressionLanguage |
| Deux routes sur même chemin, une plus spécifique | priority |
Ordre de test statique, sans coût d'exécution |
| Restreindre à un sous-domaine / domaine | host |
Contrainte sur l'hôte de la requête, pas le chemin |
| URLs différentes par langue | Routes localisées (path: {en: ..., fr: ...}) |
Symfony génère une route par locale, _canonical_route les lie |
| Grouper des routes (préfixe, host, nom) | RouteCollection ou import avec prefix/host/namePrefix |
Évite la répétition, modifiable en un seul endroit |
Variables spéciales — récapitulatif
| Variable | Direction | Rôle |
|---|---|---|
_route | match() → out | Nom de la route matchée |
_controller | defaults → in / match() → out | Contrôleur à invoquer |
_locale | defaults ou path / generate() in | Locale active |
_format | defaults | Format de réponse par défaut |
_stateless | defaults | Désactive la session |
_canonical_route | match() → out | Nom canonique des routes localisées |
_fragment | generate() in | Ancre #... dans l'URL générée |
_query | generate() in | Fusion query string dans l'URL générée |
Exceptions — récapitulatif
| Exception | Contexte | HTTP |
|---|---|---|
ResourceNotFoundException |
match() — aucune route ne correspond |
404 |
MethodNotAllowedException |
match() — chemin OK mais méthode interdite |
405 |
RouteNotFoundException |
generate() — nom de route inexistant |
— |
MissingMandatoryParametersException |
generate() — paramètre requis absent |
— |
InvalidParameterException |
generate() — valeur ne satisfait pas le requirement |
— |
4 anti-patterns à éviter
{id} sans requirement \d+ matche aussi /user/delete ou
/user/export — des mots qui ne sont pas des IDs. Toujours contraindre les
paramètres numériques.
Utiliser
condition: "true" ou une condition statique pour forcer l'ordre de matching
au lieu de priority. La condition est évaluée à chaque requête — priority
est déterminé une fois à la compilation.
Dans une commande console ou un worker de queue, le contexte n'est pas automatiquement alimenté. Oublier de configurer
RequestContext::setHost() produit des URLs avec
http://localhost/....
Générer les liens
hreflang depuis _route (qui vaut blog_post.fr)
au lieu de _canonical_route (qui vaut blog_post) produit des URLs
hreflang incorrectes — blog_post.fr n'existe pas avec _locale=en.