Symfony 1.4, 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 }

# generic rules
# please, remove them by adding more specific rules
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 wildcards o comodines (que se representan con un asterisco, *) y comodines con nombre (que empiezan por dos puntos, :). Si se produce una coincidencia con un comodín con nombre, ese valor 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 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.

Nota 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 es la forma más rápida, ya que no necesita ser procesado:
<?php echo link_to('Mi artículo', 'articulo_segun_id', array('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.
  • Conoces exactamente las acciones que están activadas simplemente leyendo el archivo routing.yml

Por otra parte, la principal desventaja es que es más complicado añadir los enlaces, ya que debes consultar el archivo routing.yml para saber el nombre de la regla que se utiliza en la acción. Si el proyecto es grande vas a tener muchas rutas, por lo que su mantenimiento puede resultar difícil. En este caso, deberías modularizar tu aplicación en varios plugins, cada uno de los cuales definen unas pocas reglas de enrutamiento.

La experiencia dice que utilizar los nombres de las reglas de enrutamiento es la mejor opción a largo plazo.

Nota 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.

9.4.5. Creando rutas sin el archivo routing.yml

Como sucede con la mayoría de archivos de configuración, el archivo routing.yml es una forma de definir reglas de enrutamiento, pero no es la única. Puedes definir reglas directamente en el código PHP, siempre que lo hagas antes de la llamada al método dispatch(), ya que este método determina la acción a ejecutar en función de las reglas existentes. La ventaja de crear reglas con PHP es que permite crear reglas dinámicas que dependen de opciones de configuración o de cualquier otro parámetro.

El objeto que gestiona las reglas de enrutamiento es una factoría llamada sfPatternRouting. Puedes acceder a ella desde cualquier parte de la aplicación mediante sfContext::getInstance()->getRouting(). Su método prependRoute() añade una ruta por encima de todas las rutas definidas en el archivo routing.yml. Este método utiliza dos parámetros: el nombre de la ruta y un objeto de tipo sfRoute. La ruta definida en el archivo routing.yml del listado 9-18 es equivalente a la ruta generada mediante PHP en el listado 9-22.

Listado 9-22 - Definiendo una regla mediante PHP

sfContext::getInstance()->getRouting()->prependRoute(
  'articulo_segun_id',                                  // Nombre de la ruta
  new sfRoute('/articulo/:id', array('module' => 'articulo', 'action' => 'leer'), array('id' => '\d+')),                         // Objeto de la ruta
);

El constructor de la clase sfRoute utiliza tres argumentos: el patrón de la ruta, un array asociativo con los valores por defecto y otro array asociativo con los requisitos.

La clase sfPatternRouting dispone de otros métodos útiles para gestionar las rutas manualmente: clearRoutes(), hasRoutes(), etc. La documentación de la API explica todos los métodos disponibles.

Nota Una vez que entiendas bien todos los conceptos explicados en este libro, es muy recomendable que consultes la documentación de la API y que investigues el propio código fuente de Symfony para mejorar tus conocimientos. Como es fácil de entender, este libro no puede explicar todas las opciones de configuración y todos los parámetros disponibles. La documentación online es ilimitada y si que explica todas las opciones, parámetros, métodos y clases disponibles.

Nota La clase del enrutamiento se puede configurar en el archivo factories.yml (para modificar la clase que se utiliza por defecto, consulta el capítulo 17). En este capítulo sólo se explica la clase sfPatternRouting, que es la que utiliza Symfony por defecto.