Las URL bonitas absolutamente son una necesidad para cualquier aplicación web seria. Esto significa dejar atrás las URL feas como index.php?article_id=57 en favor de algo así como /leer/intro-a-symfony.
Tener tal flexibilidad es más importante aún. ¿Qué pasa si necesitas cambiar la URL de una página de /blog a /noticias? ¿Cuántos enlaces necesitas cazar y actualizar para hacer el cambio? Si estás utilizando el enrutador de Symfony, el cambio es sencillo.
El enrutador de Symfony2 te permite definir URL creativas que se asignan a diferentes áreas de la aplicación. Al final de este capítulo, serás capaz de:
Una ruta es un mapa desde la trayectoria de una URL hasta un controlador. Por ejemplo, supongamos que deseas adaptar cualquier URL como /blog/mi-post o /blog/todo-sobre-symfony y enviarla a un controlador que puede buscar y reproducir esta entrada del blog. La ruta es simple:
# app/config/routing.yml
blog_show:
path: /blog/{slug}
defaults: { _controller: AcmeBlogBundle:Blog:show }
<!-- app/config/routing.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="blog_show" path="/blog/{slug}">
<default key="_controller">AcmeBlogBundle:Blog:show</default>
</route>
</routes>
// app/config/routing.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add('blog_show', new Route('/blog/{slug}', array(
'_controller' => 'AcmeBlogBundle:Blog:show',
)));
return $collection;
Nuevo en la versión 2.2: La opción path es nueva en Symfony 2.2, en versiones anteriores se utilizaba pattern.
La ruta definida por blog_show actúa como /blog/* donde al comodín se le da el nombre de slug. Para la URL /blog/my-blog-post, la variable slug obtiene un valor de my-blog-post, que está disponible para usarlo en el controlador (sigue leyendo).
El parámetro _controller es una clave especial que le dice a Symfony qué controlador se debe ejecutar cuando una URL coincida con esta ruta. La cadena _controller se conoce como el nombre lógico. Esta sigue un patrón que apunta a una clase y método PHP específicos:
// src/Acme/BlogBundle/Controller/BlogController.php
namespace Acme\BlogBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class BlogController extends Controller
{
public function showAction($slug)
{
// usa la variable $slug para consultar la base de datos
$blog = ...;
return $this->render('AcmeBlogBundle:Blog:show.html.twig', array(
'blog' => $blog,
));
}
}
¡Enhorabuena! Acabas de crear tu primera ruta y la conectaste a un controlador. Ahora, cuando visites /blog/my-post, el controlador showAction será ejecutado y la variable $slug será igual a my-post.
Este es el objetivo del enrutador de Symfony2: asignar la URL de una petición a un controlador. De paso, aprenderás todo tipo de trucos que incluso facilitan la asignación de URL complejas.
Cuando se hace una petición a tu aplicación, esta contiene una dirección al «recurso» exacto que solicitó el cliente. Esta dirección se conoce como URL (o URI), y podría ser /contact, /blog/read-me, o cualquier otra cosa. Tomemos la siguiente petición HTTP, por ejemplo:
GET /blog/my-blog-post
El objetivo del sistema de enrutado de Symfony2 es analizar esta URL y determinar qué controlador se debe ejecutar. Todo el proceso es el siguiente:
Symfony carga todas las rutas de tu aplicación desde un archivo de configuración de enrutado. El archivo usualmente es app/config/routing.yml, pero lo puedes configurar para que sea cualquier otro (incluyendo un archivo XML o PHP) vía el archivo de configuración de la aplicación:
# app/config/config.yml
framework:
# ...
router: { resource: "%kernel.root_dir%/config/routing.yml" }
<!-- app/config/config.xml -->
<framework:config ...>
<!-- ... -->
<framework:router resource="%kernel.root_dir%/config/routing.xml" />
</framework:config>
// app/config/config.php
$container->loadFromExtension('framework', array(
// ...
'router' => array('resource' => '%kernel.root_dir%/config/routing.php'),
));
Truco
A pesar de que todas las rutas se cargan desde un solo archivo, es práctica común incluir recursos de enrutado adicionales. Para ello, sólo indica en el archivo de configuración de enrutado principal cuáles archivos externos se tendrían que incluir. Consulta la sección Incluyendo recursos de enrutado externos para más información.
Definir una ruta es fácil, y una aplicación típica tendrá un montón de rutas. Una ruta básica consta de dos partes: el path a emparejar y un arreglo defaults:
_welcome:
path: /
defaults: { _controller: AcmeDemoBundle:Main:homepage }
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="_welcome" path="/">
<default key="_controller">AcmeDemoBundle:Main:homepage</default>
</route>
</routes>
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add('_welcome', new Route('/', array(
'_controller' => 'AcmeDemoBundle:Main:homepage',
)));
return $collection;
Esta ruta coincide con la página de inicio (/) y la asigna al controlador de la página principal AcmeDemoBundle:Main:homepage. Symfony2 convierte la cadena _controller en una función PHP real y la ejecuta. Este proceso será explicado en breve en la sección Patrón de nomenclatura para controladores.
Por supuesto, el sistema de enrutado es compatible con rutas mucho más interesantes. Muchas rutas contienen uno o más «comodines» llamados marcadores de posición:
blog_show:
path: /blog/{slug}
defaults: { _controller: AcmeBlogBundle:Blog:show }
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="blog_show" path="/blog/{slug}">
<default key="_controller">AcmeBlogBundle:Blog:show</default>
</route>
</routes>
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add('blog_show', new Route('/blog/{slug}', array(
'_controller' => 'AcmeBlogBundle:Blog:show',
)));
return $collection;
La ruta emparejará con cualquier cosa que se parezca a /blog/*. Aún mejor, el valor coincide con el marcador de posición {slug} que estará disponible dentro de tu controlador. En otras palabras, si la URL es /blog/hello-world, una variable $slug, con un valor de hello-world, estará disponible en el controlador. Esta se puede usar, por ejemplo, para cargar la entrada en el blog coincidente con esa cadena.
La ruta no debe, sin embargo, emparejar con un /blog sencillo. Eso es porque, por omisión, todos los marcadores de posición son obligatorios. Esto se puede cambiar agregando un valor marcador de posición al arreglo defaults.
Para hacer las cosas más emocionantes, añade una nueva ruta que muestre una lista de todas las entradas del ‘blog’ para la petición imaginaria ‘blog’:
blog:
path: /blog
defaults: { _controller: AcmeBlogBundle:Blog:index }
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="blog" path="/blog">
<default key="_controller">AcmeBlogBundle:Blog:index</default>
</route>
</routes>
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add('blog', new Route('/blog', array(
'_controller' => 'AcmeBlogBundle:Blog:index',
)));
return $collection;
Hasta el momento, esta ruta es tan simple como es posible — no contiene marcadores de posición y sólo coincidirá con la URL exacta /blog. ¿Pero si necesitamos que esta ruta sea compatible con paginación, donde /blog/2 muestra la segunda página de las entradas del blog? Actualiza la ruta para que tenga un nuevo marcador de posición {page}:
blog:
path: /blog/{page}
defaults: { _controller: AcmeBlogBundle:Blog:index }
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="blog" path="/blog/{page}">
<default key="_controller">AcmeBlogBundle:Blog:index</default>
</route>
</routes>
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add('blog', new Route('/blog/{page}', array(
'_controller' => 'AcmeBlogBundle:Blog:index',
)));
return $collection;
Al igual que el marcador de posición {slug} anterior, el valor coincidente con {page} estará disponible dentro de tu controlador. Puedes utilizar su valor para determinar cual conjunto de entradas del blog muestra determinada página.
¡Pero espera! Puesto que los marcadores de posición de forma predeterminada son obligatorios, esta ruta ya no coincidirá con /blog simplemente. En su lugar, para ver la página 1 del blog, ¡habrá la necesidad de utilizar la URL /blog/1! Debido a que esa no es la manera en que se comporta una aplicación web rica, debes modificar la ruta para que el parámetro {page} sea opcional. Esto se consigue incluyéndolo en la colección defaults:
blog:
path: /blog/{page}
defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="blog" path="/blog/{page}">
<default key="_controller">AcmeBlogBundle:Blog:index</default>
<default key="page">1</default>
</route>
</routes>
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add('blog', new Route('/blog/{page}', array(
'_controller' => 'AcmeBlogBundle:Blog:index',
'page' => 1,
)));
return $collection;
Agregando page a la clave defaults, el marcador de posición {page} ya no es necesario. La URL /blog coincidirá con esta ruta y el valor del parámetro page se fijará en 1. La URL /blog/2 también coincide, dando al parámetro page un valor de 2. Perfecto.
/blog | {page} = 1 |
/blog/1 | {page} = 1 |
/blog/2 | {page} = 2 |
Truco
Las rutas con parámetros opcionales al final no coincidirán con peticiones con una barra inclinada final (es decir, /blog/ no coincidirá, en cambio /blog concordará).
Echa un vistazo a las rutas que hemos creado hasta ahora:
blog:
path: /blog/{page}
defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }
blog_show:
path: /blog/{slug}
defaults: { _controller: AcmeBlogBundle:Blog:show }
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="blog" path="/blog/{page}">
<default key="_controller">AcmeBlogBundle:Blog:index</default>
<default key="page">1</default>
</route>
<route id="blog_show" path="/blog/{slug}">
<default key="_controller">AcmeBlogBundle:Blog:show</default>
</route>
</routes>
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add('blog', new Route('/blog/{page}', array(
'_controller' => 'AcmeBlogBundle:Blog:index',
'page' => 1,
)));
$collection->add('blog_show', new Route('/blog/{show}', array(
'_controller' => 'AcmeBlogBundle:Blog:show',
)));
return $collection;
¿Puedes ver el problema? Ten en cuenta que ambas rutas tienen patrones que coinciden con las URL que se parezcan a /blog/*. El enrutador de Symfony siempre elegirá la primera ruta coincidente que encuentre. En otras palabras, la ruta blog_show nunca corresponderá. En cambio, una URL como /blog/my-blog-post coincidirá con la primera ruta (blog) y devolverá un valor sin sentido de my-blog-post para el parámetro {page}.
URL | ruta | parámetros |
---|---|---|
/blog/2 | blog | {page} = 2 |
/blog/mi-entrada-del-blog | blog | {page} = mi-entrada-del-blog |
La respuesta al problema es añadir requisitos a la ruta. Las rutas en este ejemplo funcionarán perfectamente si el patrón /blog/{page} sólo concuerda con una URL dónde la parte {page} sea un número entero. Afortunadamente, se puede agregar fácilmente una expresión regular de requisitos para cada parámetro. Por ejemplo:
blog:
path: /blog/{page}
defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }
requirements:
page: \d+
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="blog" path="/blog/{page}">
<default key="_controller">AcmeBlogBundle:Blog:index</default>
<default key="page">1</default>
<requirement key="page">\d+</requirement>
</route>
</routes>
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add('blog', new Route('/blog/{page}', array(
'_controller' => 'AcmeBlogBundle:Blog:index',
'page' => 1,
), array(
'page' => '\d+',
)));
return $collection;
El requisito \d+ es una expresión regular diciendo que el valor del parámetro {page} debe ser un dígito (es decir, un número). La ruta blog todavía coincide con una URL como /blog/2 (porque 2 es un número), pero ya no concuerda con una URL como /blog/my-blog-pos (porque my-blog-post no es un número).
Como resultado, una URL como /blog/my-blog-post ahora coincide correctamente con la ruta blog_show.
URL | ruta | parámetros |
---|---|---|
/blog/2 | blog | {page} = 2 |
/blog/mi-entrada-del-blog | blog_show | {ficha} = mi-entrada-del-blog |
Puesto que el parámetro requirements consiste de expresiones regulares, la complejidad y flexibilidad de cada requisito es totalmente tuya. Supongamos que la página principal de tu aplicación está disponible en dos diferentes idiomas, basándose en la URL:
homepage:
path: /{_locale}
defaults: { _controller: AcmeDemoBundle:Main:homepage, _locale: en }
requirements:
_locale: en|fr
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="homepage" path="/{culture}">
<default key="_controller">AcmeDemoBundle:Main:homepage</default>
<default key="_locale">en</default>
<requirement key="_locale">en|fr</requirement>
</route>
</routes>
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add('homepage', new Route('/{culture}', array(
'_controller' => 'AcmeDemoBundle:Main:homepage',
'culture' => 'en',
), array(
'culture' => 'en|fr',
)));
return $collection;
Para las peticiones entrantes, la porción {_locale} de la dirección se compara con la expresión regular (en|es).
/ | {_locale} = es |
/en | {_locale} = en |
/es | {_locale} = es |
/fr | no coincidirá con esta ruta |
Además de la URL, también puedes coincidir con el método de la petición entrante (es decir, GET, HEAD, POST, PUT, DELETE). Supongamos que tienes un formulario de contacto con dos controladores —uno para mostrar el formulario (en una petición GET) y uno para procesar el formulario una vez presentado (en una petición POST). Esto se puede lograr con la siguiente configuración de ruta:
contact:
path: /contact
defaults: { _controller: AcmeDemoBundle:Main:contact }
methods: [GET]
contact_process:
path: /contact
defaults: { _controller: AcmeDemoBundle:Main:contactProcess }
methods: [POST]
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="contact" path="/contact" methods="GET">
<default key="_controller">AcmeDemoBundle:Main:contact</default>
</route>
<route id="contact_process" path="/contact" methods="POST">
<default key="_controller">AcmeDemoBundle:Main:contactProcess</default>
</route>
</routes>
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add('contact', new Route('/contact', array(
'_controller' => 'AcmeDemoBundle:Main:contact',
), array(), array(), '', array(), array('GET')));
$collection->add('contact_process', new Route('/contact', array(
'_controller' => 'AcmeDemoBundle:Main:contactProcess',
), array(), array(), '', array(), array('POST')));
return $collection;
Nuevo en la versión 2.2: La opción methods se añadió en Symfony 2.2. Usa el requisito _method en versiones anteriores.
A pesar del hecho de que estas dos rutas tienen patrones idénticos (/contact), la primera ruta sólo coincidirá con las peticiones GET y la segunda sólo coincidirá con las peticiones POST. Esto significa que puedes mostrar y enviar el formulario a través de la misma URL, mientras usas controladores distintos para las dos acciones.
Nota
Si no especifícas ningún método, la ruta emparejará con todos los métodos.
Nuevo en la versión 2.2: El soporte necesario para emparejar con el servidor se añadió en Symfony 2.2
También puedes emparejar con el host HTTP de la petición entrante. Para más información, consulta el Cómo emparejar una ruta basándose en el servidor en la documentación del componente Routing.
En este punto, tienes todo lo necesario para crear una poderosa estructura de enrutado Symfony. El siguiente es un ejemplo de cuán flexible puede ser el sistema de enrutado:
article_show:
path: /articles/{_locale}/{year}/{title}.{_format}
defaults: { _controller: AcmeDemoBundle:Article:show, _format: html }
requirements:
_locale: en|fr
_format: html|rss
year: \d+
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="article_show" path="/articles/{culture}/{year}/{title}.{_format}">
<default key="_controller">AcmeDemoBundle:Article:show</default>
<default key="_format">html</default>
<requirement key="_locale">en|fr</requirement>
<requirement key="_format">html|rss</requirement>
<requirement key="year">\d+</requirement>
</route>
</routes>
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add('homepage', new Route('/articles/{culture}/{year}/{title}.{_format}', array(
'_controller' => 'AcmeDemoBundle:Article:show',
'_format' => 'html',
), array(
'culture' => 'en|fr',
'_format' => 'html|rss',
'year' => '\d+',
)));
return $collection;
Como hemos visto, esta ruta sólo coincide si la porción {_locale} de la URL es o bien «en» o «fr» y si {year} es un número. Esta ruta también muestra cómo puedes utilizar un punto entre los marcadores de posición en lugar de una barra inclinada. Las URL que coinciden con esta ruta se podrían ver como las siguientes:
Nota
A veces quieres hacer configurables globalmente ciertas partes de tus rutas. Symfony 2.1 te proporciona una manera de hacer esto aprovechando los parámetros del contenedor de servicios. Lee más sobre esto en «Cómo utilizar parámetros del contenedor de servicios en tus rutas».
Como has visto, cada parámetro de enrutado o valor predeterminado finalmente está disponible como un argumento en el método controlador. Adicionalmente, hay tres parámetros que son especiales: cada uno añade una única pieza de funcionalidad a tu aplicación:
Truco
Si utilizas el parámetro _locale en una ruta, ese valor también se almacenará en la sesión para las subsecuentes peticiones lo cual evita guardar la misma región.
Cada ruta debe tener un parámetro _controller, el cual determina qué controlador se debe ejecutar cuando dicha ruta concuerde. Este parámetro utiliza un patrón de cadena simple llamado el nombre lógico del controlador, que Symfony asigna a un método y clase PHP específico. El patrón consta de tres partes, cada una separada por dos puntos:
paquete:controlador:acción
Por ejemplo, un valor _controller de AcmeBlogBundle:Blog:show significa:
Paquete | Clase de controlador | Nombre método |
---|---|---|
AcmeBlogBundle | BlogController | showAction |
El controlador podría tener este aspecto:
// src/Acme/BlogBundle/Controller/BlogController.php
namespace Acme\BlogBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class BlogController extends Controller
{
public function showAction($slug)
{
// ...
}
}
Ten en cuenta que Symfony añade la cadena Controller al nombre de la clase (Blog => BlogController) y Action al nombre del método (show => showAction).
También podrías referirte a este controlador utilizando su nombre de clase y método completamente cualificado: Acme\BlogBundle\Controller\BlogController::showAction. Pero si sigues algunas simples convenciones, el nombre lógico es más conciso y permite mayor flexibilidad.
Nota
Además de utilizar el nombre lógico o el nombre de clase completamente cualificado, Symfony es compatible con una tercera forma de referirse a un controlador. Este método utiliza un solo separador de dos puntos (por ejemplo, service_name:indexAction) y hace referencia al controlador como un servicio (consulta Cómo definir controladores como servicios).
Los parámetros de ruta (por ejemplo, {slug}) son especialmente importantes porque cada uno se pone a disposición como argumento para el método controlador:
public function showAction($slug)
{
// ...
}
En realidad, toda la colección defaults se combina con los valores del parámetro para formar un solo arreglo. Cada clave de ese arreglo está disponible como un argumento en el controlador.
En otras palabras, por cada argumento de tu método controlador, Symfony busca un parámetro de ruta con ese nombre y asigna su valor a ese argumento. En el ejemplo avanzado anterior, podrías utilizar cualquier combinación (en cualquier orden) de las siguientes variables como argumentos para el método showAction():
Dado que los marcadores de posición y los valores de la colección defaults se combinan, incluso la variable $_controller está disponible. Para una explicación más detallada, consulta Parámetros de ruta como argumentos para el controlador.
Truco
También puedes utilizar una variable especial $_route, que se fija al nombre de la ruta que concordó.
Todas las rutas se cargan a través de un único archivo de configuración —usualmente app/config/routing.yml (consulta Creando rutas más arriba). Por lo general, sin embargo, deseas cargar rutas para otros lugares, como un archivo de enrutado que vive dentro de un paquete. Esto se puede hacer «importando» ese archivo:
# app/config/routing.yml
acme_hello:
resource: "@AcmeHelloBundle/Resources/config/routing.yml"
<!-- app/config/routing.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
<import resource="@AcmeHelloBundle/Resources/config/routing.xml" />
</routes>
// app/config/routing.php
use Symfony\Component\Routing\RouteCollection;
$collection = new RouteCollection();
$collection->addCollection($loader->import("@AcmeHelloBundle/Resources/config/routing.php"));
return $collection;
Nota
Cuando importas recursos desde YAML, la clave (por ejemplo, acme_hello) no tiene sentido. Sólo asegúrate de que es única para que no haya otras líneas que reemplazar.
La clave resource carga el recurso de la ruta dada. En este ejemplo, el recurso es la ruta completa a un archivo, donde la sintaxis contextual del atajo @AcmeHelloBundle se resuelve en la ruta a ese paquete. El archivo importado podría tener este aspecto:
# src/Acme/HelloBundle/Resources/config/routing.yml
acme_hello:
path: /hello/{name}
defaults: { _controller: AcmeHelloBundle:Hello:index }
<!-- src/Acme/HelloBundle/Resources/config/routing.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="acme_hello" path="/hello/{name}">
<default key="_controller">AcmeHelloBundle:Hello:index</default>
</route>
</routes>
// src/Acme/HelloBundle/Resources/config/routing.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add('acme_hello', new Route('/hello/{name}', array(
'_controller' => 'AcmeHelloBundle:Hello:index',
)));
return $collection;
Las rutas de este archivo se analizan y cargan en la misma forma que el archivo de enrutado principal.
También puedes optar por proporcionar un «prefijo» para las rutas importadas. Por ejemplo, supongamos que deseas que la ruta acme_hello tenga una ruta final de /admin/hello/{name} en lugar de simplemente /hello/{name}:
# app/config/routing.yml
acme_hello:
resource: "@AcmeHelloBundle/Resources/config/routing.yml"
prefix: /admin
<!-- app/config/routing.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
<import resource="@AcmeHelloBundle/Resources/config/routing.xml" prefix="/admin" />
</routes>
// app/config/routing.php
use Symfony\Component\Routing\RouteCollection;
$collection = new RouteCollection();
$collection->addCollection($loader->import("@AcmeHelloBundle/Resources/config/routing.php"), '/admin');
return $collection;
La cadena /admin ahora será prefijada a la trayectoria de cada ruta cargada del nuevo recurso de enrutado.
Truco
Además puedes definir rutas usando anotaciones. Consulta la documentación del FrameworkExtraBundle para ver cómo hacerlo.
Nuevo en la versión 2.2: El soporte necesario para emparejar con el servidor se añadió en Symfony 2.2
Puedes poner el servidor en la expresión regular de las rutas importadas. Para más información, ve Añadiendo un servidor a la expresión regular de las rutas importadas.
Si bien agregar y personalizar rutas, es útil para poder visualizar y obtener información detallada sobre tus rutas. Una buena manera de ver todas las rutas en tu aplicación es a través de la orden de consola router:debug. Ejecuta la siguiente orden desde la raíz de tu proyecto.
$ php app/console router:debug
Esta orden imprimirá una útil lista de todas las rutas configuradas en tu aplicación:
homepage ANY /
contact GET /contact
contact_process POST /contact
article_show ANY /articles/{_locale}/{year}/{title}.{_format}
blog ANY /blog/{page}
blog_show ANY /blog/{slug}
También puedes obtener información muy específica de una sola ruta incluyendo el nombre de la ruta después de la orden:
$ php app/console router:debug article_show
Asímismo, si deseas probar acuál ruta coincide con una trayectoria dada, puedes usar la orden de consola router:match:
$ php app/console router:match /blog/my-latest-post
Esta orden imprimirá la ruta de la URL coincidente.
Route "blog_show" matches
El sistema de enrutado también se debe utilizar para generar URL. En realidad, el enrutado es un sistema bidireccional: asignando la URL a un controlador+parámetros y la ruta+parámetros a una URL. Los métodos match() y generate() de este sistema bidireccional. Tomando la ruta blog_show del ejemplo anterior:
$params = $this->get('router')->match('/blog/my-blog-post');
// array(
// 'slug' => 'my-blog-post',
// '_controller' => 'AcmeBlogBundle:Blog:show',
// )
$uri = $this->get('router')->generate('blog_show', array('slug' => 'my-blog-post'));
// /blog/my-blog-post
Para generar una URL, debes especificar el nombre de la ruta (por ejemplo, blog_show) y cualquier comodín (por ejemplo, slug = my-blog-post) usado en el patrón de esa ruta. Con esta información, fácilmente puedes generar cualquier URL:
class MainController extends Controller
{
public function showAction($slug)
{
// ...
$url = $this->generateUrl(
'blog_show',
array('slug' => 'my-blog-post')
);
}
}
Nota
En los controladores que extienden la clase base Symfony\Bundle\FrameworkBundle\Controller\Controller de Symfony, puedes utilizar el método generateUrl(), el cual llama al método generate() del servicio enrutador.
En una sección posterior, aprenderás cómo generar URL desde el interior de tus plantillas.
Truco
Si la interfaz de tu aplicación utiliza peticiones AJAX, posiblemente desees poder generar las direcciones URL en JavaScript basándote en tu configuración de enrutado. Usando el FOSJsRoutingBundle, puedes hacer eso exactamente:
var url = Routing.generate(
'blog_show',
{"slug": 'my-blog-post'}
);
Para más información, consulta la documentación del paquete.
De forma predeterminada, el enrutador va a generar URL relativas (por ejemplo /blog). Para generar una URL absoluta, sólo tienes que pasar true como tercer argumento del método generate():
$router->generate('blog_show', array('slug' => 'my-blog-post'), true);
// http://www.example.com/blog/my-blog-post
Nota
El servidor utilizado al generar una URL absoluta es el anfitrión del objeto Petición actual. Este se detercta automática basándose en la información del servidor suministrada por PHP. Al generar direcciones URL absolutas para archivos ejecutables desde la línea de ordenes, debes configurar manualmente el servidor que desees en el objeto RequestContext:
$router->getContext()->setHost('www.example.com');
El método generate toma un arreglo de valores comodín para generar la URI. Pero si pasas adicionales, se añadirán a la URI como cadena de consulta:
$router->generate('blog', array('page' => 2, 'category' => 'Symfony'));
// /blog/2?category=Symfony
El lugar más común para generar una URL es dentro de una plantilla cuando creas enlaces entre las páginas de tu aplicación. Esto se hace igual que antes, pero utilizando una función ayudante de plantilla:
<a href="{{ path('blog_show', {'slug': 'my-blog-post'}) }}">
Read this blog post.
</a>
<a href="<?php echo $view['router']->generate('blog_show', array('slug' => 'my-blog-post')) ?>">
Read this blog post.
</a>
También puedes generar URL absolutas.
<a href="{{ url('blog_show', {'slug': 'my-blog-post'}) }}">
Read this blog post.
</a>
<a href="<?php echo $view['router']->generate('blog_show', array('slug' => 'my-blog-post'), true) ?>">
Read this blog post.
</a>
El enrutado es un sistema para asignar la dirección de las peticiones entrantes a la función controladora que se debe llamar para procesar la petición. Este permite especificar ambas URL bonitas y mantiene la funcionalidad de tu aplicación disociada de las URL. El enrutado es un mecanismo de dos vías, lo cual significa que también lo debes usar para generar tus direcciones URL.