Symfony: priorizando la carga de rutas

2021-08-07 - Categorías: PHP / Symfony
Herramientas Symfony

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:

Symfony, listando rutas..

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í:

Symfony ordenando rutas
Symfony, ordenando rutas..

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

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

 

© 2021 JnjSite.com - MIT license

Sitio hecho con WordPress, diseño y programación del tema por Jnj.