Symfony 1.2, la guía definitiva

9.4. Configuración del sistema de enrutamiento

El sistema de enrutamiento se encarga de 2 tareas:

  • Interpreta las URL externas de las peticiones entrantes y las transforma en URI internas para determinar el módulo, la acción y los parámetros de la petición.
  • Transforma las URI internas utilizadas en los enlaces en URL externas (siempre que se utilicen los helpers de enlaces).

La transformación se realiza en base a una serie de reglas de enrutamiento. Todas estas reglas se almacenan en un archivo de configuración llamado routing.yml y que se encuentra en el directorio config/. El listado 9-15 muestra las reglas que incluyen por defecto todos los proyectos de Symfony.

Listado 9-15 - Las reglas de enrutamiento por defecto, en frontend/config/routing.yml

# default rules
homepage:
  url:   /
  param: { module: default, action: index }

default_symfony:
  url:   /symfony/:action/*
  param: { module: default }

default_index:
  url:   /:module
  param: { action: index }

default:
  url:   /:module/:action/*

9.4.1. Reglas y patrones

Las reglas de enrutamiento son asociaciones biyectivas entre las URL externas y las URI internas. Una regla típica está formada por:

  • Un identificador único en forma de texto, que se define por legibilidad y por rapidez, y que se puede utilizar en los helpers de enlaces
  • El patrón que debe cumplirse (en la clave url)
  • Un array de valores para los parámetros de la petición (en la clave param)

Los patrones pueden contener comodines (que se representan con un asterisco, *) y comodines con nombre (que empiezan por 2 puntos, :). Si se produce una coincidencia con un comodín con nombre, ese valor que coincide se transforma en un parámetro de la petición. Por ejemplo, la regla anterior llamada default produce coincidencias con cualquier URL del tipo /valor1/valor2, en cuyo caso se ejecutará el módulo llamado valor1 y la acción llamada valor2. Y en la regla llamada default_symfony, el valor symfony es una palabra clave y action es un comodín con nombre que se transforma en parámetro de la petición.

Nota A partir de la versión 1.1 de Symfony, los comodines con nombre pueden separarse con un guión medio o con un punto, por lo que es posible crear patrones avanzados como el siguiente:

mi_regla:
  url:   /ruta/:parametro1.:formato
  param: { module: mimodulo, action: miaccion }

Si se define la regla anterior, una URL como /ruta/12.xml produce una coincidencia con esa regla y provoca que se ejecute la acción miaccion dentro del módulo mimodulo. Además, a la acción se le pasa un parámetro llamado parametro1 con valor igual a 12 y otro parámetro llamado formato con valor xml.

Si quieres utilizar otros separadores, puedes modificar la opción segment_separators en la configuración de la factoría sfPatternRouting (ver capítulo 19).

El sistema de enrutamiento procesa el archivo routing.yml desde la primera línea hasta la última y se detiene en la primera regla que produzca una coincidencia. Por este motivo se deben añadir las reglas personalizadas antes que las reglas por defecto. Si se consideran las reglas del listado 9-16, la URL /valor/123 produce coincidencias con las dos reglas, pero como Symfony prueba primero la regla mi_regla:, y esa regla produce una coincidencia, ni siquiera se llega a probar la regla default:. De esta forma, la petición se procesa en la acción mimodulo/miaccion con el parámetro id inicializado con el valor 123 (no se procesa por tanto en la acción valor/123).

Listado 9-16 - Las reglas se procesan de principio a fin

mi_regla:
  url:   /valor/:id
  param: { module: mimodulo, action: miaccion }

# default rules
default:
  url:   /:module/:action/*

Nota No siempre que se crea una nueva acción es necesario añadir una nueva regla al sistema de enrutamiento. Si el patrón modulo/accion es útil para la nueva acción, no es necesario añadir más reglas al archivo routing.yml. Sin embargo, si se quieren personalizar las URL externas de la acción, es necesario añadir una nueva regla por encima de las reglas por defecto.

El listado 9-17 muestra el proceso de modificación del formato de la URL externa de la acción articulo/ver.

Listado 9-17 - Modificación del formato de las URL externas de la acción articulo/ver

<?php echo url_for('articulo/ver?id=123') ?>
 => /articulo/ver/id/123       // Formato por defecto

// Para cambiarlo por /articulo/123, se añade una nueva regla al
// principio del archivo routing.yml
articulo_segun_id:
  url:   /articulo/:id
  param: { module: articulo, action: ver }

El problema es que la regla articulo_segun_id del listado 9-17 rompe con el enrutamiento normal de todas las otras acciones del módulo articulo. De hecho, ahora una URL como articulo/borrar produce una coincidencia en esta regla, por lo que no se ejecuta la regla default, sino que se ejecuta la regla articulo_segun_id. Por tanto, esta URL no llama a la acción borrar, sino que llama a la acción ver con el atributo id inicializado con el valor borrar. Para evitar estos problemas, se deben definir restricciones en el patrón, de forma que la regla articulo_segun_id solo produzca coincidencias con las URL cuyo comodín id sea un número entero.

9.4.2. Restricciones en los patrones

Cuando una URL puede producir coincidencias con varias reglas diferentes, se deben refinar las reglas añadiendo restricciones o requisitos a sus patrones. Un requisito es una serie de expresiones regulares que deben cumplir los comodines para que la regla produzca una coincidencia.

Para modificar por ejemplo la regla articulo_segun_id anterior de forma que solo se aplique a las URL cuyo atributo id sea un número entero, se debe añadir una nueva línea a la regla, como muestra el listado 9-18.

Listado 9-18 - Añadiendo requisitos a las reglas de enrutamiento

articulo_segun_id:
  url:   /articulo/:id
  param: { module: articulo, action: ver }
  requirements: { id: \d+ }

Ahora, una URL como articulo/borrar nunca producirá una coincidencia con la regla articulo_segun_id, porque la cadena de texto borrar no cumple con los requisitos de la regla. Por consiguiente, el sistema de enrutamiento continua buscando posibles coincidencias con otras reglas hasta que al final la encuentra en la regla llamada default.

9.4.3. Asignando valores por defecto

Para completar las reglas, se pueden asignar valores por defecto a los comodines con nombre, incluso aunque el parámetro no esté definido. Los valores por defecto se establecen en el array param:.

Por ejemplo, la regla articulo_segun_id no se ejecuta si no se pasa el parámetro id. El listado 9-19 muestra como forzar la presencia de ese parámetro.

Listado 9-19 - Asignar un valor por defecto a un comodín

articulo_segun_id:
  url:          /articulo/:id
  param:        { module: articulo, action: ver, id: 1 }

Los parámetros por defecto no necesariamente tienen que ser comodines que se encuentran en el patrón de la regla de enrutamiento. En el listado 9-20, al parámetro display se le asigna el valor true, aunque ni siquiera forma parte de la URL.

Listado 9-20 - Asignar un valor por defecto a un parámetro de la petición

articulo_segun_id:
  url:          /articulo/:id
  param:        { module: articulo, action: ver, id: 1, display: true }

Si se mira con un poco de detenimiento, se puede observar que articulo y ver son también valores por defecto asignados a las variables module y action que no se encuentran en el patrón de la URL.

Truco Para incluir un parámetro por defecto en todas las reglas de enrutamiento, se utiliza el método sfRouting::setDefaultParameter(). Si por ejemplo se necesita que todas las reglas tengan un parámetro llamado tema con un valor por defecto igual a default, se añade la instrucción $this->context->getRouting()->setDefaultParameter('tema', 'default'); en al menos un filtro global de la aplicación.

9.4.4. Acelerando el sistema de enrutamiento mediante el uso de los nombres de las reglas

Los helpers de enlaces aceptan como argumento el nombre o etiqueta de la regla en vez del par modulo/acción, siempre que la etiqueta vaya precedida del signo @, como muestra el listado 9-21.

Listado 9-21 - Uso de la etiqueta de las reglas en vez de Modulo/Acción

<?php echo link_to('Mi artículo', 'articulo/ver?id='.$articulo->getId()) ?>

// también se puede escribir como...
<?php echo link_to('Mi artículo', '@articulo_segun_id?id='.$articulo->getId()) ?>

Esta técnica tiene sus ventajas e inconvenientes. En cuanto a las ventajas:

  • El formateo de las URI internas es más rápido, ya que Symfony no debe recorrer todas las reglas hasta encontrar la que se corresponde con el enlace. Si la página contiene un gran número de enlaces, el ahorro de tiempo de las reglas con nombre será apreciable respecto a los pares módulo/acción.
  • El uso de los nombres de las reglas permite abstraer aun más la lógica de la acción. Si se modifica el nombre de la acción pero se mantiene la URL, solo será necesario realizar un cambio en el archivo routing.yml. Todas las llamadas al helper link_to() funcionarán sin tener que realizar ningún cambio.
  • La lógica que se ejecuta es más comprensible si se utiliza el nombre de la regla. Aunque los módulos y las acciones tengan nombres explícitos, normalmente es más comprensible llamar a la regla @ver_articulo_segun_slug que simplemente llamar a articulo/ver.

Por otra parte, la desventaja principal es que es más complicado añadir los enlaces, ya que siempre se debe consultar el archivo routing.yml para saber el nombre de la regla que se utiliza en la acción.

La mejor técnica de las 2 depende del proyecto en el que se trate, por lo que es el programador el que tendrá que tomar la decisión.

Truco Mientras se prueba la aplicación (en el entorno dev), se puede comprobar la regla que se está aplicando para cada petición del navegador. Para ello, se debe desplegar la sección "logs and msgs" de la barra de depuración y se debe buscar la línea que dice "matched route XXX". El Capítulo 16 contiene más información sobre el modo de depuración web.

Nota Desde la versión 1.1 de Symfony las operaciones del sistema de enrutamiento son mucho más rápidas en el entorno de producción, ya que las conversiones de URI internas a URL externas se guardan en la caché.

9.4.5. Añadiendo la extensión .html

Si se comparan estas dos URL:

http://frontend.ejemplo.com/articulo/Economia_en_Francia
http://frontend.ejemplo.com/articulo/Economia_en_Francia.html

Aunque se trata de la misma página, los usuarios (y los robots que utilizan los buscadores) las consideran como si fueran diferentes debido a sus URL. La segunda URL parece que pertenece a un directorio web de páginas estáticas correctamente organizadas, que es exactamente el tipo de sitio web que mejor saben indexar los buscadores.

Para añadir un sufijo a todas las URL externas generadas en el sistema de enrutamiento, se debe modificar el valor de la opción suffix en el archivo de configuración factories.yml, como se muestra en el listado 9-22.

Listado 9-22 - Establecer un sufijo a todas las URL, en frontend/config/factories.yml

prod:
  routing:
    param:
      suffix:         .html

El sufijo por defecto es un punto (.), lo que significa que el sistema de enrutamiento no añade ningún sufijo a menos que se especifique uno.

En ocasiones es necesario indicar un sufijo específico para una única regla de enrutamiento. En ese caso, se indica el sufijo directamente como parte del patrón definido mediante url: en la regla del archivo routing.yml, como se muestra en el listado 9-23. El sufijo global se ignora en este caso.

Listado 9-23 - Estableciendo un sufijo en una única URL, en frontend/config/routing.yml

articulo_listado:
  url:          /ultimos_articulos
  param:        { module: articulo, action: listado }

articulo_listado_rss:
  url:          /ultimos_articulos.rss
  param:        { module: articulo, action: listado, type: feed }

9.4.6. Creando reglas sin el archivo routing.yml

Como sucede con la mayoría de archivos de configuración, el archivo routing.yml es una buena solución para definir las reglas del sistema de enrutamiento, pero no es la única solución. Se pueden definir reglas en PHP, en el archivo config.php de la aplicación o en el script del controlador frontal, pero antes de llamar a la función dispatch(), ya que este método determina la acción que se ejecuta en función de las reglas de enrutamiento disponibles en ese momento. Definir reglas mediante PHP permite crear reglas dinámicas que dependan de la configuración o de otros parámetros.

El objeto que gestiona las reglas de enrutamiento es una factoría llamada sfPatternRouting. Se encuentra disponible en cualquier parte del código mediante la llamada sfContext::getInstance()->getRouting(). Su método prependRoute() añade una nueva regla por encima de las reglas definidas en el archivo routing.yml. El método espera 4 parámetros, que son los mismos que se utilizan para definir una regla: la etiqueta de la ruta, el patrón de la URL, el array asociativo con los valores por defecto y otro array asociativo con los requisitos. La regla definida en el archivo routing.yml del listado 9-18 es equivalente por ejemplo al código PHP mostrado en el listado 9-24.

Nota A partir de la versión 1.1 de Symfony la clase que se encarga del enrutamiento se puede configurar en el archivo de configuración factories.yml. En este capítulo se explica el funcionamiento de la clase sfPatternRouting, que es la clase configurada por defecto para gestionar el sistema de enrutamiento, mientras que en el capítulo 17 se explica cómo cambiar esa clase por defecto.

Listado 9-24 - Definiendo una regla en PHP

sfContext::getInstance()->getRouting()->prependRoute(
  'articulo_segun_id',                              // Nombre ruta
  '/articulo/:id',                                  // Patrón de la ruta
  array('module' => 'articulo', 'action' => 'ver'), // Valores por defecto
  array('id' => '\d+'),                             // Requisitos
);

La clase sfPatternRouting define otros métodos muy útiles para la gestión manual de las rutas: clearRoutes(), hasRoutes(), etc. La API de Symfony (http://www.symfony-project.org/api/1_2/) dispone de mucha más información.

Truco A medida que se profundiza en los conceptos presentados en este libro, se pueden ampliar los conocimientos visitando la documentación de la API disponible online o incluso, investigando el código fuente de Symfony. En este libro no se describen todas las opciones y parámetros de Symfony, pero la documentación online contiene todos los detalles posibles.