Sistema de Routing
Una de las necesidades más comunes en el desarrollo de Sitios profesionales es implementar URLs amigables, así convertimos algo como /index.php?articulo=1 por algo más cómodo y agradable a la vista del usuario: /blog/introduccion_symfony2.htm.
El Routing de Symfony2 nos brinda un sistema de enrutado muy dinámico que deja concentrarnos en el desarrollo de los “caminos” hacia nuestra aplicación, además es bidireccional, lo cual nos permite cambiar la ruta /blog/introduccion_symfony2.htm por /noticia/introduccion_symfony2.htm editando sólo la definición de la ruta y así evitarnos la tarea de buscar todas las referencias internas hacia ella en nuestra aplicación para hacer el cambio.
El objetivo de éste capítulo es comprender el funcionamiento básico del sistema de Routing de Symfony2, crear e importar rutas, así como configuraciones básicas para permitir rutas flexibles. Nos enfocaremos en la configuración en formato YAML por ser simple y fácil de entender, recuerda que Symfony 2 es un Fw altamente configurable y que puedes utilizar como configuración: XML, PHP y Annotations.
Funcionamiento del Routing
En la arquitectura del Modelo MVC el encargado de manejar las peticiones Web es el Controlador (Controller), como cualquier aplicación consiste en varios Controllers y necesitamos un punto de acceso centralizado para distribuir las peticiones de nuestra aplicación a los Controllers indicados, a ese punto lo llamamos el Controlador frontal (Front Controller) que generalmente corresponde al archivo raíz de nuestra web, es decir el app.php o index.php (dependiendo de la configuración del servidor HTTP) y para ello necesitamos redirigir a éste todas las peticiones por medio de un .htaccess (en el caso de Apache):
# web/.htaccess RewriteEngine On RewriteCond %{REQUEST_FILEname} !-f RewriteRule ^(.*)$ app.php [QSA,L]
En Symfony2 el Front Controller se encarga de cargar el kernel del Framework, el cual recibe nuestra petición HTTP (request) y pasa la URL al sistema de Routing, donde es procesada (comparada con las rutas definidas) y se ejecuta el Controller definido; este es el comportamiento básico del Routing: empatar URL y ejecutar los Controllers.
Código del Front Controller:
<?php // web/app.php require_once __DIR__.'/../app/bootstrap.php.cache'; require_once __DIR__.'/../app/AppKernel.php'; //require_once __DIR__.'/../app/AppCache.php'; use Symfony\Component\HttpFoundation\Request; $kernel = new AppKernel('prod', false); $kernel->loadClassCache(); //$kernel = new AppCache($kernel); $kernel->handle(Request::createFromGlobals())->send();
Notarás que Symfony2 tiene en principio 2 Front Controllers: web/app.php y web/app_dev.php, esto se debe a que Symfony2 maneja por cada controlador frontal un “Entorno” (Environment) que le permite ajustar la configuración interna según la situación en que se encuentre nuestra aplicación, app.php para la Producción y app_dev.php para el desarrollo.
De ahora en adelante utilizaremos el Front Controller de desarrollo web/app_dev.php debido a que desactiva el mecanismo de caché interna de Symfony, permitiéndonos comprobar los cambios que hagamos sobre la configuración de nuestra aplicación, en nuestro navegador accederíamos así: /app_dev.php (es decir: http://localhost/Symfony/web/app_dev.php en tu navegador).
Definiendo e Importando Rutas
Como ya sabes una ruta es una asociación o mapa de un patrón URL hacia un Controlador, las mismas se definen en el archivo routing de nuestra aplicación, el cual se encuentra en app/config y puede estar definido en tres formatos: YAML, XML o PHP; Symfony 2 utiliza por defecto YAML (routing.yml) pero puede cambiarse. Por ejemplo, tenemos una ruta (suponiendo un Bundle MDWDemoBundle con DefaultController previamente definido):
# app/config/routing.yml path_hello_world: pattern: /hello defaults: { _controller: MDWDemoBundle:Default:index }
Analicemos cada componente:
path_hello_world: corresponde al nombre de la ruta, por ahora te parecerá irrelevante, pero es el requisito indispensable para generar las URLs internas en tu sitio que se verá más adelante, el nombre debe ser único, corto y conciso.
pattern: /hello el atributo pattern define el patrón de la ruta, lo que le permite al Routing empatar las peticiones, si el patrón fuese solo “/” representaría nuestra página de inicio, más adelante se verá su uso avanzado y los comodines.
defaults: { _controller: MDWDemoBundle:Default:index } dentro de defaults tenemos el parámetro especial _controller donde se define el “Controlador”, nótese que sigue un patrón definido Bundle:Controller:Action, esto le permite al Routing hallar el controlador especificado, automáticamente Routing resuelve la ubicación del Bundle, el controlador y la acción, sin necesidad de definir los sufijos “Controller” y “Action” correspondientes al controlador y acción.
Con la ruta anterior accederíamos al siguiente Controlador y Acción:
<?php // src/MDW/DemoBundle/Controller/DefaultController.php namespace MDW\DemoBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class DefaultController extends Controller { public function indexAction() { $response = new Response('Hola mundo!!!'); return $response; } }
Así que ésta es la definición básica de una ruta, con ello puedo acceder desde mi navegador a /app_dev.php/hello.
Pero según la filosofía de los Bundles en Symfony2 se debe hacer el código lo más portable posible y si defino mis rutas en la aplicación, serían parte de mi aplicación y no de mi “Bundle”, por lo cual debería definirlas en el Bundle e importarlas hacia mi aplicación, de esa forma cuando tenga la necesidad de crear otra aplicación en la cual necesite cierto Bundle, sólo tengo que importar las rutas definidas en dicho Bundle para utilizarlas en mi aplicación, con lo cual mi Bundle es verdaderamente desacoplado y portable; para ello traslado mis rutas hacia el Bundle en su propio archivo de rutas: src/MDW/DemoBundle/Resources/config/routing.yml y en mi archivo de rutas de la aplicación lo importo:
# app/config/routing.yml MDWDemoBundle: resource: "@MDWDemoBundle/Resources/config/routing.yml" prefix: /
Como podemos ver la estructura ha cambiado, en el atributo resource: podemos definir la ruta completa hacia nuestro archivo de rutas del Bundle, en este caso utilizamos la forma especial @NombreBundle lo cual le indica al Routing que internamente resuelva la ruta hacia nuestro Bundle, haciendo la tarea muy cómoda.
También tenemos el atributo prefix: ¡que nos permite definir un prefijo!, con ello podemos hacer muchas cosas como diferenciar patrones similares en Bundles diferentes anteponiendo un prefijo, por ejemplo, si colocamos prefix: /blogger las URLs del Bundle quedarían así: app_dev.php/blogger/hello o /blogger/hello motivo por el cual el prefijo predeterminado es “/” es decir: sin prefijo.
A partir de aquí las rutas están definidas en el Bundle, y en el routing de nuestra aplicación se importan, haciendo nuestro “Bundle” más portable.
Rutas por defecto en el entorno de Desarrollo
Si revisamos bien el archivo config_dev.yml, utilizado para el entorno de desarrollo notamos que el archivo de rutas principalmente importado es routing_dev.yml, en el cual no sólo se registran las rutas del perfilador y otras que necesitas al momento de probar en el entorno de desarrollo, sino también una serie de rutas hacia el AcmeDemoBundle, que no es más que un Bundle de pruebas que no necesitas realmente en tu aplicación.
Como el AcmeDemoBundle sólo se registra en el entorno de Desarrollo, no afectará para nada tu aplicación cuando la ejecutes normalmente desde el entorno de Producción, pero debido a que el routing.yml normal es importado al final por éste, si intentas definir una ruta hacia “/” o alguna que coincida con dicho AcmeDemoBundle, notarás que al acceder desde el entorno de Desarrollo tomará prioridad AcmeDemoBundle y no el tuyo, afortunadamente la solución es muy sencilla donde simplemente comentas o eliminas las 3 rutas que pertenecen al AcmeDemoBundle, luego, si lo deseas eliminas dicho Bundle:
# app/config/routing_dev.yml # COMENTAMOS o ELIMINAMOS estas 3 rutas: ------------------------ #_welcome: # pattern: / # defaults: { _controller: AcmeDemoBundle:Welcome:index } #_demo_secured: # resource: "@AcmeDemoBundle/Controller/SecuredController.php" # type: annotation #_demo: # resource: "@AcmeDemoBundle/Controller/DemoController.php" # type: annotation # prefix: /demo _assetic: resource: . type: assetic _wdt: resource: "@WebProfilerBundle/Resources/config/routing/wdt.xml" prefix: /_wdt _profiler: resource: "@WebProfilerBundle/Resources/config/routing/profiler.xml" prefix: /_profiler _configurator: resource: "@SensioDistributionBundle/Resources/config/routing/webconfigurator.xml" prefix: /_configurator _main: resource: routing.yml
Resumen Final
En éste capítulo profundizamos en el comportamiento esencial del Sistema de Routing, en dónde un Controlador Frontal despacha las peticiones a los controladores reales de nuestra aplicación, además comprendimos la importancia en la importación de las rutas, para así obtener el código correspondiente a nuestros Bundles encapsulados en los mismos, fomentando un diseño perfectamente desacoplable, adicionalmente vimos como desactivar el AcmeDemoBundle desde nuestro entorno de Desarrollo para así permitirnos navegar nuestra aplicación por completo desde éste y así obtener las ventajas del perfilador para depurar nuestros proyectos.
En el próximo capitulo aprenderemos a como definir rutas a la medida, aprovechar las ventajas del sistema bidireccional y con ello rendir al máximo con el desarrollo de rutas amigables, limpias y precisas.
[…] Guía de Symfony2 – Capítulo 4 – Sistema de Routing VN:F [1.9.15_1155]please wait…Rating: 0.0/5 (0 votes cast)Ya se encuentra publicado el capítulo número 4 de la Guía de Symfony2 en Maestros del Web. […]
Excelente Maycol, como siempre. de paso aprovecho para darle las gracias por las veces que me ayudaste en FDW, que no han sido pocas.
Saludos.
Gracias a ti, nos vemos por el foro 😉
[…] Alvarez para Maestros del Web.Agrega tu comentario | Enlace permanente al […]
Gracias por sus aportes pero como es normal nacen dudas, como la siguiente: ¿ en el routing.yml de nuestra aplicacion se crea de manera automática el enrrutamiento para llamar las rutas que definimos de manera manual en el routing.yml de nuestro Bundle o tambien toca definir de manera manual el llamado del archi routing.yml de nuestro Bundle dentro del routing.yml de la aplicacion?
@Jorge Las rutas se pueden generar de manera automatica cuando creas un CRUD desde la linea de comando
$ php app/console generate:doctrine:crud
Al ejecutar este comando configuras :
-Nombre de la entidad.
-Prefijo de la ruta
-Generar en controlador acciones new, create, edit, update y delete
-Formato de rutas puedes elegir el formato entre (yml, xml, php ó annotation)
En lo personal prefiero generar las rutas por medio de anotaciones en los controladores.
Complementando lo indicado por @Jesus, en cada generador se te hará la pregunta de si deseas actualizar el routing.yml de la aplicación, por ejemplo al crear un Bundle con “app/console generate:bundle” al final te preguntará si deseas actualizarlo.
Recuerda que Symfony 2 admite 4 formatos: YAML, XML, Annotation y PHP, depende de tí elegir cual te conviene mejor, al final el sistema de Caché se Sf2 se encarga de convertirlos en código PHP para mejorar el rendimiento, en ésta guía seguiremos con YAML por ser uno de los más fáciles de entender, luego en el último capítulo “Seguridad” daré una breve introducción del uso de Annotation que es parte del SensioFrameworkExtraBundle, saludos 😉
Ufff que joya de guía, hasta le estoy cogiendo cariño a SF2. Gracias Maycol!
Hola cuando sale el proximo capitulo? Gracias por toda esta info amigos!!!! de verdad esta buena. Pero estoy ancioso por continuar… cuando sale el proximo?
Saludos amigos, esta excelente esta guía, una pregunta que me tiene algo inquieto, “si si, se que me estoy apresurando” pero en ningún lado he encontrado como pasar una Bundle a producción? para poder verlo desde app.php (quiero probar el manejo de la cache). Aparte de eso quisiera saber como configurar el apache2 con mod_rewrite, estoy usando wampserver en window 7 y tambien ubuntu 11.10 (estoy probando en los dos ambientes)
OK ahora tengo una confusión conceptual, cuando utilice la consola de symfony y le dije que actualizara routing.yml lo hizo en el de producción, ahora como routing_dev.yml invoca a routing.yml al final, las rutas están disponibles para desarrollo y producción también? o me equivoco?
lo que no me cuadra es que el demo que venia de ejemplo no estaba en routing.yml sino en routing_dev.ym, para eso es que la consola te pregunta si deseas actualizar routing.yml?
Bueno si no es así, por favor aclárenme
pero si quisiera saber lo del mod_rewrite de apache 2
Saludos! y disculpen el enredo jejejeje
Si, este es el único defecto que le encuentre a esta guía, que hay que esperar y mientras???? jejeje, lo que voy hacer es pasar algunos de los desarrollos que tengo a symfony (apoyándome en la documentación oficial) mientras, a ver como me va
Los capítulos se publican semanalmente cada miércoles, al finalizar dispondrán de la versión en PDF al costo de un Tweet (es decir, gratis) 😉
Como lo indique anteriormente en éste capítulo, el routing.yml de producción es llamado/importado desde el routing_dev.yml de desarrollo, por lo cual no hace falta absolutamente nada para pasar a producción más allá de borrar la caché, sea por consola con $ app/console cache:clear o borrando manualmente el contenido del directorio app/cache.
El problema se presenta sólo en desarrollo cuando necesitas probar las rutas que coincidan con el AcmeDemoBundle, por lo tanto la ruta principal “\”, lo cual se resuelve al eliminar dichas rutas como lo especifica la última sección “Rutas por defecto en el entorno de Desarrollo”, si comentas o eliminas las rutas del AcmeDemoBundle no tendrás problemas tanto en producción como en desarrollo, 😉
Gracias a ti 😉
Creo que es importante definir si hay un estándar para el nombre de los IDs de las rutas, para evitar que se solapen con otras rutas, por ejemplo: en FosUserBundle todas las rutas van predecidas de “fos_user” y luego viene el controlador y la acción en sus nombres cortos. Por ejemplo: FOSUserBundle:Security:login la ruta podría ser fos_user_security_login.
Yo estoy pendiente de buscar las mejores maneras de resolver problemas, nombrar rutas, clases, controladores, bundles, servicios, parámetros de configuración, etc. porque me parece que deberíamos saber y usar estándares que faciliten contratar y ser contratado en futuros proyectos que usen Symfony 2.
[…] VN:F [1.9.15_1155]please wait…Rating: 0.0/5 (0 votes cast)Siguiendo con la explicación del capítulo anterior de la guía de Symfony 2, hoy se ha publicado la segunda parte del sistema de routing en donde se […]
No se si Maycol estará al tanto de algún estandar pero creo que no hay uno oficial. Yo suelo usar BUNDLE_NOMBRERUTA para que no se repita por bundle. Maycol, me corriges si sabes de algo.
Con el FOSUserBundle se usa una abreviatura pero creo que tampoco hay un estándar de como abreviar. Pero si estoy de acuerdo, debería haber un estándar.
Bueno, no he investigado mucho al respecto, pero creo que no existe un “estándar” propiamente dicho, lo que si he visto es que el SensioFrameworkExtraBundle define el nombre por defecto de la ruta en Annotation si no estableces explícitamente su valor, pero no llega a ser un “estándar”, todavía no he probado si me repite el nombre de la ruta en el caso de 2 controladores idénticos de bundles diferentes, pero como comenta Juan, lo ideal es definir un estándar al iniciar el proyecto y nada mejor que utilizar el nombre del propio bundle como prefijo, tal cual hacen los generadores 😉
[…] Alvarez para Maestros del Web. Agrega tu comentario | Enlace permanente al […]
Tengo una duda con respecto a la definición e importación de rutas.
Inicialmente al crear el bundle con la consola, automáticamente me genera en el archivo app/config/rounting.yml lo siguiente:
MDWDemoBundle:
resource: “@MDWDemoBundle/Resources/config/routing.yml”
prefix: /
Y en el archivo routing.yml del bundle como tal colocamos esto para definir la ruta hacia el controlador y la accion:
articulos:
pattern: /articulos
defaults: { _controller: MDWDemoBundle:Default:articulos }
Segun lo que entendi entonces debo invertir los codigos para hacer el bundle mas portable?? es decir, colocar esto:
MDWDemoBundle:
resource: “@MDWDemoBundle/Resources/config/routing.yml”
prefix: /
En el routing.yml de cada bundle y esto:
articulos:
pattern: /articulos
defaults: { _controller: MDWDemoBundle:Default:articulos }
y las demas rutas que genere a medida que vaya desarrollando, colocarlas en
app/config/routing.yml
espero haberme explicado bien.
Saludos y espero puedan sacarme de dudas.
Listo!… ya entendí luego de leer y leer y probar y analizar con detenimiento logre entender!… creo que es algo que hace super flexible a Symfony2 entre otras tantas cosas.. de verdad empiezo a amar a symfony2 aún más por tantas bondades que tiene! 😀 Jajajajajajaja
No, el routing.yml de la aplicación debe importar los routing.yml de cada bundle, y dentro de los mismos routing.yml de los bundles definir las rutas de dicho bundle. Recuerda que el el objeto del Bundle es empaquetar una funcionalidad, y si defines reutas en el el routing.yml de la aplicación, ¿como las portas con el bundle?: ¡debes definirlas dentro del bundle y luego importar el bundle a la aplicación ;-)!
Estoy entendiendo cada vez S2. De verdad gracias, estos capítulos me están convenciendo cada vez mas de trabajar lo mas pronto posible en S2. Amigo espero no salirme del tema, pero cada vez que modifico un archivo “yml” y limpio la cache me dice el navegador a la hora de acceder a una acción “RuntimeException: Failed to write cache file “/miruta/Symfony/app/cache/dev/classes.php”. Estoy trabajando con Ubuntu, y asigno los permisos de lectura y escritura de esta forma “chmod ugo+rw -R app/cache/” (asigno permisos de lectura y escritura de forma recursiva) y me muestra la acción perfectamente, pero nuevamente limpio la caché y muestra el mensaje de “RuntimeException”. ¿A alguien le ha pasado este detalle?, en S1.2 limpiaba caché y no me ocurría eso. Saludos compatriota.
Tuve problemas con Ubuntu debido a los permisos entre el usuario www-root y el de consola, lo solucioné añadiendo umask(0000); al principio de app/console y de web/app_dev.php para que al menos en el entorno de desarrollo no tenga conflictos entre usuarios, también puedes aplicárselo al app.php en tu localhost para probar, “pero NUNCA apliques ello en el verdadero servidor de producción”, intenta este truco en tu localhost, aunque me parece raro ese error al escribir la caché, no recuerdo que fueran los mismos que se me presentaron.
El ultimo párrafo antes del resumen esta algo complicado de comprender.
“pero debido a que el routing.yml normal es importado al final por éste”
Se supone que aunque no se importe “routing.yml”, de igual manera se origina conflicto al usar una ruta que AcmeDemoBundle ya use en el yml de desarrollo.
Como se puede hacer para quitar el directorio web y el archivo app.php de la url . es decir
de esta url symfony/web/app.php/hello que quede symfony/hello
. Con el htaccess pude quitar el app.php pero no puedo quitar la carpeta web
Excelente, he empezado ayer leyendo la documentación de symfony2, pero nada mas claro que en nuestro propio idioma. =)
Acabo de agregar la pagina a mis marcadores!
Gracias por compartir tu conocimento, muchas gracias.