Pequeño howto sobre el problema del orden en la carga de rutas en Symfony. Me costó un poco encontrar la solución, así que aquí me hago eco del problema con la solución.
El problema consiste en que varias rutas pueden entrar en conflicto por el orden de la carga de forma automática. Esto ocurre cuando hay rutas dinámicas, en las que en la ruta se usan valores dinámicos, para después hacer algo en los controladores.
Problema
Es más fácil leer el código fuente que explicarlo, un controlador conflictivo podría ser:
<?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("/{_locale}/{urlKey}", name="default1")
*/
public function page($urlKey): Response
{
return $this->render('default/index.html.twig', [
'controller_name' => 'DefaultController',
]);
}
/**
* @Route("/user/{username}", name="default2")
*/
public function user($username): Response
{
return $this->render('default/index.html.twig', [
'controller_name' => 'DefaultController',
]);
}
}
Podemos entonces listar las rutas disponibles con el comando siguiente:
php bin/console debug:router
Un resultado podría ser como el siguiente:
El orden en el que aparecen listadas las rutas, es el orden en que se trata de casar una ruta. En el caso de que case la primera, ya no sigue en las siguientes rutas. Si vemos las dos primeras rutas, tenemos conflicto porque por ejemplo la ruta:
/user/nombre-de-usuario
Casará con la ruta default1, aplicando:
- _locale = user
- urlKey = nombre-de-usuario
Otro problema que tenemos es que nunca llegará a aplicar las rutas que empiezan por /api.
Solución
Una solución algo rebuscada es en los mismos controladores, hacer forwarding a siguientes controladores para el caso de que algo no funcione bien. No quedará muy limpio, ni muy mantenibles los fuentes porque no tendrá mucho sentido cargar unas rutas de controladores, dentro de otras rutas.
Otra solución es que, en versiones anterior a Symfony 5 podíamos ordenar los controladores alfabéticamente o usando el fichero de rutas routes.yaml. Dentro de los controladores podemos también ordenar de arriba a abajo el orden de aplicación. O cambiar las rutas amigables poniendo prefijos que no casen con varias rutas.
A partir de la versión 5 tenemos un campo nuevo de configuración en las anotaciones, llamado priority, con el que configuramos el orden de aplicación. Por defecto priority tiene el valor 0, con lo que una solución para que no falle el controlador anterior podría ser aplicar algo como lo siguiente:
<?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("/{_locale}/{urlKey}", name="default1", priority=-2)
*/
public function page($urlKey): Response
{
return $this->render('default/index.html.twig', [
'controller_name' => 'DefaultController',
]);
}
/**
* @Route("/user/{username}", name="default2", priority=-1)
*/
public function user($username): Response
{
return $this->render('default/index.html.twig', [
'controller_name' => 'DefaultController',
]);
}
}
Con esta configuración quedará la carga así:
Ahora se tratará de ejecutar primero las rutas de la API, luego errores, las de /user y finalmente las rutas que hacen uso del _locale. Si has llegado aquí, espero haberte podido ayudar ya que te habrás encontrado con el mismo problema ?
Sólo queda remitirte a la documentación oficial sobre esto:
https://symfony.com/doc/current/routing.html#priority-parameter