Routing en Symfony

Anteriormente hemos visto como crear una primera página en Symfony, pero en esta entrada vamos a entrar en la materia del enrutamiento. Aprenderemos los conceptos básicos del routing en Symfony, establecer rutas por defecto, restringir algunas URLs y métodos HTTP que podemos pasar a una misma ruta.

Lo principal de las rutas es conocer que podemos crearlas de 4 formatos distintos: XML, YAML, PHP y la que más se suele usar, anotaciones.

Una ruta consta básicamente de dos partes: un path y un array de parámetros.

Si ya conocíamos que las rutas se colocaban justo delante del método de la clase, podemos prefijar rutas anteponiéndolas con la misma anotación en la clase. Por ejemplo, en este caso, hemos colocado una ruta prefijada llamada «/contactos» para que todo lo demás esté restringido primero a pasar por esta dirección:

/**
* @Route("/contactos")
*/
class ContactosController extends Controller
{
    /**
     * @Route("/{page}")
     */
    public function indexAction($page)
    {
      // la URL de este controlador es "/contactos/{page}"
      // donde {page} puede ser cualquier cosa
      return new Response($page);
    }
}

Establecer valores por defecto en las rutas

Se pueden añadir placeholders a las rutas que les indiquemos la necesidad de acceder por ellas. Si ya tenemos una ruta de /contactos pero queremos obligar al usuario que acceda a otro apartado de los mismos (bien sea una página, una categoría, un contacto en concreto…) utilizaremos la clave defaults dentro de nuestra ruta:

/**
* @Route("/contactos")
*/
class ContactosController extends Controller
{
    /**
     * @Route("/{page}", defaults={"page"=1})
     */
    public function indexAction($page)
    {
      // la URL de este controlador es "/contactos/{page}"
      // donde {page} puede ser cualquier cosa, por defecto es "1"
      // pero no funcionaría la ruta "/contactos"
      return new Response($page);
    }
}

A partir de ahora, el usuario no puede acceder simplemente a la URL /contactos, sino que requiere de un segundo parámetro. Este parámetro llamado «page» se le ha asignado, por defecto, el número «1».

Sin embargo, si el usuario accede a la URL /contactos/2, /contactos/3, /contactos/283492873, o incluso a /contactos/foo… siempre se se va a poder seguir accediendo a las acciones del método indexAction.

Restricción a URLs

Pero ahora tenemos el problema de que poniendo cualquier cosa, el usuario puede acceder a URLs de toda índole por /contactos/… Symfony nos permite restringir el acceso a rutas mediante parámetros requirements.

Un requirement no es más que otro parámetro de Route al que le podemos especificar, mediante expresiones regulares, el formato que ha de tener la variable a la que se le asigne.

Supongamos este ejemplo con dos métodos muy parecidos pero que se deben ejecutar dependiendo de la ruta solicitada:

/**
* @Route("/contactos")
*/
class ContactosController extends Controller
{
    /**
     * @Route("/{page}",
     * defaults={"page": 1})
     */
    public function indexAction($page)
    {
      return new Response('página: '.$page);
    }
    /**
    * @Route("/{slug}")
    */
    public function showAction($slug)
    {
      return new Response('slug: '.$slug);
    }
}

Si probamos la ruta /contactos/1 el resultado que se nos devuelve es un texto «página: 1», lo cual parece correcto; pero si queremos poner la ruta /contactos/trabajo, lo correcto sería que apareciera el mensaje «slug: trabajo». Pero eso no es lo que nos devuelve. Symfony no reconoce en el controlador a qué método en concreto debe acceder. Por eso, siempre va a acceder al primer método indexAction, devolviendo el mensaje «página: [lo que hayamos puesto]«.

Solución: restringir a los números enteros que accedan al método index y al resto al método show. Y todo ello con requirements en la notación de las rutas:

/**
* @Route("/contactos")
*/
class ContactosController extends Controller
{
    /**
     * @Route("/{page}",
     * defaults={"page": 1},
     * requirements={"page": "\d+"})
     */
    public function indexAction($page)
    {
      return new Response('página: '.$page);
    }
    /**
    * @Route("/{slug}")
    */
    public function showAction($slug)
    {
      return new Response('slug: '.$slug);
    }
}

Ahora, la variable page recibe el requerimiento de que sea por lo menos, un número entero (d+) con la expresión regular que le hemos definido. Obligando de esta manera a que si el usuario introduce /contactos/[cualquier número entero] solo se le va a mostrar el mensaje «página: x». Mientras que si pone algo distinto a un número entero, aparecerá el mensaje «slug: x».

Parámetros de rutas especiales

Cada uno de estos parámetros agregan una nueva funcionalidad dentro de las rutas:

  • _controller: Determina que controlador se ejecuta.
  • _format: Establece un formato (html, rss,…).
  • _fragment: Colocado al final de la URL con # y que indica una sección de la página.
  • _locale: Establece el idioma.

Métodos HTTP

Suponiendo que tenemos un controlador que hace las funciones CRUD para la aplicación, podemos asignarle exactamente la misma ruta a cada uno de los métodos siempre y cuando los HEAD sean PUT, UPDATE, GET, DELETE…

En Symfony se especifica con el parámetro _method y nos asegura que se va a escoger el método que se requiera para la acción jugando con la misma dirección URL:

     /**
     * @Route("/contactos/{id}")
     * @Method({"GET","HEAD"})
     */
    public function showAction($id)
    {
    }

    /**
     * @Route("/contactos/{id}")
     * @Method("PUT")
     */
    public function editAction($id)
    {
    }

Arriba vemos las mismas rutas en métodos distintos, pero dependiendo de si lo vamos a mostrar o a editar, se especifica con el parámetro y el método HTTP.

Esta la actualización del repositorio para acceder al branch de routing que corresponde a esta parte del manual sobre el routing en Symfony.

Deja un comentario