jueves, 21 de agosto de 2014
Symfony - Día 5: El Enrutamiento
Si has completado el día 4, ahora deberías estar familiarizado con el patrón MVC y deberías sentir más y más natural esta forma de codificación. Dedica un poco más de tiempo con esto para no tener que volver y mirar hacia atrás. Para practicar un poco el día de ayer, hemos personalizado la páginas Jobeet y en el proceso, también examinamos varios conceptos de Symfony, como el layout, los helpers, y los slots.
Hoy nos sumergiremos en el maravilloso mundo del Framework de Enrutamiento de Symfony .
Las URLs
Si haces clic en un puesto de trabajo en la página principal Jobeet, la URL se parece a esto:
/job/show/id/1
. Si ya has desarrollado sitios web PHP, probablemente estés más acostumbrados a las URL como /job.php?id=1
. ¿Cómo Symfony hace para que funcione? ¿Cómo Symfony determina la acción a llamar basándose en esta URL? ¿Porqué el id
de un job se obtiene con $request->getParameter('id')
? Hoy, vamos a responder a todas estas preguntas.
Pero primero, vamos a hablar acerca de las URL y exactamente que son ellas. En un contexto web, una URL es el identificador único de un recurso web. Cuando accedes a una URL, estás pidiendo al navegador obtener un recurso identificado por esa URL. Por lo tanto, como la dirección URL es la interfaz entre la página web y el usuario, debe transmitir información significativa sobre algún recurso al que hace referencia. Pero las "tradicionales" URLs realmente no describen al recurso, sino que exponen la estructura interna de la aplicación. Al usuario no le importa que tu sitio web sea desarrollado con el lenguaje PHP o que el puesto de trabajo tiene un cierto identificador en la base de datos. Exponer el funcionamiento interno de tu aplicación es también es bastante malo en lo que medida de seguridad se refiere: ¿Qué pasa si el usuario intenta adivinar la dirección URL de los recursos que no tienen acceso? Así es, el desarrollador debe asegurarlos de la manera adecuada, pero más te vale ocultar la información sensible.
Las URL son tan importantes en Symfony que tiene todo un framework dedicado a su gestión: el framework de enrutamiento. El enrutamiento gestiona el URI interno y la URL externa. Cuando una petición llega, el enrutamiento analiza la URL y la convierte en un URI interno.
Ya has visto el URI interno de la página de puestos de trabajo en la plantilla
indexSuccess.php
:'job/show?id='.$job->getId()
El helper
url_for()
convierte éste URI interno a una correcta URL:/job/show/id/1
El URI interno está hecho de varias partes:
job
es el módulo, show
es la acción y la cadena de consulta añade los parámetros a pasar a la acción. El modelo genérico para los URIs internos es:MÓDULO/ACCIÓN?clave=valor&clave_1=valor_1&...
Como el enrutamiento de Symfony es un proceso bidireccional, puedes cambiar las URLs sin cambiar la implementación técnica. Esta es una de las principales ventajas del patrón de diseño sobre controlador frontal.
La Configuración del Enrutamiento
El mapeo entre los URIs internos y las URLs externas esta listo en el archivo de configuración
routing.yml
:# apps/frontend/config/routing.yml homepage: url: / param: { module: default, action: index } default_index: url: /:module param: { action: index } default: url: /:module/:action/*
El archivo
routing.yml
describe las rutas. Una ruta tiene un nombre (homepage
), un patrón (/:module/:action/*
), y algunos parámetros (bajo la clave param
).
Cuando una petición llega, el Enrutamiento trata de hacerla coincidir la URL con un patrón dado. La primera ruta que coincida gana, por lo tanto el orden en
routing.yml
es importante. Echemos un vistazo a algunos ejemplos para comprender mejor cómo funciona esto.
Cuando solicitas la página de inicio Jobeet, la cual tiene la URL
/job
, la primera ruta que coincide es con default_index
. En un patrón, una palabra con un prefijo dos puntos (:
) es una variable, por eso el patrón /:module
significa: Concidir con un /
seguida por cualquier cosa. En nuestro ejemplo, la variable module
será job
como su valor. Este valor puede ser obtenido con $request->getParameter('module')
. Esta ruta también define un valor por defecto para la variable action
. Por eso, para todas las URLs que coincidan con esta ruta, la petición también tendrá un parámetro action
con index
como su valor.
Si solicitas la página
/job/show/id/1
, Symfony coincidirá con el último patrón:/:module/:action/*
. En un patrón, un asterisco (*
) coincide con una colección de pares variable/valor separados por una barra (/
):Parámetro de petición | Valor |
---|---|
módulo | job |
acción | show |
id | 1 |
Las variables
módulo
y acción
son especiales ya que son utilizados por Symfony para determinar la acción a ejecutar.
La URL
/job/show/id/1
se pueden crear desde una plantilla utilizando la siguiente llamada al helper url_for()
:url_for('job/show?id='.$job->getId())
También puedes usar el nombre de la ruta gracias al prefijo
@
:url_for('@default?module=job&action=show&id='.$job->getId())
Ambas llamadas son equivalentes, pero esta última es mucho más rápido ya que el enrutamiento no tiene que analizar todas las rutas para encontrar el mejor patrón coincidente, y es menos complicado su implementación (los nombres del módulo y la acción no están presentes en el URI interno).
Personalizaciones del Enrutamiento
Por el momento, cuando la solicitas la URL
/
en un navegador, tienes por defecto la página de felicitaciones de Symfony. Esto se debe a que esta URL coincide con la ruta homepage
. Pero tiene sentido cambiarla para que no sea la página de inicio de Jobeet. Para hacer el cambio, modifica la variable module
de la ruta homepage
a job
:# apps/frontend/config/routing.yml homepage: url: / param: { module: job, action: index }
Puedes ahora cambiar el enlace al logo de Jobeet en el layout para usar la ruta
homepage
:<!-- apps/frontend/templates/layout.php --> <h1> <a href="<?php echo url_for('@homepage') ?>"> <img src="/legacy/images/jobeet.gif" alt="Jobeet Job Board" /> </a> </h1>
Eso fue fácil!
Cuando se actualiza la configuración del enrutamiento, los cambios son immediatamente tomados en cuenta en el entorno de desarrollo. Pero para hacerlos que tambien funcionen en el entorno de producción, necesitas limpiar el cache ejecutando la tarea
cache:clear
.
Para algo un poco más aplicado, vamos a cambiar el la URL de la página job a algo más significativo:
/job/sensio-labs/paris-france/1/web-developer
Sin saber nada acerca de Jobeet, y sin mirar en la página, se puede entender a partir de la URL que Sensio Labs está buscando un Web developer para trabajar en Paris, France.
Las URLs amigables son importantes porque ellas transmiten información al usuario. Es también útil cuando copias y pegas la URL en un email o para optimizar tu sitio web para los motores de búsqueda.
El siguiente patrón coincide con esta URL:
/job/:company/:location/:id/:position
Edita el archivo
routing.yml
y agrega la ruta job_show_user
al principio del archivo:job_show_user: url: /job/:company/:location/:id/:position param: { module: job, action: show }
Si actualizas la página de inicio Jobeet, los enlaces a jobs no han cambiado. Esto se debe a que para generar una ruta, necesitas pasar todas las variables requeridas. Por eso, necesitas cambiar la llamada a
url_for()
en indexSuccess.php
a:url_for('job/show?id='.$job->getId().'&company='.$job->getCompany(). '&location='.$job->getLocation().'&position='.$job->getPosition())
Un URI interno también puede ser expresado como un array:
url_for(array( 'module' => 'job', 'action' => 'show', 'id' => $job->getId(), 'company' => $job->getCompany(), 'location' => $job->getLocation(), 'position' => $job->getPosition(), ))
Los Requisitos
Durante el primer día de tutoría, hemos hablado de la validación y manejo de errores por una buena razón. El sistema de enrutamiento tiene incorporada una función de validación. Cada variable patrón pueda ser validada por una expresión regular definida utilizando la linea
requirements
en la definición de la ruta:job_show_user: url: /job/:company/:location/:id/:position param: { module: job, action: show } requirements: id: \d+
La anterior linea
requirements
fuerza a que el id
sea un valor numérico. Sino lo es, la ruta no coincide.La Clase Route
Cada ruta definida en
routing.yml
es internamente convertida en un objecto de la clasesfRoute
. Esta clase se puede cambiar mediante una linea class
en la definición de la ruta. Si estás familiarizado con el protocolo HTTP, sabes que éste define varios "métodos", como GET
,POST
, HEAD
, DELETE
, y PUT
. Los tres primeros cuentan con soporte en todos los navegadores, mientras que los otros dos no.
Para restringir una ruta a sólo la coincidencia con algunos métodos, puedes modificar la clase de enrutamiento a
sfRequestRoute
y añadir un requisito para la variable virtual sf_method
:job_show_user: url: /job/:company/:location/:id/:position class: sfRequestRoute param: { module: job, action: show } requirements: id: \d+ sf_method: [get]
Exigir una ruta para solo algunos métodos HTTP no es totalmente equivalente a usar
sfWebRequest::isMethod()
en tus acciones. Eso es porque el enrutamiento continuará buscando por una ruta coincidente si el método no coincide con el esperadoEl Objecto de la Clase Route
La nuevo URI interno para un puesto de trabajo es bastante largo y tedioso de escribir, (
url_for('job/show?id='.$job->getId().'&company='.$job->getCompany().'&location='.$job->getLocation().'&position='.$job->getPosition())
), pero como acabamos de aprender en la sección anterior, la clase route puede ser modificada. Para la ruta job_show_user
, es mejor utilizar sfDoctrineRoute
ya que la clase está optimizada para las rutas que representan objetos Doctrine o colecciones de objetos Doctrine:job_show_user: url: /job/:company/:location/:id/:position class: sfDoctrineRoute options: { model: JobeetJob, type: object } param: { module: job, action: show } requirements: id: \d+ sf_method: [get]
La linea
options
personaliza el comportamiento de la ruta. Aquí, la opción model
define la clase del módelo Doctrine (JobeetJob
) relacionada a la ruta, y la opción type
define que esta ruta está vinculada a un objeto (también puedes utilizar list
si una ruta representa una colección de objetos).
La ruta
job_show_user
es ahora consciente de su relación con JobeetJob
y así podemos simplificar la llamada url_for()
a:url_for(array('sf_route' => 'job_show_user', 'sf_subject' => $job))
o solo:
url_for('job_show_user', $job)
El primer ejemplo es muy útil cuando necesitas pasar más argumentos que sólo un objeto.
Funciona porque todas las variables en la ruta tiene su método correspondiente en la clase
JobeetJob
(por ejemplo, la variable company
es reemplazada con el valor de getCompany()
).
Si hechas una mirada a las URL generadas, no son todavía bastantes amigables como queremos que sean:
http://www.jobeet.com.localhost/frontend_dev.php/job/Sensio+Labs/Paris%2C+France/1/Web+Developer
Tenemos que "slugear" los valores de columna mediante la sustitución de todos los caracteres no ASCII por un
-
. Abre el archivo JobeetJob
y añade los siguientes métodos para la clase:// lib/model/doctrine/JobeetJob.class.php public function getCompanySlug() { return Jobeet::slugify($this->getCompany()); } public function getPositionSlug() { return Jobeet::slugify($this->getPosition()); } public function getLocationSlug() { return Jobeet::slugify($this->getLocation()); }
A continuación, crea el archivo
lib/Jobeet.class.php
y añadir el método slugify
en él:// lib/Jobeet.class.php class Jobeet { static public function slugify($text) { // replace all non letters or digits by - $text = preg_replace('/\W+/', '-', $text); // trim and lowercase $text = strtolower(trim($text, '-')); return $text; } }
En este tutorial, nunca mostramos la sentencia de apertura
<?php
en los ejemplos de código que solo tienen código PHP puro para optimizar el espacio. Deberías obviamente recordar agregarlos cuando creas un nuevo archivo PHP.
Tenemos definidos tres nuevos métodos "virtuales" :
getCompanySlug()
, getPositionSlug()
, ygetLocationSlug()
. Ellos devuelven sus correspondiente valor de columna después de pasalos por el método slugify()
. Ahora, puedes sustituir los nombres de las columas reales por sus virtuales en la ruta job_show_user
:job_show_user: url: /job/:company_slug/:location_slug/:id/:position_slug class: sfDoctrineRoute options: { model: JobeetJob, type: object } param: { module: job, action: show } requirements: id: \d+ sf_method: [get]
Tendrás ahora las URLs esperadas:
http://www.jobeet.com.localhost/frontend_dev.php/job/sensio-labs/paris-france/1/web-developer
Pero esa es sólo la mitad de la historia. La ruta es capaz de generar una URL sobre la base de un objeto, pero también es capaz de encontrar el objeto en relación con una determinada URL. Los objetos pueden ser recuperados con el método
getObject()
de la ruta. Al analizar una petición, el enrutamiento guarda la ruta del objeto coincidentes para que la uses en las acciones. Por lo tanto, cambia el método executeShow()
para utilizar el objeto route para obtener el objetoJobeet
:class jobActions extends sfActions { public function executeShow(sfWebRequest $request) { $this->job = $this->getRoute()->getObject(); $this->forward404Unless($this->job); } // ... }
Si intentas obtener un job para un desconocido
id
, verás una página de error 404 pero el mensaje de error ha cambiado:
Esto es así porque el error 404 ha sido lanzado de forma automática por el método
getRoute()
. Así, podemos simplificar el método executeShow
aún más:class jobActions extends sfActions { public function executeShow(sfWebRequest $request) { $this->job = $this->getRoute()->getObject(); } // ... }
Si no deseas que la ruta genere un error 404, puede establecer la opción
allow_empty
a true
.
El objeto relacionado de una ruta es cargado ligeramente. Este es obtenido desde la base de datos solo si llamas al método
getRoute()
.El Enrutamiento en Acciones y Plantillas
En una plantilla, el helper
url_for()
convierte una URI interna a una URL external. Algunos otros helpers symfony también toman una URI interna como un argumento, como el helperlink_to()
el cual genera una etiqueta <a>
:<?php echo link_to($job->getPosition(), 'job_show_user', $job) ?>
Genera el siguiente código HTML:
<a href="/job/sensio-labs/paris-france/1/web-developer">Web Developer</a>
Para ambos
url_for()
y link_to()
también pueden generar URL absoluta:url_for('job_show_user', $job, true); link_to($job->getPosition(), 'job_show_user', $job, true);
Si deseas generar una URL desde una acción, puedes usar el método
generateUrl()
:$this->redirect($this->generateUrl('job_show_user', $job));
La Familia de Métodos "redirect"
En el tutorial de ayer, hablamos acerca del método "forward". Estos métodos envían l apetición actual a otra acción sin regresar al navegador.
El método "redirect" redirige al usuario a otra URL. Al igual que con forward, puedes utilizar el método
redirect()
, o los métodos redirectIf()
yredirectUnless()
.La Clase de Colección de Rutas
Para el módulo
job
, ya tenemos personalizado la ruta de la acción show
, pero las URLs para los otros métodos (index
, new
, edit
, create
, update
, and delete
) están aun gestionadas por la rutadefault
:default: url: /:module/:action/*
La ruta
default
es una gran manera de comenzar la codificación sin definir demasiadas rutas. Pero como la ruta actúa como un "catch-all", no puede ser configurada para necesidades específicas.
Como todas las acciones
job
están relacionadas con el modelo de la clase JobeetJob
, podemos facilmente definir una ruta particular sfDoctrineRoute
para cada uno de ellas como ya lo hemos hecho para la acción show
. Sin embargo, como el módulo job
define las clásicas siete posibles acciones para un modelo, también podemos utilizar la clase sfDoctrineRouteCollection
. Abre el archivo routing.yml
y modificalo para que se lea como sigue:# apps/frontend/config/routing.yml job: class: sfDoctrineRouteCollection options: { model: JobeetJob } job_show_user: url: /job/:company_slug/:location_slug/:id/:position_slug class: sfDoctrineRoute options: { model: JobeetJob, type: object } param: { module: job, action: show } requirements: id: \d+ sf_method: [get] # default rules homepage: url: / param: { module: job, action: index } default_index: url: /:module param: { action: index } default: url: /:module/:action/*
La ruta
job
anterior es en realidad un acceso directo que generará automáticamente las siguientes siete rutas sfDoctrineRoute
:job: url: /job.:sf_format class: sfDoctrineRoute options: { model: JobeetJob, type: list } param: { module: job, action: index, sf_format: html } requirements: { sf_method: get } job_new: url: /job/new.:sf_format class: sfDoctrineRoute options: { model: JobeetJob, type: object } param: { module: job, action: new, sf_format: html } requirements: { sf_method: get } job_create: url: /job.:sf_format class: sfDoctrineRoute options: { model: JobeetJob, type: object } param: { module: job, action: create, sf_format: html } requirements: { sf_method: post } job_edit: url: /job/:id/edit.:sf_format class: sfDoctrineRoute options: { model: JobeetJob, type: object } param: { module: job, action: edit, sf_format: html } requirements: { sf_method: get } job_update: url: /job/:id.:sf_format class: sfDoctrineRoute options: { model: JobeetJob, type: object } param: { module: job, action: update, sf_format: html } requirements: { sf_method: put } job_delete: url: /job/:id.:sf_format class: sfDoctrineRoute options: { model: JobeetJob, type: object } param: { module: job, action: delete, sf_format: html } requirements: { sf_method: delete } job_show: url: /job/:id.:sf_format class: sfDoctrineRoute options: { model: JobeetJob, type: object } param: { module: job, action: show, sf_format: html } requirements: { sf_method: get }
Algunas rutas generadas por
sfDoctrineRouteCollection
tienen la misma URL. El enrutamiento está aún disponible para usarlas porque todas ellas tienen diferentes parámetro en el método HTTP.
Las rutas
job_delete
y job_update
require métodos HTTP que no son compatibles con los navegadores (DELETE
y PUT
respectivamente). Esto funciona porque los simula Symfony. Abre la plantilla _form.php
Para ver un ejemplo:// apps/frontend/modules/job/templates/_form.php <form action="..." ...> <?php if (!$form->getObject()->isNew()): ?> <input type="hidden" name="sf_method" value="PUT" /> <?php endif; ?> <?php echo link_to( 'Delete', 'job/delete?id='.$form->getObject()->getId(), array('method' => 'delete', 'confirm' => 'Are you sure?') ) ?>
Todos los helpers symfony pueden ser invocados para simular cualquier método HTTP que desea pasando el parámetro especial
sf_method
.
Symfony tiene otros parámetros especiales como
sf_method
, todo lo que inicie con el prefijo sf_
. En las rutas generadas antes, se puede ver otro: sf_format
, que se explicará en los próximos días.Debugeando la Ruta
Cuando se utiliza una colección de rutas, a veces es útil listar de rutas generadas. La tarea
app:routes
muestra todas las rutas para una aplicación determinada:$ php symfony app:routes frontend
También puedes tener una gran cantidad de información de depuración para una ruta pasando su nombre como un argumento adicional:
$ php symfony app:routes frontend job_edit
Las Rutas por defecto
Es una buena práctica definir las rutas para todas tus URLs. Ya que la ruta
job
define todas las rutas necesarias para describir la aplicación Jobeet, sigue adelante y quita o comenta las rutas por defecto del archivo de configuración routing.yml
:# apps/frontend/config/routing.yml #default_index: # url: /:module # param: { action: index } # #default: # url: /:module/:action/*
La aplicación Jobeet debe seguir funcionando como antes.
Referencia: http://symfony.com/legacy/doc/jobeet/1_4/es/05?orm=Doctrine
Suscribirse a:
Enviar comentarios
(
Atom
)
Sígueme en las Redes Sociales
Donaciones
Datos personales
Entradas populares
-
En este apartado vamos a explicar como ejercutar archivos PHP a través del terminal de Ubuntu. Lo primero que tendríamos que hacer es inst...
-
En este blog voy a comentar un tema que se utilizan en casi todas las páginas web que existen, y es el tema de la paginación. La paginaci...
-
Este post trata de la integración de la librería PHPExcel en Codeigniter, aunque se podría aplicar a cualquier librería, como por ejemplo mP...
-
Ejemplo para añadir o sumar un número determinado de hora/s, minuto/s, segundo/s a una fecha en php. Con la función strtotime se puede ...
-
Este tema es uno de los temas primordiales sobre el framework Codeigniter, ya que en alguna ocación nos hemos visto obligados a recoger dato...
© Espacio Daycry - Espacio de programación 2013 . Powered by Bootstrap , Blogger templates and RWD Testing Tool
No hay comentarios :
Publicar un comentario