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 miapp/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.
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 2 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 crea un parámetro de configuración llamado sf_routing_default
. Si por ejemplo se necesita que todas las reglas tengan un parámetro llamado tema
con un valor por defecto igual a default
, se debe añadir la siguiente línea al archivo config.php
de la aplicación: sfConfig::set('sf_routing_defaults', array('tema' => 'default'));
.
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 mucho 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 helperlink_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 aarticulo/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.
9.4.5. Añadiendo la extensión .html
Si se comparan estas dos URL:
http://miapp.ejemplo.com/articulo/Economia_en_Francia http://miapp.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 settings.yml
, como se muestra en el listado 9-22.
Listado 9-22 - Establecer un sufijo a todas las URL, en miapp/config/settings.yml
prod:
.settings
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 miapp/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 un singleton llamado sfRouting
. Se encuentra disponible en cualquier parte del código mediante la llamada sfRouting::getInstance()
. 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.
Listado 9-24 - Definiendo una regla en PHP
sfRouting::getInstance()->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
);
El singleton sfRouting
define otros métodos muy útiles para la gestión manual de las rutas: clearRoutes()
, hasRoutes()
, getRoutesByName()
, etc. La API de Symfony (http://www.symfony-project.org/api/1_0/) 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.